非阻塞套接字客户端和选择器
Non-blocking socket client and selectors
由于我想尝试实现与 Telegram 服务器的基本 TCP 连接(使用 MTProto),我开始阅读有关 Java NIO 类 的内容。但是,我在试图理解 Selector
s 对客户的意义时“卡住了”。
A selector supports key-based, non-blocking, multiplexed I/O. In other words, selectors enable you to perform I/O through multiple channels. (Java - The complete reference)
由于 TCP 消息作为流总是有序的,而且我只会打开一个套接字连接(一个 SocketChannel
),使用 Selector
s 有什么意义?我觉得没有意义吧?
如果我的自我回答是正确的,为什么不直接使用阻塞 I/O?
NIO
基本上是用在服务器端处理大规模的。我将尝试解释典型服务器的工作原理。
服务器有一个请求队列,轮询线程从中消耗连接作为 blocking dequeue
操作(在 Java 中,请求队列的默认长度为 50。这意味着如果您尝试在请求队列已满时启动第 51 个连接,您将得到 ConnectionRefused
异常)。
一个典型的 blocking
实现是这样的:
服务器接受连接并将其置于 requests queue
。
轮询 thread
从队列头部消耗连接并将其分派给 thread pool
。如果 thread-pool
队列未满并继续使用来自 queue
.
的连接,则轮询线程变为空闲
在某些时候,线程池中的所有线程都将变得繁忙,并且轮询线程将在向池提交更多连接时被阻塞(因为线程池队列是 blocking queue
).
requests queue
同时开始填充。在某些时候,它会变得完全满,服务器将不再接受任何连接。
此时,我们的服务器无法再扩展了。请注意,池中的“繁忙”线程可能根本不忙,而只是被阻塞 - 比如说在它们所服务的相应套接字的 InputStream
上获取更多数据。
现在考虑这个设计:
轮询线程使用请求队列头部的项目。
它把它放在一个无限列表中。
另一个线程不断迭代此列表并检查套接字上是否发生了任何 activity(读到读、准备好写等)。如果有 activity,则提供 socket
。请注意,这些 sockets
在 NIO
模式下运行。也就是说,如果没有activity,我们的线程就不会被阻塞。
轮询线程同时继续向列表提交连接,因为列表是无界的。它不会在任何地方被阻塞(除非它正在等待请求队列上的新连接)。
在上面的设计中,请注意我们的规模仅受我们系统资源的限制——即 list
持有的连接数。响应时间会受到影响,因为只有一个线程为所有连接提供服务。由于无意识的迭代,您 CPU 的消耗将非常高。但与之前的设计不同,您仍然可以连接到服务器。
NIO
基本上用selectors
.
解决了这个问题
由于我想尝试实现与 Telegram 服务器的基本 TCP 连接(使用 MTProto),我开始阅读有关 Java NIO 类 的内容。但是,我在试图理解 Selector
s 对客户的意义时“卡住了”。
A selector supports key-based, non-blocking, multiplexed I/O. In other words, selectors enable you to perform I/O through multiple channels. (Java - The complete reference)
由于 TCP 消息作为流总是有序的,而且我只会打开一个套接字连接(一个 SocketChannel
),使用 Selector
s 有什么意义?我觉得没有意义吧?
如果我的自我回答是正确的,为什么不直接使用阻塞 I/O?
NIO
基本上是用在服务器端处理大规模的。我将尝试解释典型服务器的工作原理。
服务器有一个请求队列,轮询线程从中消耗连接作为 blocking dequeue
操作(在 Java 中,请求队列的默认长度为 50。这意味着如果您尝试在请求队列已满时启动第 51 个连接,您将得到 ConnectionRefused
异常)。
一个典型的 blocking
实现是这样的:
服务器接受连接并将其置于
requests queue
。轮询
的连接,则轮询线程变为空闲thread
从队列头部消耗连接并将其分派给thread pool
。如果thread-pool
队列未满并继续使用来自queue
.在某些时候,线程池中的所有线程都将变得繁忙,并且轮询线程将在向池提交更多连接时被阻塞(因为线程池队列是
blocking queue
).requests queue
同时开始填充。在某些时候,它会变得完全满,服务器将不再接受任何连接。
此时,我们的服务器无法再扩展了。请注意,池中的“繁忙”线程可能根本不忙,而只是被阻塞 - 比如说在它们所服务的相应套接字的 InputStream
上获取更多数据。
现在考虑这个设计:
轮询线程使用请求队列头部的项目。
它把它放在一个无限列表中。
另一个线程不断迭代此列表并检查套接字上是否发生了任何 activity(读到读、准备好写等)。如果有 activity,则提供
socket
。请注意,这些sockets
在NIO
模式下运行。也就是说,如果没有activity,我们的线程就不会被阻塞。轮询线程同时继续向列表提交连接,因为列表是无界的。它不会在任何地方被阻塞(除非它正在等待请求队列上的新连接)。
在上面的设计中,请注意我们的规模仅受我们系统资源的限制——即 list
持有的连接数。响应时间会受到影响,因为只有一个线程为所有连接提供服务。由于无意识的迭代,您 CPU 的消耗将非常高。但与之前的设计不同,您仍然可以连接到服务器。
NIO
基本上用selectors
.