如何避免使用Java ServerSocket时被阻塞?

14

我正在开发一个套接字监听器,需要监听两个端口上的两种数据(端口80和端口81)。这些数据非常相似,因为它们执行的操作类型相同,只是因为它们到达不同的端口而不同。我使用Java的ServerSocket类编写了一个实现,后来才意识到ServerSocket类的accept()方法是阻塞的,而我的实现无法承受这种情况。所以我现在正在考虑使用Java NIO来实现相同的功能,但是在经过一些教程之后,我觉得比刚开始还更加困惑了。如果有人能够在整个过程中为我提供帮助,即使是伪代码或者技术上的“下一步”提示,那将非常棒。

实现的目标如下:

通过调用2个类似的线程(非阻塞),永久侦听2个端口。 远程设备从某个网络位置连接,发送数据,然后断开连接。

我认为,如果只要掌握如何使用NIO在本地主机上设置服务器以侦听例如端口80的端口,那么其余的实现就都很容易了。

干杯

3个回答

15

这是一个使用NIO入门的小例子。

这是一个监听端口80和81并打印所有接收到内容的服务器。当收到以CLOSE开头的数据包时,连接将关闭;当收到以QUIT开头的数据包时,整个服务器将关闭。缺少发送部分和错误处理可能需要更好一些。:-)

public static void main() throws IOException {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    Selector selector = Selector.open();

    ServerSocketChannel server1 = ServerSocketChannel.open();
    server1.configureBlocking(false);
    server1.socket().bind(new InetSocketAddress(80));
    server1.register(selector, OP_ACCEPT);

    ServerSocketChannel server2 = ServerSocketChannel.open();
    server2.configureBlocking(false);
    server2.socket().bind(new InetSocketAddress(81));
    server2.register(selector, OP_ACCEPT);

    while (true) {
        selector.select();
        Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
        while (iter.hasNext()) {
            SocketChannel client;
            SelectionKey key = iter.next();
            iter.remove();

            switch (key.readyOps()) {
                case OP_ACCEPT:
                    client = ((ServerSocketChannel) key.channel()).accept();
                    client.configureBlocking(false);
                    client.register(selector, OP_READ);
                    break;
                case OP_READ:
                    client = (SocketChannel) key.channel();
                    buffer.clear();
                    if (client.read(buffer) != -1) {
                        buffer.flip();
                        String line = new String(buffer.array(), buffer.position(), buffer.remaining());
                        System.out.println(line);
                        if (line.startsWith("CLOSE")) {
                            client.close();
                        } else if (line.startsWith("QUIT")) {
                            for (SelectionKey k : selector.keys()) {
                                k.cancel();
                                k.channel().close();
                            }
                            selector.close();
                            return;
                        }
                    } else {
                        key.cancel();
                    }
                    break;
                default:
                    System.out.println("unhandled " + key.readyOps());
                    break;
            }
        }
    }
}

注意: SelectionKey 的字段 (OP_ACCEPT...) 是静态导入的:

import static java.nio.channels.SelectionKey.*;

8

当您需要扩展到成千上万个同时连接时,建议使用NIO。

否则,我建议使用多线程。对于每个端口(及其相应的 ServerSocket ),创建一个调用循环中的 accept()的线程。这些调用将被阻塞,但这是可以接受的,因为其他线程正在运行,处理任何可用任务。

当接受新的 Socket 时,请创建另一个专用于该连接的线程。它取决于应用程序,但通常此线程将从套接字读取(一种阻塞操作),并执行请求的操作,将结果写回套接字。

这种架构在大多数桌面平台上可以扩展到许多百个连接。只要每个连接都是自包含且独立于其他连接的(这避免了并发问题),编程模型就相当简单。引入NIO将提供更高的可扩展性,但需要更复杂的操作。


2
并没有真正回答问题:使用NIO的套接字监听器 - user458577

8
许多框架,例如Apache MINANetty,都是基于Java NIO实现的,可以提高非阻塞IO编程的效率。我强烈建议使用它们来使您的NIO编程变得简单而不会成为噩梦。它们适合您的问题。
此外,尽量使用在传输消息大小和编码/解码(序列化/反序列化)性能方面高效的协议。在这个领域,Google Protocol Buffers是一个可靠的解决方案。还要看看KryoKryoNet。它们可能会有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接