Example 4-1. Using select( ) to service multiple channels 使用 select()来为多个通道提供服务
|
|
书中解读如下:
例4-1实现了一个简单的服务器。它创建了ServerSocketChannel和Selector对象,并将通道注册到选择器上。我们不在注册的键中保存服务器socket的引用,因为它永远不会被注销。这个无限循环在最上面先调用了select(),这可能会无限期地阻塞。当选择结束时,就遍历选择键并检查已经就绪的通道。如果一个键指示与它相关的通道已经准备好执行一个accecpt()操作,我们就通过键获取关联的通道,并将它转换为SeverSocketChannel对象。我们都知道这么做是安全的,因为只有ServerSocketChannel支持OP_ACCEPT操作。我们也知道我们的代码只把对一个单一的ServerSocketChannel对象的OP_ACCEPT操作进行了注册。通过对服务器 socket 通道的引用,我们调用了它的accept()方法,来获取刚到达的socket 的句柄。返回的对象的类型是SocketChannel,也是一个可选择的通道类型。这时,与创建一个新线程来从新的连接中读取数据不同,我们只是简单地将 socket 同多注册到选择器上。我们通过传入 OP_READ 标记,告诉选择器我们关心新的socket通道什么时候可以准备好读取数据。如果键指示通道还没有准备好执行 accept(),我们就检查它是否准备好执行read()。任何一个这么指示的socket通道一定是之前ServerSocketChannel创建的SocketChannel 对象之一,并且被注册为只对读操作感兴趣。对于每个有数据需要读取的socket通道,我们调用一个公共的方法来读取并处理这个带有数据的socket。需要注意的是这个公共方法需要准备好以非阻塞的方式处理socket上的不完整的数据。它需要迅速地返回,以其他带有后续输入的通道能够及时地得到处理。例4-1中只是简单地对数据进行响应,将数据写回socket,传回给发送者。在循环的底部,我们通过调用Iterator(迭代器)对象的remove()方法,将键从已选择的键的集合中移除。键可以直接从 selectKeys()返回的Set中移除,但同时需要用Iterator来检查集合,您需要使用迭代器的 remove()方法来避免破坏迭代器内部的状态。
Example 4-2. Servicing channels with a thread pool 使用线程池来为通道提供服务
|
|
书中解读如下:
由于执行选择过程的线程将重新循环并几乎立即再次调用select(),键的interest集合将被修改,并将 interest(感兴趣的操作)从读取就绪(read-rreadiness)状态中移除。这将防止选择器重复地调用 readDataFromSocket( )(因为通道仍然会准备好读取数据,直到工作线程从它那里读取数据)。当工作线程结束为通道提供的服务时,它将再次更新键的ready集合,来将interest重新放到读取就绪集合中。它也会在选择器上显式地调用wakeup()。如果主线程在select()中被阻塞,这将使它继续执行。这个选择循环会再次执行一个轮回(可能什么也没做)并带着被更新的键重新进入select()
个人觉得这个译文没有完全表达原文意思,导致很难理解,摘录原文如下,
Because the thread doing the selection will loop back and call select( ) again almost immediately, the interest set in the key is modified to remove interest in read-readiness. This prevents the selector from repeatedly invoking readDataFromSocket( ) (because the channel will remain ready to read until the worker thread can drain the data from it). When a worker thread has finished servicing the channel, it will again update the key’s interest set to reassert an interest in read-readiness. It also does an explicit wakeup( ) on the selector. If the main thread is blocked in select( ), this causes it to resume. The selection loop will then cycle (possibly doing nothing) and reenter select( ) with the updated key.
个人理解:首先要清楚drainChannel的过程需要一定时间,并且由于选择线程和响应socket的线程分开,选择线程并不会在处理socket这个步骤阻塞而是又进入了下一次选择,这就导致有可能下一次select操作时上一次响应socket的处理还未结束。在不将Read从interest集合移除的情况下:假如drainChannel还未完成,未将对应的Channel从已选择的集合中移除,那么在下一次select操作后这个Channel对应的key还会被selectKeys()返回,接着readDataFromSocket()还会被调用,即又启动了一个新的线程来处理之前处理到中途的socketChannel