非阻塞式通信详解

                  

用ServerSocket 和Socket编写服务器和客户端程序的时候,其在运行过程中往往是阻塞的。例如SeverSocket中的accept()方法,假如没有客户连接就一直处于阻塞状态。JDK1.4

后引入非阻塞式通信机制,服务器程序接收客户连接,客户程序建立与服务器的连接以及服务器程序和客户程序收发数据的操作都可以按照非阻塞的方式进行,服务器只需创建一个线程,就能完成同时与多个客户通信的任务。

阻塞线程的特点:

放弃CPU,暂停运行,只有等待阻塞消除才能恢复运行。被其他线程中断,该线程会退出阻塞状态,并且抛出InterruptException。

线程阻塞的原因:

1:执行了sleep()方法,放弃cpu,休眠完成后恢复运行。

2:线程执行了一段同步代码由于无法获得相关的同步锁,只好进入阻塞状态,等到获得后方可执行。

3:线程执行了wait ()方法,进入阻塞状态,只有等待其他线程执行了notify ()和notifyAll ()方法后,才能将其唤醒。

线程执行了I/O操作进行远程通信时,会因为等待相关的资源而进入阻塞状态。

在远程通信中造成阻塞的原因:

1:请求与服务器连接时,或执行connect()方法时,会进入阻塞状态。直到连接成功

2:线程从Socket的输入流读入数据的时候,如果没有足够的数据就会进入阻塞状态

直到读入足够的数据,或者到达输入流的末尾。

3:线程向socket的输出流写入一批数据,可能会进入阻塞状态,等到输出了所有数据,或者出现异常,才从输出流的write()方法中返回异常。

服务器程序用多线程处理阻塞通信的局限:

当主线程接收客户连接以及工作线程执行I/O操作时,尽管能满足多个各户的需求但是仍有局限:

1:java虚拟机会为每个线程分配独立的堆栈空间,工作线程数目越多,系统的开销就越大,增加了JVM调度线程的负担,增加了线程之间同步的复杂性,提高了线程死锁的可能性。

2:工作线层的许多时间都浪费在I/O操作上,JVM需要频繁的切换上下文。增加系统的开销。

工作线程并不是越多越好,保持适量的工作线程,会提高服务器的并发性能。当工作线程达到某个极限,反而会降低并发性能。

非阻塞式通信:

非阻塞式通信采用轮询的工作方式,当某一操作完成时,就会执行该操作,否则就查看是否还有其他就绪的操作可以执行,线程不会因为某一个操作还没有就绪,就进入阻塞状态。

为了使轮询的工作方式顺利进行,接收客户的连接,读数据,写数据,都应该是非阻塞的,就是执行这些操作时,如果操作还没有就绪,就立即返回,而不会一直等待操作就绪。

例如:当线程从输入流中读取数据,如果输入流中没有数据就立即返回或者没有足够的数据那么就读取现有的数据,然后返回。

java.nio包下提供了支持非阻塞通信的类。

ServerSocketChannel :ServerSocket的替代类,支持阻塞,与非阻塞通信

SocketChannel: Socket的替代类,支持阻塞,与非阻塞通信。

Selector:为ServerSocketChannel监控接收连接就绪的事件,为SocketChannel监控连接就绪,读,写就绪。

SelectionKey :代表ServerSocketChannel,SocketChannel 向Selector注册事件的句柄,

当一个SelectionKey对象位于Selector对象的Selected-keys集合中时,就表示这个SelectionKey对象的相关事件的发生。

 ServerSocketChannel与SocketChannel都是SelectableChannel的子类,SelectableChannel类及其子类都委托Selector来监控一些可能发生的事件,这种委托过程也称为注册事件过程。

ServerSocketChannel向Selector注册接收连接就绪事件的代码:

SelectionKey key = ServerSocketChannel.register (selector,SelectionKey.OP_ACCEPY);

SelectionKey.OP_ACCEPY:接收连接就绪事件,表示至少有一个客户连接,服务器可以接收这个连接。

SocketChannel可能发生如下三种事件:

SelectionKey.OP_CONNECT:连接就绪,客户与服务器连接成功

SelectionKey.OP_READ :读就绪,输入流中已有可读数据,可以执行读操作了

SelectionKey.OP_WRITE : 写就绪,表示可以向输入流写数据了。

SocketChannel提供的接收发送方法:

read (ByteBuffer ,buffer):接收数据将其存放到参数指定的ByteBuffer

write (ByteBuffer ,buffer):把指定的ByteBuffer中的数据发送出去。

缓冲区Buffer:(数据输入输出往往比较耗时)

作用:1:减少实际的物理读写次数。

      2:缓冲区在创建的时候被非配内存,这块内存一直被重用,这可以减少动态分配和回收内存的次数。

所有的缓冲区都有以下属性:

容量:存储数据的空间大小。

极限:表示缓冲区当前的终点,不能对缓冲区中超过极限的区域进行读写操作,极限可以修改,这有利于缓冲区的重用。

位置:表示缓冲区中下一个读写单元的位置,每次读写缓冲区的数据时都会改变,为下一条数据做准备,位置是一非负数不应该大于极限。

java.nio.Buffer 类是一个抽象类:提供了来两个获得ByteBuffer实例的静态工厂方法:

allocate(int capacity):返回一个ByteBuffer对象,参数指定缓冲区的容量。

directAllocate (int capacity) ,该方法的缓冲区称为直接缓冲区,与当前操作系统能够更好的耦合,因此进一步提高I/o的操作速度。但是系统的开销很大,因此只有在缓冲区较大或者经常重用时才用。

字符编码:CharSet

java.nio.CharSet类的每一个实例代表特定的字符编码类型

把字节序列转换为字符串的过程称为解码,把字符转换为字节序列的过程称为编码

CharSet类提供的编码与解码方式:

ByteBuffer encodes (String str):对参数指定的字符串进行编码,并把的到的字节序列放在一个ByteBuffer对象中。

CharBuffer decode (ByteBuffer  bb):把参数bb指定的ByteBuffer 中的字节序列进行解码,并把得到的字符序列存放在CharBuffer中。

Charset 的forName(String , encode) :方法返回一个CharSet对象,参数代表指定的编码类型。

通道Channel

通道Channel用来连接缓冲区与数据源或数据汇(数据目的地)。

通道在创建时被打开,一旦关闭就不能重新打开。

SelectableChannel :是一种支持阻塞与非阻塞的i/o通道。在非阻塞模式下,读写数据不会阻塞,SelectableChannel 可以向Selector注册读就绪和写就绪事件。

Selector类:

只要ServerSocketChannel即SocketChannel向Selector()注册了特定的事件,Selector就会监控这些数据是否发生,SelectableChannel的register方法负责注册事件,

SelectionKey :

ServerSocketChannel即SocketChannel向Selector()注册了特定的事件,register()方法会创建一个SelectionKey对象,这个SelectionKey对象用来跟踪注册事件的句柄,

在其有效期内会一直监控与SelectionKey对象相关的事件,如果有事件发生,就会把SelectionKey对象加入到selected-key集合中。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值