技术开发 频道

Java nio如何处理慢速网络连接

  3、Grizzly中对OP_WRITE的处理

1.public static long flushChannel(SocketChannel socketChannel,  
2.        ByteBuffer bb, long writeTimeout) throws IOException  
3.{  
4.    SelectionKey key = null;  
5.    Selector writeSelector = null;  
6.    int attempts = 0;  
7.    int bytesProduced = 0;  
8.    try {  
9.        while (bb.hasRemaining()) {  
10.            int len = socketChannel.write(bb);  
11.            attempts++;  
12.            if (len < 0){  
13.                throw new EOFException();  
14.            }  
15.            bytesProduced += len;  
16.            if (len == 0) {  
17.                if (writeSelector == null){  
18.                    writeSelector = SelectorFactory.getSelector();  
19.                    if (writeSelector == null){  
20.                        // Continue using the main one  
21.                        continue;  
22.                    }  
23.                }  
24.                key = socketChannel.register(writeSelector, key.OP_WRITE);  
25.                if (writeSelector.select(writeTimeout) == 0) {  
26.                    if (attempts > 2)  
27.                        throw new IOException("Client disconnected");  
28.                } else {  
29.                    attempts--;  
30.                }  
31.            } else {  
32.                attempts = 0;  
33.            }  
34.        }  
35.    } finally {  
36.        if (key != null) {  
37.            key.cancel();  
38.            key = null;  
39.        }  
40.        if (writeSelector != null) {  
41.            // Cancel the key.  
42.            writeSelector.selectNow();  
43.            SelectorFactory.returnSelector(writeSelector);  
44.        }  
45.    }  
46.    return bytesProduced;  
47.}  

  上面的程序例3与例2的区别之处在于:当发现由于网络情况而导致的发送数据受阻(len==0)时,例2的处理是将当前的频道注册到当前的Selector中;而在例3中,程序从SelectorFactory中获得了一个临时的Selector。在获得这个临时的Selector之后,程序做了一个阻塞的操作:writeSelector.select(writeTimeout)。这个阻塞操作会在一定时间内(writeTimeout)等待这个频道的发送状态。如果等待时间过长,便认为当前的客户端的连接异常中断了。

  这种实现方式颇受争议。有很多开发者置疑Grizzly的作者为什么不使用例2的模式。另外在实际处理中,Grizzly的处理方式事实上放弃了NIO中的非阻塞的优势,使用writeSelector.select(writeTimeout)做了个阻塞操作。虽然CPU的资源没有浪费,可是线程资源在阻塞的时间内,被这个请求所占有,不能释放给其他请求来使用。

  Grizzly的作者对此的回应如下。

  ① 使用临时的Selector的目的是减少线程间的切换。当前的Selector一般用来处理OP_ACCEPT,和OP_READ的操作。使用临时的Selector可减轻主Selector的负担;而在注册的时候则需要进行线程切换,会引起不必要的系统调用。这种方式避免了线程之间的频繁切换,有利于系统的性能提高。

  ②虽然writeSelector.select(writeTimeout)做了阻塞操作,但是这种情况只是少数极端的环境下才会发生。大多数的客户端是不会频繁出现这种现象的,因此在同一时刻被阻塞的线程不会很多。

  ③利用这个阻塞操作来判断异常中断的客户连接。

  ④ 经过压力实验证明这种实现的性能是非常好的。

0
相关文章