NIO
1、SelectionKey key = channel.register(Selector, ops) :
其中channel可以是ServerSocketChannel或者是SocketChannel。返回的key代表了一个Channel和Selector的绑定,并附上一个操作。
多次register,只要Selector不变,channel不变,则返回的SelectionKey对象不变,变的只是ops。
2、SelectionKey中读和写事件的就绪条件为:
读事件:OP_READ:当有数据可读,当客户端shutDownOutputStream,即服务器端读到流的末尾(-1),再或者是客户端关闭了连接,则都会见读事件加入到 通道 的就绪事件中去。
写事件:OP_WRITE:当可以写数据,或者当客户端关闭了连接,都会将 写事件 加入到 通道 的就绪事件中去。
上面提到的客户端关闭连接,会触发对应通道上 的读和写事件为就绪,但是只要服务器端执行 通道读 或者 通道写 都会出现异常。
因此建议在客户端不要去关闭socket通道,而只由服务器端去关闭连接。
3、可以将读和写分开注册到不同的Selector中去。
如果是短连接,则可以注册在一个Selector中。
流程: serverChannel.register(16)-->doaccept-->clientChannel.register(1)-->doread-->clientChannel.register(4)-->dowrite-->clientChannel.close()或者key.cancel;
即先在服务端的接受通道ServerSocketChannel中注册接受事件(OP_ACCEPT=16),
然后客户端请求连接过来,通过ServerSocketChannel获取得到Socket,然后accept,获取到SocketChannel clientChannel,即获取到服务端到客户单的连接通道。
然后再clientChannel上注册读事件(OP_READ=1)
然后执行读逻辑。
读完后,将clientChannel上注册写事件,注意这里返回的key没有变更,只是感兴趣的操作变更了。
然后执行写逻辑。
写完后,必须要关闭通道,或者key.cancel。
短连接用NIO实现的优势在于可以通过有限几个线程,对上千上万个客户端请求提供服务。
如果是要用NIO来实现长连接(这是一种假的长连接,并不是一个线程对应一个请求去提供服务。):
可以通过顺序来控制,先读再写,即接收连接后,先注册读事件,执行读,读完后,注册写,写完后,再注册读,如此重复之。
这种方式必须确保读的时候,一定要读到数据,否则等待读到数据。并且客户端传输过来的数据必须每次格式都一样,即提供的服务是一中格式(如HTTP)的。
或者:可以通过将通道注册在不同的Selector中来实现,每个Selector一个线程。(这也是HBase的做法),具体实现可以参看后面hbase分析。
如果不将注册的key cancel掉,则它会一直被认为是就绪key(即被Seletor.select()选出来)的问题,
这个理论是有前提的:
1、关闭了写通道,服务器端读到了末尾(-1),此时读事件会一直就绪,即一直读到 -1,需要服务器端读到 -1 时做处理。
2、客户端关闭了 socket ,此时通道上 读 和 写 事件都会认为是 就绪的,这里也需要服务端处理。
3、只要在通道上的SelectionKey添加了对写事件感兴趣,在服务器端未关闭通道或者未将写事件去除掉之前,此通道都会被认为是可以写的。
现在将 读 写 事件的就绪条件总结如下,以此更正:
SelectionKey中读和写事件的就绪条件为:
读事件:OP_READ:当有数据可读,当客户端socket.shutdownOutput() ,即服务器端读到流的末尾(-1),再或者是客户端关闭了连接,则都会将 读事件 加入到 通道 的就绪事件中去。
写事件:OP_WRITE:当可以写数据,或者当客户端关闭了连接,都会将 写事件 加入到 通道 的就绪事件中去。
上面提到的客户端关闭连接,会触发对应通道上 的读和写事件为就绪,但是只要服务器端执行 通道读 或者 通道写 都会出现异常。
因此建议在客户端不要去关闭socket通道,而只由服务器端去关闭连接。