background image

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)做了阻塞操作,但是这种情况只

是少数极端的环境下才会发生。大多数的客户端是不会频繁出现这种现象的,因此在同一
时刻被阻塞的线程不会很多。

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

 

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