没有合适的资源?快使用搜索试试~ 我知道了~
首页用Java实现非阻塞通信
用Java实现非阻塞通信 java.nio包提供了支持非阻塞通信的类,主要包括: ● ServerSocketChannel:ServerSocket的替代类,支持阻塞通信与非阻塞通信。 ● SocketChannel:Socket的替代类,支持阻塞通信与非阻塞通信。 ● Selector:为ServerSocketChannel监控接收连接就绪事件,为SocketChannel监控连接就绪、读就绪和写就绪事件。 ● SelectionKey:代表ServerSocketChannel以及SocketChannel向Selector注册事件的句柄。当一个SelectionKey对象位于Selector对象的 selected-keys集合中,就表示与这个SelectionKey对象相关的事件发生了。
资源详情
资源评论
资源推荐

用Java实现非阻塞通信 - lowZoom - CSDN博客
用Java实现非阻塞通信
收藏
用ServerSocket和Socket来编写服务器程序和客户程序,是Java网络编程的最基本的方式。这些服务器程序或客户程序在运行过程中常常会阻
塞。例如当一个线程执行ServerSocket的accept()方法时,假如没有客户连接,该线程就会一直等到有了客户连接才从accept()方法返回。再例
如当线程执行Socket的read()方法时,如果输入流中没有数据,该线程就会一直等到读入了足够的数据才从read()方法返回。
假如服务器程序需要同时与多个客户通信,就必须分配多个工作线程,让它们分别负责与一个客户通信,当然每个工作线程都有可能经常处
于长时间的阻塞状态。
从JDK1.4版本开始,引入了非阻塞的通信机制。服务器程序接收客户连接、客户程序建立与服务器的连接,以及服务器程序和客户程序收发
数据的操作都可以按非阻塞的方式进行。服务器程序只需要创建一个线程,就能完成同时与多个客户通信的任务。
非阻塞的通信机制主要由java.nio包(新I/O包)中的类实现,主要的类包括ServerSocketChannel、SocketChannel、Selector、SelectionKey和
ByteBuffer等。
一、线程阻塞
在生活中,最常见的阻塞现象是公路上汽车的堵塞。汽车在公路上快速运行,如果前方交通受阻,就只好停下来等待,等到公路顺畅,才能
恢复运行。
线程在运行中也会因为某些原因而阻塞。所有处于阻塞状态的线程的共同特征是:放弃CPU,暂停运行,只有等到导致阻塞的原因消除,才
能恢复运行;或者被其他线程中断,该线程会退出阻塞状态,并且抛出InterruptedException。
1.线程阻塞的原因
导致线程阻塞的原因主要有以下方面:
● 线程执行了Thread.sleep(int n)方法,线程放弃CPU,睡眠n毫秒,然后恢复运行。
● 线程要执行一段同步代码,由于无法获得相关的同步锁,只好进入阻塞状态,等到获得了同步锁,才能恢复运行。
● 线程执行了一个对象的wait()方法,进入阻塞状态,只有等到其他线程执行了该对象的notify()或notifyAll()方法,才可能将其唤醒。
● 线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。例如当线程执行System.in.read()方法时,如果用户没
有向控制台输入数据,则该线程会一直等读到了用户的输入数据才从read()方法返回。
进行远程通信时,在客户程序中,线程在以下情况可能进入阻塞状态:
● 请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法时,会进入阻塞状态,直到连接
成功,此线程才从Socket的构造方法或connect()方法返回。
● 线程从Socket的输入流读入数据时,如果没有足够的数据,就会进入阻塞状态,直到读到了足够的数据,或者到达输入流的末尾,或
者出现了异常,才从输入流的read()方法返回或异常中断。输入流中有多少数据才算足够呢?这要看线程执行的read()方法的类型:
1.
1.
int read():只要输入流中有一个字节,就算足够。
2.
int read(byte[] buff):只要输入流中的字节数目与参数buff数组的长度相同就算足够。
3.
String readLine():只要输入流中有一行字符串,就算足够。值得注意的是InputStream类并没有readLine()方法,在过滤流
BufferedReader类中才有此方法。
● 线程向Socket的输出流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write()方法返回
或异常中断。
● 当调用Socket的setSoLinger()方法设置了关闭Socket的延迟时间,那么当线程执行Socket的close()方法时,会进入阻塞状态,直到底层
Socket发送完所有剩余数据,或者超过了setSoLinger()方法设置的延迟时间,才从close()方法返回。
http://blog.csdn.net/lowZoom/archive/2010/05/30/5634697.aspx(第 1/23 页)2011-3-9 15:22:14

用Java实现非阻塞通信 - lowZoom - CSDN博客
在服务器程序中,线程在以下情况可能会进入阻塞状态:
● 线程执行ServerSocket的accept()方法,等待客户的连接,直到接收到了客户连接,才从accept()方法返回。
● 线程从Socket的输入流读入数据时, 如果输入流没有足够的数据,就会进入阻塞状态。
● 线程向Socket的输出流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write()方法返回
或异常中断。
由此可见,无论是在服务器程序还是客户程序中,当通过Socket的输入流和输出流来读写数据时,都可能进入阻塞状态。这种可能出现阻塞
的输入和输出操作被称为阻塞I/O。与此对照,如果执行输入和输出操作时,不会发生阻塞,则称为非阻塞I/O。
2.服务器程序用多线程处理阻塞通信的局限
图1显示了服务器程序用多线程来同时处理多个客户连接的工作流程。主线程负责接收客户的连接。在线程池中有若干工作线程,它们负责
处理具体的客户连接。每当主线程接收到一个客户连接,主线程就会把与这个客户交互的任务交一个空闲的工作线程去完成,主线程继续负
责接收下一个客户连接。
图1 服务器程序用多线程处理阻塞通信
在图1中,用粗体框标识的步骤为可能引起阻塞的步骤。可以看出,当主线程接收客户连接,以及工作线程执行I/O操作时,都有可能进入阻
塞状态。
服务器程序用多线程来处理阻塞I/O,尽管能满足同时响应多个客户请求的需求,但是有以下局限:
(1)Java虚拟机会为每个线程分配独立的堆栈空间,工作线程数目越多,系统开销就越大,而且增加了Java虚拟机调度线程的负担,增加了
http://blog.csdn.net/lowZoom/archive/2010/05/30/5634697.aspx(第 2/23 页)2011-3-9 15:22:14

用Java实现非阻塞通信 - lowZoom - CSDN博客
线程之间同步的复杂性,提高了线程死锁的可能性。
(2)工作线程的许多时间都浪费在阻塞I/O操作上,Java虚拟机需要频繁地转让CPU的使用权,使进入阻塞状态的线程放弃CPU,再把CPU
分配给处于可运行状态的线程。
由此可见,工作线程并不是越多越好。如图2所示,保持适量的工作线程,会提高服务器的并发性能,但是当工作线程的数目到达某个极
限,超出了系统的负荷时,反而会降低并发性能,使得多数客户无法快速得服务器的响应。
并发性能
图2线程数目与并发技能的关系
3.非阻塞通信的基本思想
假如同事要做两件事:烧开水和烧粥。烧开水的步骤如下:
锅里放水,打开煤气炉;
等待水烧开; //阻塞
关闭煤气炉,把开水灌到水壶里;
烧烧粥的步骤如下:
锅里放水和米,打开煤气炉;
等待粥烧开; //阻塞
调整煤气炉,改为小火;
等待粥烧熟; //阻塞
关闭煤气炉;
为了同时完成两件事,一种方案是同时请两个人分别做其中的一件事,这相当于采用多线程来同时完成多个任务。还有一种方案是让一个人
同时完成两件事,这个人应该善于利用一件事的空闲时间去做另一件事,这个人一刻也不应该闲着:
http://blog.csdn.net/lowZoom/archive/2010/05/30/5634697.aspx(第 3/23 页)2011-3-9 15:22:14

用Java实现非阻塞通信 - lowZoom - CSDN博客
锅里放水,打开煤气炉; //开始烧开水
锅里放水和米,打开煤气炉; //开始烧粥
while(一直等待,直到有水烧开、粥烧开或粥烧熟事件发生){ //阻塞
if(水烧开)
关闭煤气炉,把开水灌到水壶里;
if(粥烧开)
调整煤气炉,改为小火;
if(粥烧熟)
关闭煤气炉;
}
这个人不断监控烧水以及烧粥的状态,如果发生了“水烧开”、“粥烧开”或“粥烧熟”事件,就去处理这些事件,处理完一件事后继续监
控烧水以及烧粥的状态,直到所有的任务都完成。
以上工作方式也可以运用到服务器程序中,服务器程序只需要一个线程就能同时负责接收客户的连接、接收各个客户发送的数据,以及向各
个客户发送响应数据。服务器程序的处理流程如下:
while(一直等待,直到有接收连接就绪事件、读就绪事件或写就绪事件发生){ //阻塞
if(有客户连接)
接收客户的连接; //非阻塞
if(某个Socket的输入流中有可读数据)
从输入流中读数据; //非阻塞
if(某个Socket的输出流可以写数据)
向输出流写数据; //非阻塞
}
以上处理流程采用了轮询的工作方式,当某一种操作就绪,就执行该操作,否则就察看是否还有其他就绪的操作可以执行。线程不会因为某
一个操作还没有就绪,就进入阻塞状态,一直傻傻地在那里等待这个操作就绪。
为了使轮询的工作方式顺利进行,接收客户的连接、从输入流读数据、以及向输出流写数据的操作都应该以非阻塞的方式运行。所谓非阻
塞,就是指当线程执行这些方法时,如果操作还没有就绪,就立即返回,而不会一直等到操作就绪。例如当线程接收客户连接时,如果没有
客户连接,就立即返回;再例如当线程从输入流中读数据时,如果输入流中还没有数据,就立即返回,或者如果输入流还没有足够的数据,
那么就读取现有的数据,然后返回。值得注意的是,以上while循环条件中的操作还是按照阻塞方式进行的,如果未发生任何事件,就会进入
阻塞状态,直到接收连接就绪事件、读就绪事件或写就绪事件中至少有一个事件发生,此时就会执行while循环体中的操作。
http://blog.csdn.net/lowZoom/archive/2010/05/30/5634697.aspx(第 4/23 页)2011-3-9 15:22:14

用Java实现非阻塞通信 - lowZoom - CSDN博客
二、java.nio包中的主要类
java.nio包提供了支持非阻塞通信的类,主要包括:
● ServerSocketChannel:ServerSocket的替代类,支持阻塞通信与非阻塞通信。
● SocketChannel:Socket的替代类,支持阻塞通信与非阻塞通信。
● Selector:为ServerSocketChannel监控接收连接就绪事件,为SocketChannel监控连接就绪、读就绪和写就绪事件。
● SelectionKey:代表ServerSocketChannel以及SocketChannel向Selector注册事件的句柄。当一个SelectionKey对象位于Selector对象的
selected-keys集合中,就表示与这个SelectionKey对象相关的事件发生了。
ServerSocketChannel以及SocketChannel都是SelectableChannel的子类,如图3所示。SelectableChannel类以及其子类都能委托Selector来监控它们可
能发生的一些事件,这种委托过程也称为注册事件过程。
图3 SelectableChannel类及其子类的类框图
ServerSocketChannel向Selector注册接收连接就绪事件的代码如下:
SelectionKey key=serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
SelectionKey类的一些静态常量表示事件类型,ServerSocketChannel只可能发生一种事件:
● SelectionKey.OP_ACCEPT:接收连接就绪事件,表示至少有了一个客户连接,服务器可以接收这个连接。
SocketChannel可能发生以下三种事件:
● SelectionKey.OP_CONNECT:连接就绪事件,表示客户与服务器的连接已经建立成功。
● SelectionKey.OP_READ:读就绪事件,表示输入流中已经有了可读数据,可以执行读操作了。
● SelectionKey.OP_WRITE:写就绪事件,表示已经可以向输出流写数据了。
SocketChannel提供了接收和发送数据的方法:
● read(ByteBuffer buffer):接收数据,把它们存放到参数指定的ByteBuffer中。
● write(ByteBuffer buffer):把参数指定的ByteBuffer中的数据发送出去。
http://blog.csdn.net/lowZoom/archive/2010/05/30/5634697.aspx(第 5/23 页)2011-3-9 15:22:14
剩余22页未读,继续阅读


















安全验证
文档复制为VIP权益,开通VIP直接复制

评论0