如何避免使用Java ServerSocket阻塞?
我正在研究一个套接字监听器,它必须在2个端口上侦听2种类型的数据(端口80和端口81)。 这些数据与对数据执行的操作类型非常相似,只是因为它们到达不同的端口而不同。 我继续使用Java的ServerSocket类编写了一个实现,但后来才意识到ServerSocket类的accept()方法是块,我的实现无法承受。 所以现在我正在考虑使用Java NIO实现相同的function,但在完成了一些教程之后,我认为我比我的开始更困惑。 如果这里的某个人可以引导我完成整个过程,即使它是伪代码或技术“下一步该做什么”,那将是很棒的。 这就是我打算实现的目标。
通过调用2个类似的线程来监听,就像永远在2个端口上一样。(非阻塞)来自某个网络位置的远程设备连接,发送数据然后断开连接。
我想如果只知道如何使用NIO来设置服务器来监听端口,那么就可以实现localhost上的端口80,其余部分都很容易实现。
干杯
当您需要扩展到数千个并发连接时,需要NIO。
否则,我建议使用多个线程。 对于每个端口(及其相应的ServerSocket
),创建一个在循环中调用accept()
的线程。 这些调用会阻塞,但这没关系,因为其他线程正在运行,负责任何可用的任务。
接受新Socket
,创建专用于该连接的另一个线程。 它取决于应用程序,但通常此线程将从套接字读取(阻塞操作),并执行请求的操作,将结果写回套接字。
在大多数桌面平台上,此体系结构将扩展到数百个连接。 编程模型非常简单,只要每个连接都是独立的并且独立于其他连接(这可以避免并发问题)。 引入NIO将提供更多可扩展性,但需要很多复杂性。
这里有一个开始使用NIO的小例子。
它是一个侦听端口80和81的服务器,并打印标准输出上接收的所有内容。 收到以CLOSE
开头的数据包后CLOSE
; 收到以QUIT
开头的数据包后,整个服务器都会关闭。 缺少发送部分和error handling可能会好一点。 🙂
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 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; } } } }
Obs : SelectionKey
( OP_ACCEPT
…)的字段是静态导入的:
import static java.nio.channels.SelectionKey.*;
许多框架,例如Apache MINA和Netty ,都是基于Java NIO实现的,以增强非阻塞IO编程。 我强烈建议他们让你的NIO编程变得快乐,而不是噩梦。 它们适合你的问题。
此外,尝试在传输消息大小和编码/解码(序列化/反序列化)性能中使用有效的协议。 Google Protocol Buffers是该领域的可靠解决方案。 还可以看看Kryo和KryoNet 。 他们可以提供帮助。