background image

使用 Java nio 处理慢速网络连接

  对企业级的服务器软件,高性能和可扩展性是基本的要求。除此之外,还应该有应对
各种不同环境的能力。例如,一个好的服务器软件不应该假设所有的客户端都有很快的处
理能力和很好的网络环境。如果一个客户端的运行速度很慢,或者网络速度很慢,这就意
味着整个请求的时间变长。而对于服务器来说,这就意味着这个客户端的请求将占用更长
的时间。这个时间的延迟不是由服务器造成的,因此 CPU 的占用不会增加什么,但是网
络连接的时间会增加,处理线程的占用时间也会增加。这就造成了当前处理线程和其他资
源得不到很快的释放,无法被其他客户端的请求来重用。例如 Tomcat,当存在大量慢速
连接的客户端时,线程资源被这些慢速的连接消耗掉,使得服务器不能响应其他的请求
了。
  NIO 的异步非阻塞的形式,使得很少的线程就能服务于大量的请求。通过 Selector
的注册功能,可以有选择性地返回已经准备好的频道,这样就不需要为每一个请求分配
单独的线程来服务。
  在一些流行的 NIO 的框架中,都能看到对 OP_ACCEPT 和 OP_READ 的处理。很少
有对 OP_WRITE 的处理。我们经常看到的代码就是在请求处理完成后,直接通过下面的
代码将结果返回给客户端:
  1、不对 OP_WRITE 进行处理的样例:
1.while (bb.hasRemaining()) {   
2.     int len = socketChannel.write(bb);   
3.     if (len < 0) { 
4.      throw new EOFException();  
5.    }   
6.  }  
   这样写在大多数的情况下都没有什么问题。但是在客户端的网络环境很糟糕的情况
下,服务器会遭到很沉重的打击。
  因为如果客户端的网络或者是中间交换机的问题,使得网络传输的效率很低,这时
候会出现服务器已经准备好的返回结果无法通过 TCP/IP 层传输到客户端。这时候在执行
上面这段程序的时候就会出现以下情况。
  ① bb.hasRemaining()

一直为 true”,因为服务器的返回结果已经准备好了。

  ② socketChannel.write(bb)的结果一直为 0,因为由于网络原因数据一直传不过
去。
  ③因为是异步非阻塞的方式,socketChannel.write(bb)不会被阻塞,立刻被返回。
  ④在一段时间内,这段代码会被无休止地快速执行着,消耗着大量的 CPU 的资源。
事实上什么具体的任务也没有做,一直到网络允许当前的数据传送出去为止。
  这样的结果显然不是我们想要的。因此,我们对 OP_WRITE 也应该加以处理。在 NIO
中最常用的方法如下。
  2、一般 NIO 框架中对 OP_WRITE 的处理:
1.while (bb.hasRemaining()) {     
2.    int len = socketChannel.write(bb);   
3.    if (len < 0){