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