17.3.1 如何处理慢速的连接
对企业级的服务器软件,高性能和可扩展性是基本的要求。除此之外,还应该有应对各种不同环境的能力。例如,一个好的服务器软件不应该假设所有的客户端都有很快的处理能力和很好的网络环境。如果一个客户端的运行速度很慢,或者网络速度很慢,这就意味着整个请求的时间变长。而对于服务器来说,这就意味着这个客户端的请求将占用更长的时间。这个时间的延迟不是由服务器造成的,因此CPU的占用不会增加什么,但是网络连接的时间会增加,处理线程的占用时间也会增加。这就造成了当前处理线程和其他资源得不到很快的释放,无法被其他客户端的请求来重用。例如Tomcat,当存在大量慢速连接的客户端时,线程资源被这些慢速的连接消耗掉,使得服务器不能响应其他的请求了。
NIO的异步非阻塞的形式,使得很少的线程就能服务于大量的请求。通过Selector的注册功能,可以有选择性地返回已经准备好的频道,这样就不需要为每一个请求分配单独的线程来服务。
在一些流行的NIO的框架中,都能看到对OP_ACCEPT和OP_READ的处理。很少有对OP_WRITE的处理。我们经常看到的代码就是在请求处理完成后,直接通过下面的代码将结果返回给客户端:
【例17.7】不对OP_WRITE进行处理的样例:
while (bb.hasRemaining()) {
int len = socketChannel.write(bb);
if (len < 0) {
throw new EOFException();
}
}
这样写在大多数的情况下都没有什么问题。但是在客户端的网络环境很糟糕的情况下,服务器会遭到很沉重的打击。
因为如果客户端的网络或者是中间交换机的问题,使得网络传输的效率很低,这时候会出现服务器已经准备好的返回结果无法通过TCP/IP层传输到客户端。这时候在执行上面这段程序的时候就会出现以下情况。
(1) bb.hasRemaining()一直为“true”,因为服务器的返回结果已经准备好了。
(2) socketChannel.write(bb)的结果一直为0,因为由于网络原因数据一直传不过去。
(3) 因为是异步非阻塞的方式,socketChannel.write(bb)不会被阻塞,立刻被返回。
(4) 在一段时间内,这段代码会被无休止地快速执行着,消耗着大量的CPU的资源。事实上什么具体的任务也没有做,一直到网络允许当前的数据传送出去为止。
这样的结果显然不是我们想要的。因此,我们对OP_WRITE也应该加以处理。在NIO中最常用的方法如下。
【例17.8】一般NIO框架中对OP_WRITE的处理:
while (bb.hasRemaining()) {
int len = socketChannel.write(bb);
if (len < 0){
throw new EOFException();
}
if (len == 0) {
selectionKey.interestOps(
selectionKey.interestOps() | SelectionKey.OP_WRITE);
mainSelector.wakeup();
break;
}
}
上面的程序在网络不好的时候,将此频道的OP_WRITE操作注册到Selector上,这样,当网络恢复,频道可以继续将结果数据返回客户端的时候,Selector会通过SelectionKey来通知应用程序,再去执行写的操作。这样就能节约大量的CPU资源,使得服务器能适应各种恶劣的网络环境。
可是,Grizzly中对OP_WRITE的处理并不是这样的。我们先看看Grizzly的源码吧。在Grizzly中,对请求结果的返回是在ProcessTask中处理的,经过SocketChannelOutputBuffer的类,最终通过OutputWriter类来完成返回结果的动作。在OutputWriter中处理OP_WRITE的代码如下:
【例17.9】Grizzly中对OP_WRITE的处理:
public static long flushChannel(SocketChannel socketChannel,
ByteBuffer bb, long writeTimeout) throws IOException
{
SelectionKey key = null;
Selector writeSelector = null;
int attempts = 0;
int bytesProduced = 0;
try {
while (bb.hasRemaining()) {
int len = socketChannel.write(bb);
attempts++;
if (len < 0){
throw new EOFException();
}
bytesProduced += len;
if (len == 0) {
if (writeSelector == null){
writeSelector = SelectorFactory.getSelector();
if (writeSelector == null){
// Continue using the main one
continue;
}
}
key = socketChannel.register(writeSelector, key.OP_WRITE);
if (writeSelector.select(writeTimeout) == 0) {
if (attempts > 2)
throw new IOException("Client disconnected");
} else {
attempts--;
}
} else {
attempts = 0;
}
}
} finally {
if (key != null) {
key.cancel();
key = null;
}
if (writeSelector != null) {
// Cancel the key.
writeSelector.selectNow();
SelectorFactory.returnSelector(writeSelector);
}
}
return bytesProduced;
}
上面的程序例17.9与例17.8的区别之处在于:当发现由于网络情况而导致的发送数据受阻(len==0)时,例17.8的处理是将当前的频道注册到当前的Selector中;而在例17.9中,程序从SelectorFactory中获得了一个临时的Selector。在获得这个临时的Selector之后,程序做了一个阻塞的操作:writeSelector.select(writeTimeout)。这个阻塞操作会在一定时间内(writeTimeout)等待这个频道的发送状态。如果等待时间过长,便认为当前的客户端的连接异常中断了。
这种实现方式颇受争议。有很多开发者置疑Grizzly的作者为什么不使用例17.8的模式。另外在实际处理中,Grizzly的处理方式事实上放弃了NIO中的非阻塞的优势,使用writeSelector.select(writeTimeout)做了个阻塞操作。虽然CPU的资源没有浪费,可是线程资源在阻塞的时间内,被这个请求所占有,不能释放给其他请求来使用。
Grizzly的作者对此的回应如下。
(1) 使用临时的Selector的目的是减少线程间的切换。当前的Selector一般用来处理OP_ACCEPT,和OP_READ的操作。使用临时的Selector可减轻主Selector的负担;而在注册的时候则需要进行线程切换,会引起不必要的系统调用。这种方式避免了线程之间的频繁切换,有利于系统的性能提高。
(2) 虽然writeSelector.select(writeTimeout)做了阻塞操作,但是这种情况只是少数极端的环境下才会发生。大多数的客户端是不会频繁出现这种现象的,因此在同一时刻被阻塞的线程不会很多。
(3) 利用这个阻塞操作来判断异常中断的客户连接。
(4) 经过压力实验证明这种实现的性能是非常好的。
相关推荐
标题“nio.rar_NIO_NIO-socket_java nio_java 实例_java.nio”表明这个压缩包包含了一个关于Java NIO的实例,特别是关于NIO套接字(Socket)的编程示例。NIO套接字是Java NIO库中用于网络通信的关键组件,它们允许...
总结来说,Java NIO是一种高效的I/O模型,它通过Channel、Buffer和Selector的组合,实现了非阻塞的数据读写和多路复用,从而提升了系统在处理高并发连接时的性能。Mina作为基于NIO的框架,为开发者提供了便利的工具...
`java_nio_chm`指的是包含Java NIO相关内容的CHM(Compiled Help Manual)文件,这是一种Windows下的帮助文档格式,通常包含一系列的章节和索引,方便用户查阅。在这个文档中,你可以找到关于Java NIO类库的详细说明...
Java NIO的设计理念是提高并发性能,适用于需要处理大量连接的服务器,如网络服务器。它简化了多路复用的实现,减少了线程上下文切换的开销。然而,对于简单的I/O操作,传统的IO模型可能更易于理解和使用。理解并...
标题中的“nioserver.zip_NIO_event driven java_java nio_java nioserv_nioser”表明这是一个关于Java NIO的服务器实现,且是基于事件驱动模式的。事件驱动编程是一种设计模式,它允许程序对特定事件做出响应,而...
Java NIO(New Input/Output)是Java标准库中提供的一种I/O模型,与传统的BIO( Blocking I/O)相比,NIO具有更好的性能和更高的灵活性。NIO的核心组件包括通道(Channel)、缓冲区(Buffer)和选择器(Selector)。...
Java NIO中的Selector就是多路复用器,它可以监视多个通道,当某个通道准备就绪时,选择器会通知我们,这样就可以有效地处理多个并发连接。 6. **管道(Pipe)** 管道是两个线程之间的单向数据通道。通过Pipe的...
标题中提到的"java_Nio_server_and_j2me_client.rar"是一个包含Java NIO服务器和J2ME客户端的应用示例。J2ME(Java Micro Edition)是Java平台的一个子集,主要用于移动设备和嵌入式设备。在这个项目中,NIO服务器...
标题中的“Java socketA_java nio_java socket a”可能是指使用Java NIO实现的Socket通信,这里的"A"可能是表示"Advanced"或"Alternative",意味着比传统的阻塞I/O模型更为高级或替代方案。 在Java Socket API中,...
TCP(传输控制协议)是一种面向连接、可靠的、基于字节流的传输层通信协议,而NIO(非阻塞I/O)则是Java提供的一种高效处理I/O操作的方式。在这个“tcp.zip”压缩包中,我们可能找到了关于使用Java实现TCP服务器和...
Java NIO,全称为New Input/Output,是Java在1.4版本引入的一个新特性,是对传统的I/O模型的一种改进。传统的Java I/O基于字节流和字符流,而NIO则提供了通道(Channels)和缓冲区(Buffers)的概念,以及非阻塞I/O...
本项目“Large-File-Processing-master_javanio_java大文件处理_”显然专注于通过Java NIO实现大文件处理,下面我们将详细探讨相关的知识点。 1. **Java NIO基础**:NIO的核心组件包括通道(Channels)、缓冲区...
在标题中提到的“java-nio.rar_java nio_nio 对象实例化”,我们可以理解为这个压缩包中包含了关于Java NIO对象实例化的具体示例或教程。下面我们将详细讨论NIO中的核心对象及其实例化方法。 1. **通道(Channel)*...
在这个"NIO.rar_NIO_java nio"的源码中,我们可以看到以下几个关键知识点: 1. **通道(Channel)**:Channel是数据传输的载体,它可以是文件、套接字、网络等。Java NIO提供了多种类型的通道,如FileChannel用于文件...
在给定的"nio.rar_Different_NIO_java nio package"压缩包中,可能包含14个不同的NIO使用示例,涵盖了上述知识点的不同方面。这些例子可以帮助开发者深入理解NIO的工作原理,掌握如何在实际项目中应用NIO技术,提高...
在本项目"chacha.rar_NIO soclet java_java nio"中,开发者使用Java NIO实现了一个用户动态口令的认证系统,并结合了MongoDB数据库来存储和管理数据。 1. **Java NIO基础**: - **通道(Channels)**:NIO的核心...
1. **网络编程**:Java NIO在服务器端开发中,特别是高并发的TCP连接处理,如聊天服务器、游戏服务器等。 2. **大文件处理**:处理大文件时,NIO的直接内存访问和非阻塞特性可以提高性能。 3. **高性能数据交换**...
Java NIO(Non-Blocking Input/Output)是Java在1.4版本引入的新I/O模型,是对传统BIO(Blocking I/O)模型的一种扩展和优化。NIO的主要特点是其非阻塞特性,允许程序在等待数据准备就绪时进行其他处理,从而提高了...
根据提供的文件信息,本文将围绕Java NIO(New Input/Output)的相关知识点进行详细解析,同时探讨相关的视频学习资源。 ### Java NIO简介 Java NIO(New Input/Output),即新输入输出,是Java 1.4版本引入的一个...