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)做了阻塞操作,但是这种情况只
是少数极端的环境下才会发生。大多数的客户端是不会频繁出现这种现象的,因此在同一
时刻被阻塞的线程不会很多。
③利用这个阻塞操作来判断异常中断的客户连接。
④ 经过压力实验证明这种实现的性能是非常好的。