且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Java NIO服务器/客户端聊天应用程序-仅通过关闭套接字来发送数据

更新时间:2023-01-18 09:07:02

您的代码经常遇到大量NIO错误:

Your code suffers from the usual raft of NIO mistakes:

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.使用超时,甚至是短暂的超时.

You are selecting without a sleep. If there are no ready channels this loop will smoke the CPU. Use a timeout, even a short one.

                        SelectionKey selectionKey = client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);

除非已经写过东西并获得简短的返回值,否则不应该注册OP_WRITE.

You should not register for OP_WRITE unless you've already written something and got a short return value.

public void read(SelectionKey key) throws IOException {
    SocketChannel channel = (SocketChannel)key.channel();
    channel.configureBlocking(false);

该频道已处于非阻塞模式.接受时将其放到那里.除非它处于非阻止模式,否则您无法选择它.删除.

The channel is already in non-blocking mode. You put it there when you accepted it. You couldn't have selected on it unless it was in non-blocking mode. Remove.

    ByteBuffer buffer = ByteBuffer.allocate(100);
    buffer.clear();

缓冲区已清除.您刚刚创建了它.删除.

The buffer is already clear. You just created it. Remove.

    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();

关闭频道会取消键.两者都不需要.取消取消.

Closing the channel cancels the key. You don't need both. Remove the cancel.

    //key.cancel();
    //channel.close();

删除.不要留下无效的代码来迷惑未来的读者.

Remove. Don't leave dead code lying around to confuse future readers.

具有NIO选择器的客户端:

Client with NIO Selector:

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(),因为先前的处理程序可能已关闭通道或取消了密钥.服务器代码中也是如此.

Very good but you need this test before every other one below, e.g. currentKey.isValid() && currentKey.isReadable(), because a prior handler may have closed the channel or cancelled the key. Same applies in the server code.

            if(currentKey.isConnectable()) {
                System.out.println("I'm connected to the server!");
                handleConnectable(currentKey);
            }
            if(currentKey.isWritable()){
                handleWritable(currentKey);
            }

您永远不会在客户端中处理isReadable().您不希望有任何输入吗?

You never handle isReadable() in the client. Don't you expect any input?

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),等待用户输入一些输入.这是非常糟糕的设计.

Here you are blocking the entire client including all its SocketChannels waiting for the user to enter some input. This is very poor design.

    buffer.clear();

您不需要这个.您将要释放缓冲区作为局部变量.您已经完成了.

You don't need this. You're about to release the buffer as a local variable. You're done with it.

    channel.close();

一次写入后您要关闭通道吗?为什么?

You're closing the channel after one write? Why?

    key.cancel();

关闭频道会取消键.两者都不需要.您不需要这个.删除.

Closing the channel cancels the key. You don't need both. You don't need this. Remove.

private void handleConnectable(SelectionKey key) throws IOException {
    SocketChannel channel = (SocketChannel) key.channel();
    if(channel.isConnectionPending()) {
        channel.finishConnect();

finishConnect()可以返回false,在这种情况下,您不应该对此方法做进一步的操作.

finishConnect() can return false, in which case you should do nothing further in this method.

    channel.configureBlocking(false);

该频道已处于阻止模式.否则,您将无法到达这里.删除.

The channel is already in blocking mode. Otherwise you couldn't have got here. Remove.

    channel.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);
}

有关OP_WRITE,请参见上文.

See above 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.

Remove this loop. That's what OP_CONNECT is for. You are keeping a dog and barking yourself. If you want not to proceed out of here until the connection is complete, do it in blocking mode. Instead of just smoking the CPU.

这是非NIO的条件:

And this is the non-NIO cliet:

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()不是对等断开连接的有效测试.

The socket is connected. You connected it when you constructed it. It stays that way. isConnected() is not a valid test for peer disconnection.