[英]Java NIO Server/Client Chat App - sending data only by closing the socket
朋友们! 我是Java NIO的新手,目前正在尝试制作一个非阻塞的聊天应用程序。 客户端毫无问题地连接到服务器。 客户端向服务器写入一条消息或几条消息,但是服务器仅在从客户端代码关闭Socket连接时才开始读取消息,因此必须为每个消息在客户端代码中创建并关闭SocketChannel(或仅Socket) -在我看来,这似乎不对。 我已经用简单的Java I / O以及NIO Selector尝试了客户端。 同样的问题-仅当从客户端关闭SocketChannel或Socket时,服务器才开始读取。 有人可以告诉我进行这种非阻塞连接的正确方法,还是告诉我我的逻辑错误...非常感谢!
这是服务器代码:
public class NIOServer implements Runnable {
@Override
public void run() {
try {
runServer();
} catch (IOException e) {
e.printStackTrace();
}
}
private void runServer() throws IOException {
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(8080));
server.configureBlocking(false);
Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
while(true) {
int readyChannels = selector.selectNow();
if(readyChannels==0){
continue;
}
System.out.println("Ready channels: "+readyChannels);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
keyIterator.remove();
if(key.isAcceptable()){
ServerSocketChannel acceptableServer = (ServerSocketChannel)key.channel();
SocketChannel client = server.accept();
if(client!=null){
System.out.println("Client accepted!");
client.configureBlocking(false);
SelectionKey selectionKey = client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
}
}
if (key.isReadable()) {
read(key);
}
/*if(key.isConnectable()){
System.out.println("connectable");
}
if(key.isWritable()){
//System.out.println("writable");
}*/
}
}
}
public void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel)key.channel();
channel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(100);
buffer.clear();
int bytesRead = channel.read(buffer);
while(bytesRead>0){
System.out.println("Read bytes: "+ bytesRead);
bytesRead=channel.read(buffer);
if(bytesRead==-1){
channel.close();
key.cancel();
}
buffer.flip();
while(buffer.hasRemaining()){
System.out.print((char)buffer.get());
}
}
//key.cancel();
//channel.close();
}
}
带有NIO选择器的客户端:
public class NIOSelectorClient implements Runnable{
private Selector selector;
@Override
public void run() {
try {
startClient();
} catch (IOException e) {
e.printStackTrace();
}
}
public void startClient() throws IOException {
SocketChannel socketChannel= openConnection();
selector = Selector.open();
socketChannel.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);
while(!Thread.interrupted()) {
int readyChannels = selector.selectNow();
if(readyChannels==0) {
continue;
}
Set<SelectionKey> keySet = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keySet.iterator();
while(keyIterator.hasNext()) {
SelectionKey currentKey = keyIterator.next();
keyIterator.remove();
if(!currentKey.isValid()) {
continue;
}
if(currentKey.isConnectable()) {
System.out.println("I'm connected to the server!");
handleConnectable(currentKey);
}
if(currentKey.isWritable()){
handleWritable(currentKey);
}
}
}
}
private void handleWritable(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(100);
Scanner scanner = new Scanner(System.in);
System.out.println("Enter message to server: ");
String output = scanner.nextLine();
buffer.put(output.getBytes());
buffer.flip();
//while(buffer.hasRemaining()) {
channel.write(buffer);
//}
System.out.println("Message send");
buffer.clear();
channel.close();
key.cancel();
}
private void handleConnectable(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
if(channel.isConnectionPending()) {
channel.finishConnect();
}
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);
}
private static SocketChannel openConnection() throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
socketChannel.configureBlocking(false);
while(!socketChannel.finishConnect()) {
System.out.println("waiting connection....");
}
return socketChannel;
}
}
这是非NIO的条件:
public class NIOClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(socket.isConnected()) {
//synchronized (socket) {
writeMessage(socket,writer);
//readServerMessage(socket);
//}
}
}
public static void writeMessage(Socket socket, BufferedWriter writer) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter message: ");
String output = "Client 1: " + scanner.nextLine();
writer.write(output);
writer.flush();
//writer.close();
}
public static void readServerMessage(Socket socket) throws IOException {
}
}
您的代码经常遇到大量NIO错误:
public class NIOServer implements Runnable {
private void runServer() throws IOException {
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(8080));
server.configureBlocking(false);
Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
while(true) {
int readyChannels = selector.selectNow();
您正在选择不睡觉。 如果没有就绪通道,则此循环将占用CPU。 使用超时,即使是短暂的超时。
SelectionKey selectionKey = client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
除非您已经写过东西并获得了简短的返回值,否则您不应该注册OP_WRITE。
public void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel)key.channel();
channel.configureBlocking(false);
通道已处于非阻塞模式。 接受时将其放在那里。 除非它处于非阻止模式,否则您无法选择它。 去掉。
ByteBuffer buffer = ByteBuffer.allocate(100);
buffer.clear();
缓冲区已经清除。 您刚刚创建了它。 去掉。
int bytesRead = channel.read(buffer);
while(bytesRead>0){
System.out.println("Read bytes: "+ bytesRead);
bytesRead=channel.read(buffer);
if(bytesRead==-1){
channel.close();
key.cancel();
关闭通道会取消该键。 您不需要两者。 取消取消。
//key.cancel();
//channel.close();
去掉。 不要留下无效的代码来迷惑未来的读者。
带有NIO选择器的客户端:
public class NIOSelectorClient implements Runnable{
private Selector selector;
public void startClient() throws IOException {
SocketChannel socketChannel= openConnection();
selector = Selector.open();
socketChannel.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);
往上看。
while(!Thread.interrupted()) {
int readyChannels = selector.selectNow();
往上看。
if(!currentKey.isValid()) {
continue;
}
很好,但是您需要在下面的其他所有测试之前进行此测试,例如currentKey.isValid() && currentKey.isReadable()
,因为先前的处理程序可能已关闭通道或取消了键。 服务器代码中也是如此。
if(currentKey.isConnectable()) {
System.out.println("I'm connected to the server!");
handleConnectable(currentKey);
}
if(currentKey.isWritable()){
handleWritable(currentKey);
}
您永远不会在客户端中处理isReadable()
。 您不希望有任何输入吗?
private void handleWritable(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(100);
Scanner scanner = new Scanner(System.in);
System.out.println("Enter message to server: ");
String output = scanner.nextLine();
在这里,您正在阻止整个客户端,包括其所有的SocketChannels
等待用户输入一些输入。 这是非常糟糕的设计。
buffer.clear();
你不需要这个 您将要释放缓冲区作为局部变量。 您已经完成了。
channel.close();
一次写入后您要关闭通道吗? 为什么?
key.cancel();
关闭通道会取消该键。 您不需要两者。 你不需要这个 去掉。
private void handleConnectable(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
if(channel.isConnectionPending()) {
channel.finishConnect();
finishConnect()
可以返回false
,在这种情况下,您不应该对此方法做任何进一步的操作。
channel.configureBlocking(false);
通道已处于阻塞模式。 否则,您将无法到达这里。 去掉。
channel.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);
}
参见上文re OP_WRITE。
private static SocketChannel openConnection() throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
socketChannel.configureBlocking(false);
while(!socketChannel.finishConnect()) {
System.out.println("waiting connection....");
}
删除此循环。 这就是OP_CONNECT的目的。 您正在养狗,吠叫自己。 如果您不希望在连接完成之前不离开这里,请以阻止模式进行操作。 不仅仅是抽CPU。
这是非NIO的条件:
public class NIOClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(socket.isConnected()) {
插座已连接。 在构造它时就已连接它。 它保持这种方式。 isConnected()
不是对等断开连接的有效测试。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.