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

时间:2016-10-22 12:39:10

标签: java sockets chat nio socketchannel

朋友们!我是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 {

}

}

1 个答案:

答案 0 :(得分:1)

你的代码遭受了通常的大量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);
}

见上文重新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()不是对等断开连接的有效测试。