我试图理解SocketChannels和NIO。我知道如何使用常规套接字以及如何创建一个简单的每个客户端线程服务器(使用常规阻塞套接字)。
所以我的问题:
A selectable channel for stream-oriented connecting sockets.
。这是什么意思?我也读过this documentation,但不知怎的,我没有得到它......
答案 0 :(得分:51)
Socket
是阻塞输入/输出设备。它使正在使用它的Thread
阻塞读取,如果底层缓冲区已满,则可能还会阻止写入。因此,如果您的服务器有一堆打开的Socket
,则必须创建一堆不同的线程。
SocketChannel
是一种从套接字读取的非阻塞方式,因此您可以让一个线程同时与一堆打开的连接进行通信。这可以通过向SocketChannel
添加一堆Selector
,然后在选择器的select()
方法上循环,该方法可以通知您是否已接受套接字,已接收数据或已关闭套接字。这允许您在一个线程中与多个客户端通信,而不会有多个线程和同步的开销。
Buffer
是NIO的另一个功能,它允许您从读取和写入访问基础数据,以避免将数据复制到新数组中的开销。
答案 1 :(得分:16)
到目前为止NIO
已经很久了,很少有人记得1.4之前的Java是什么样的,这是你需要知道的,以便理解NIO
的“为什么”。
简而言之,在Java 1.3之前,所有I / O都是阻塞类型。更糟糕的是,没有类似的select()
系统调用多路复用I / O.因此,用Java实现的服务器别无选择,只能采用“每个连接一个线程”的服务策略。
在Java 1.4中引入的NIO的基本要点是使Java中可用的传统UNIX样式多路复用非阻塞I / O的功能。如果您了解如何使用select()
或poll()
编程来检测一组文件描述符(通常是套接字)上的I / O就绪状态,那么您将在{{1}中找到所需的服务您将NIO
用于非阻塞I / O端点,SocketChannel
用于fdsets或pollfd数组。现在可以使用具有线程池的服务器,或者每个处理多个连接的线程的服务器。这是“额外的”。
Selector
是非阻塞套接字I / O所需的字节数组,特别是在输出/写入端。如果只能立即写入缓冲区的一部分,使用阻塞I / O,您的线程将直接阻塞,直到可以写入整体。使用非阻塞I / O,您的线程会获得写入多少的返回值,由您来处理下一轮的剩余部分。 Buffer
通过显式实现生产者/消费者模式来填充和排空来处理这些机械细节,可以理解你的线程和JVM的内核将不同步。
答案 2 :(得分:5)
即使您使用SocketChannels
,也必须使用线程池来处理channels
。
考虑到scenairo,你只使用一个线程负责轮询select()
并处理从SocketChannels
中选择的Selectors
,如果一个频道需要1秒进行处理,那么队列中有10个通道,这意味着您必须在下次轮询之前等待10秒,这是不可容忍的。所以应该有一个用于频道处理的线程池。
从这个意义上讲,我没有看到每个客户端的线程阻塞套接字模式存在巨大差异。主要区别在于NIO
模式,任务较小,更像是每个任务的线程,任务可以是读,写,流程等等,更多细节,你可以看一下 Netty 的NioServerSocketChannelFactory
实现,它使用一个Boss线程接受连接,并将任务分派给一个工作线程池进行处理
如果你真的很喜欢一个线程,那么底线是至少你已经汇集了I / O线程,因为I / O操作通常比指令处理周期慢一些,你不会想要宝贵的一个线程被I / O阻塞,这正是 NodeJS 所做的,使用一个线程接受连接,并且所有I / O都是异步的并且由后端I / O线程池并行处理
是每个客户端的旧式线程死了吗? 我不这么认为,NIO编程很复杂,多线程并不自然是邪恶的,请记住,现代操作系统和CPU在多任务处理中变得越来越好,因此随着时间的推移,多线程的开销变得越来越小。