08 非阻塞通信的API基本介绍

1 缓冲区Buffer

java.nio包公开了Buffer API,使得Java程序可以直接控制和运用缓冲区。

数据输入和输出往往是比较耗时的操作。缓冲区从两个方面提高I/O操作的效率:

  • 减少实际的物理读写次数。
  • 缓冲区在创建时被分配内存,这块内存区域一直被重用,这可以减少动态分配和回收内存区域的次数

java.nio.Buffer类是一个抽象类,不能被实例化。共有8个具体的缓冲区类:
在这里插入图片描述

1.1 缓冲区的几个属性

  • 容量(capacity):表示该缓冲区可以保存多少数据。
  • 极限(limit):表示缓冲区的当前终点,不能对缓冲区中超过极限的区域进行读写操作。极限是可以修改的,这有利于缓冲区的重用。例如,假定容量为100的缓冲区已经填满了数据,接着程序在重用缓冲区时,仅仅将10个新的数据写入缓冲区中从位置0到10的区域,这时可以将极限设为10,这样就不能读取先前的数据了。极限是一个非负整数,不应该大于容量
  • 位置(position):表示缓冲区中下一个读写单元的位置每次读写缓冲区的数据时,都会改变该值,为下一次读写数据作准备位置是一个非负整数,不应该大于极限

以上三个属性的关系为:容量>=极限>=位置>=0

缓冲区提供了用于改变以上三个属性的方法:

  • clear():把极限设为容量,再把位置设为0。
  • flip():把极限设为位置,再把位置设为0。
  • rewind():不改变极限,把位置设为0。
  • remaining()方法: 返回缓冲区的剩余容量,取值等于极限-位置。
  • compact()方法:删除缓冲区内从0到当前位置position的内容,然后把从当前位置position到极限limit的内容拷贝到0到limit-position的区域内,当前位置position和极限limit的取值也做相应的变化,参见下图。
    在这里插入图片描述

1.2 缓冲区基本方法

所有具体缓冲区类都提供了读写缓冲区的方法:

  • get():相对读。从缓冲区的当前位置读取一个单元的数据,读完后把位置加1。
  • get(int index):绝对读。从参数index指定的位置读取一个单元的数据。
  • put(数据):相对写。向缓冲区的当前位置写入一个单元的数据,写完后把位置加1。
  • put(int index, 数据):绝对写。向参数index指定的位置写入一个单元的数据。

1.3 ByteBuffer

基本的缓冲区是ByteBuffer,它存放的数据单元是字节。ByteBuffer类并没有提供公开的构造方法,但是提供了两个获得ByteBuffer实例的静态工厂方法:

  • allocate(int capacity):返回一个ByteBuffer对象,参数capacity指定缓冲区的容量。
  • directAllocate(int capacity): 返回一个ByteBuffer对象,参数capacity指定缓冲区的容量。该方法返回的缓冲区称为直接缓冲区,它与当前操作系统能够更好的耦合,因此能进一步提高I/O操作的速度。但是分配直接缓冲区的系统开销很大,因此只有在缓冲区较大并且长期存在,或者需要经常重用时,才使用这种缓冲区

其他的缓冲区实现也都有这样返回自身实例的静态方法

MappedByteBuffer是ByteBuffer的的子类,能够把缓冲区和文件的某个区域直接映射。

ByteBuffer还提供了用于获得缓冲区视图的方法:

  • asShortBuffer
  • asCharBuffer
  • asIntBuffer
  • asFloatBuffer

2 字符编码

Charset类提供了编码与解码的方法:

  • ByteBuffer encode(String str):对参数str指定的字符串进行编码,把得到的字节序列存放在一个ByteBuffer对象中,并将其返回。
  • ByteBuffer encode(CharBuffer cb):对参数cb指定的字符缓冲区中的字符进行编码,把得到的字节序列存放在一个ByteBuffer对象中,并将其返回。
  • CharBuffer decode(ByteBuffer bb):把参数bb指定的ByteBuffer中的字节序列进行解码,把得到的字符序列存放在一个CharBuffer对象中,并将其返回。

另外,Charset类的静态forName(String encode)方法返回一个Charset对象,它代表参数encode指定的编码类型。例如以下代码创建了一个代表“GBK”编码的Charset对象:

Charset charset=Charset.forName("GBK");

还有一个方法,返回本地平台默认的字符编码:

 Charset charset = Charset.defaultCharset();

3 通道(Channel)

通道Channel用来连接缓冲区与数据源或数据汇(即数据目的地)。如图所示,数据源的数据经过通道到达缓冲区,缓冲区的数据经过通道到达数据汇。
在这里插入图片描述
Channel的主要类继承关系:
在这里插入图片描述
java.nio.channels.Channel接口只声明了两个方法:

  • close():关闭通道。
  • isOpen():判断通道是否打开。

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

Channel最重要的两个字子接口是ReadableByteChannelWritableByteChannel

  • ReadableByteChannel声明了read(ByteBuffer dst)方法,用于把数据源的数据读入指定的ByteBuffer缓冲区
  • WritableByteChannel声明了write(ByteBuffer src),用于把指定的ByteBuffer缓冲区中的数据写入到数据目的地

在这里插入图片描述

  • ByteChannel接口:集成上面两个接口,同时支持读写操作
  • ScatteringByteChannel接口:扩展了ReadableByteChannel接口,允许分散的读取数据。分散的读取数据指单个读取操作能填充多个缓冲区:
// 形参是个ByteBuffer数组,也就是ScatteringByteChannel可以将读取的数据填充到形参所有的ByteBuffer数组中
 public long read(ByteBuffer[] dsts) throws IOException;
  • GatheringByteChannel接口:扩展了WritableByteChannel接口,允许集中的写入数据。集中的写入数据指的是单个写操作可以能够把多个缓冲区的数据写入到目标中
// 会把形参中ByteBuffer数组所有的ByteBuffer缓冲区数据一次写入
public long write(ByteBuffer[] srcs) throws IOException;
  • FileChannel类:是Channel接口的实现类,代表一个与文件相连的通道。该类实现了ByteChannel、ScatteringByteChannel和GatheringByteChannel接口,支持读操作、写操作、分散读操作和集中写操作。
  • SelectableChannel类:也是一种通道,它不仅支持阻塞的I/O操作,还支持非阻塞的I/O操作。SelectableChannel有两个子类:
    ServerSocketChannel类和SocketChannel类:SocketChannel还实现了ByteChannel接口,具有read(ByteBuffer dst)和write(ByteBuffer src)方法。

4 SelectableChannel类

SelectableChannel是一种支持阻塞I/O和非阻塞I/O的通道。

  • 在非阻塞模式下,读写数据不会阻塞
  • SelectableChannel可以向Selector注册读就绪和写就绪等事件。Selector负责监控这些事件,等到事件发生时,比如发生了读就绪事件,SelectableChannel就可以执行读操作了。

SelectableChannel的主要方法如下:

/**
block为true时,表示SelectableChannel设置为阻塞式,fasle为非阻塞式
默认采用阻塞式
**/
public SelectableChannel configureBlocking(boolean block) throws IOException

/**
注册事件,返回值SelectionKey 可以用来跟踪被注册的事件
**/
public SelectionKey register(Selector sel,int ops)throws ClosedChannelException
/**
注册事件,返回值SelectionKey 可以用来跟踪被注册的事件
attachment参数,表示一个上下文,事件触发的时候,可以从SelectionKey获取到该上下文
**/
public SelectionKey register(Selector sel,int ops,Object attachment)throws ClosedChannelException 

eg:

selectableChannel.register(selector,SelectionKey.OP_READ | SelectionKey.OP_WRITE);

Object attachment = new Object();
selectableChannel.register(selector,SelectionKey.OP_READ | SelectionKey.OP_WRITE,attachment);

可以通过这个方法关联一个attachment

 SelectionKey register = selectableChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
 Object attachment = new Object();
 register.attach(attachment);

5 ServerSocketChannel

  • ServerSocketChannel从SelectableChannel中继承了configureBlocking()和register()方法。ServerSocketChannel是ServerSocket的替代类,也具有负责接收客户连接的accept()方法。ServerSocketChannel并没有public类型的构造方法,必须通过它的静态方法open()来创建ServerSocketChannel对象每个ServerSocketChannel对象都与一个ServerSocket对象关联ServerSocketChannel的socket()方法返回与它关联的ServerSocket对象
  • 可通过以下方式把服务器进程绑定到一个本地端口:
serverSocketChannel.socket().bind(port);

ServerSocketChannel的主要方法如下:

/**
静态工厂方法,返回一个ServerSocketChannel的实例对象,
这个对象没有和任何本地端口绑定,并且处于阻塞模式
可通过上面的configureBlocking方法修改
**/
public static ServerSocketChannel open()throws IOException

/**
类似于ServerSocket的accept方法,用于接收客户端连接
如果处于非阻塞模式,当没有客户端连接时,该方法立即返回null。
如果处于阻塞模式,当没有客户端连接时,该方法就会一直阻塞下去,直到有客户端连接或者出现IO异常
**/
public SocketChannel accept()throws IOException
/**
返回ServerSocketChannel所能产生的事件,这个方法总是返回SelectionKey.OP_ACCEPT
**/
public final int validOps()
/**
返回与ServerSocketChannel关联的ServerSocket对象,每个ServerSocketChannel对象都与一个ServerSocket对象关联
**/
public ServerSocket socket()

6 SocketChannel

SocketChannel可看作是Socket的替代类,但它比Socket具有更多的功能。SocketChannel不仅从SelectableChannel父类中继承了configureBlocking()和register()方法,而且实现了ByteChannel接口,因此具有用于读写数据的read(ByteBuffer dst)和write(ByteBuffer src)方法。SocketChannel没有public类型的构造方法,必须通过它的静态方法open()来创建SocketChannel对象

SocketChannel的主要方法如下:

/**
静态方法,打开SocketChannel,返回SocketChannel的实例,
为阻塞模式,可通过上面的configureBlocking方法修改
**/
public static SocketChannel open() throws IOException 
/**
静态方法,打开SocketChannel,并指定要连接的远程服务器地址,返回SocketChannel的实例
为阻塞模式,可通过上面的configureBlocking方法修改
**/
public static SocketChannel open(SocketAddress remote) throws IOException
/**
返回SocketChannel所能产生的事件,这个方法总是返回以下值:
SelectionKey.OP_READ ,
SelectionKey.OP_WRITE ,
SelectionKey.OP_CONNECT
**/
public final int validOps()
/**
返回与当前SocketChannel关联的Socket对象,每个SocketChannel都与一个Socket对象关联
**/
public Socket socket()
/**
判断底层的Socket对象是否已经建立远程连接
**/
public boolean isConnected()
/**
判断是否正在进行远程连接
如果已经开始远程连接,但是还没有完成,则返回true,否则返回false
**/
public boolean isConnectionPending()
/**
当SocketChannel处于非阻塞模式时,如果理解连接成功返回true,如果不能连接成功返回false,程序必须稍后通过finishConnect方法完成连接
当SocketChannel处于阻塞模式时,如果理解连接成功返回true,如果不能连接成功返回则进入阻塞,直到连接成功,或者出现IO异常
**/
public boolean connect(SocketAddress remote)throws IOException
/**
试图完成连接远程服务器操作,在非阻塞模式下,建立连接从调用connect方法开始,到调用finishConnect结束。
如果finishConnect方法顺利完成远程连接或者在调用该方法之前已经完成连接,则立即返回true。
如果连接操作还没有完成,则该方法返回false
如果过程中出现异常而失败,则抛出相应的异常

在阻塞模式下, 如果连接操作还没有完成,就会进入阻塞,直到连接成功
**/
public boolean finishConnect()throws IOException
/**
从Channel中读入若干字节,把读取到的字节数据放到dst指定的ByteBuffer中
**/
public int read(ByteBuffer dst)throws IOException
/**
将src指定的ByteBuffer中的字节数据写到Channel中。
**/
public int write(ByteBuffer src)throws IOException
SocketChannel socketChannel = SocketChannel.open();
SocketAddress remote = new InetSocketAddress("localhost",8080);
socketChannel.connect(remote);
// 或者
SocketChannel socketChannel1 = SocketChannel.open(remote);

关于read和write方法图解

  • read:
    在这里插入图片描述
    假定执行read方法之前,ByteBuffer的位置为p,剩余容量为r,r等于bytebuffer.remaining()的返回值。假定调用read方法实际读取了n字节,那么(0<=n<=r)。当read方法返回后,ByteBuffer的postiton属性就变为p+n,limit不变。如上图所示。

在阻塞模式下:read方法会争取读取到r字节数量的数据,如果输入流中不足r字节,就进入阻塞状态,直到读取到了r字节,或者读到了输入流的末尾或者出现了IO异常。

在非阻塞模式下:read方法奉行能读到多少数据就读多少数据的原则,read方法读取当前通道中的可读数据,有可能不足r,或者为0,read方法总是立即返回。

  • write
    在这里插入图片描述
    假定执行write方法之前,ByteBuffer的位置为p,剩余容量为r,r等于bytebuffer.remaining()的返回值。假定调用write方法实际向通道里写入了n个字节,那么(0<=n<=r)。当write方法返回后,ByteBuffer的postiton属性就变为p+n,limit不变。如上图所示。

阻塞模式下:write方法会争取输出r字节的数据,如果底层网络的输出缓冲区不能容纳r字节,就会进入阻塞,直到输出了r字节或者出现IO异常

非阻塞模式:write方法奉行能输出多少数据就输出多少数据的原则,有可能不足r字节,也有可能为0,总是立即返回。

7 Selector

只要ServerSocketChannel以及SocketChannel向Selector注册了特定事件,Selector就会监控这些事件是否发生。
SelectableChannel的regist方法负责注册事件,并返回SelectionKey对象,这个对象用于跟踪这些事件的句柄。

一个Selector对象中会包含三种类型的SelectionKey的集合:

  • all-keys集合:当前所有向Selector注册的SelectionKey的集合, Selector的keys()方法返回该集合。
  • selected-keys集合:相关事件已经被Selector捕获的SelectionKey的集合。Selector的selectedKeys()方法返回该集合。
  • cancelled-keys集合:已经被取消的SelectionKey的集合。Selector没有提供访问这种集合的方法。

当执行SelectableChannel的regist方法,该方法会新键一个SelectionKey,并把它加入到all-keys集合中。
如果关闭了与SelectionKey对象关联的channel对象,或者调用了SelectionKey的cannel方法,那么这个SelectionKey就会被加入到cancelled-keys集合,在程序下次执行select方法的时候,被取消的SelectionKey将从所有的集合(all-keys,selected-keys,cancelled-keys)中删除。

在执行select方法的时候,如果与SelectionKey相关的事件发生,这个SelectionKey就会被加入到selected-keys集合中

Selector类的主要方法如下。

/**
静态工厂方法,创建一个Selector的实例
**/
public static Selector open()throws IOException
/**
判断Selector 是否处于打开状态,Selector对象创建后就处于打开状态,当调用了close方法就进入关闭状态
**/
public boolean isOpen()
/**
返回Selector 中的all-keys集合,包含了所有与当前Selector关联的SelectionKey对象
**/
public Set<SelectionKey> keys()
/**
返回已经发生的SelectionKey对象的数量,该方法是非阻塞的,如果没有就立即返回0
**/
public int selectNow()throws IOException
/**
返回已经发生的SelectionKey对象的数量,该方法是阻塞的,如果一个也没有就进入阻塞,直到有以下情况发生,就会返回
1. 至少有一个相关的SelectionKey的相关事件发生
2. 其他线程调用了Selector的wakeup方法
3. 当前执行select方法的线程被其他线程打断
4. 超出了等待时间, select(long timeout)可以设置超时时间,但是超时是不会抛出超时异常的
**/
public int select()throws IOException
public int select(long timeout)throws IOException
/**
唤醒执行select方法的线程,
**/
public Selector wakeup()
/**
关闭Selector,Selector占有的所有资源都会被释放,所有关联的SelectionKey都会被取消
**/
public void close()throws IOException

8 SelectionKey

  • ServerSocketChannel或SocketChannel通过register()方法向Selector注册事件时,register()方法会创建一个SelectionKey对象,这个SelectionKey对象是用来跟踪注册事件的句柄。
  • 在SelectionKey对象的有效期间,Selector会一直监控与SelectionKey对象相关的事件,如果事件发生,就会把SelectionKey对象加入到selected-keys集合中。

在以下情况,SelectionKey对象会失效,这意味着Selector再也不会监控与它相关的事件:

  1. 程序调用SelectionKey的cancel()方法。
  2. 关闭与SelectionKey关联的Channel。
  3. 与SelectionKey关联的Selector被关闭。

8.1 事件类型

在SelectionKey中定义了四种事件,分别用4个int类型的常量来表示:

  • SelectionKey.OP_ACCEPT:接收连接就绪事件,表示服务器监听到了客户连接,服务器可以接收这个连接了。常量值为16
  • SelectionKey.OP_CONNECT:连接就绪事件,表示客户与服务器的连接已经建立成功。常量值为8。
  • SelectionKey.OP_READ:读就绪事件,表示通道中已经有了可读数据,可以执行读操作了。常量值为1。
  • SelectionKey.OP_WRITE:写就绪事件,表示已经可以向通道写数据了。常量值为4。

以上常量分别占居不同的二进制位,因此可以通过二进制的或运算“|”,来将它们进行任意组合。

一个SelectionKey对象中包含两种类型的事件:

  • 所有感兴趣的事件:
    SelectionKey的interestOps()方法返回所有感兴趣的事件,例如假定返回值为SelectionKey.OP_WRITE | SelectionKey.OP_READ,就表示这个SelectionKey对读就绪和写就绪事件感兴趣。与之关联的Selector对象会负责监控这些事件。
    当通过SelectableChannel的register()方法注册事件时,可以在参数中指定SelectionKey感兴趣的事件,例如以下代码表明新建的SelectionKey对连接就绪和读就绪事件感兴趣:
    SelectionKey key = socketChannel.register(selector,
    SelectionKey.OP_CONNECT | SelectionKey.OP_READ);
    
    SelectionKey的interestOps(int ops)方法用于为SelectionKey对象增加一个感兴趣的事件。例如以下代码使得SelectionKey增加了一个感兴趣的事件:
    key. interestOps(SelectionKey.OP_WRITE);
    
  • 所有已经发生的事件
    SelectionKey的readyOps()方法返回所有已经发生的事件,例如假定返回值为SelectionKey.OP_WRITE | SelectionKey.OP_READ,表示读就绪和写就绪事件已经发生了,这意味着与之关联的SocketChannel对象可以进行读操作和写操作了。

当程序调用一个SelectableChannel(ServerSocketChannel或者SocketChannel)的register方法时,会建立SelectableChannel对象,Selector对象 以及register方法返回的SelectionKey对象的关联关联。SelectionKey的channel()方法会返回与之关联的SelectableChannel对象,selector()方法返回与之关联的Selector对象。

在这里插入图片描述

8.2 主要方法

/**
返回与之关联的SelectableChannel对象
**/
public SelectableChannel channel()
/**
返回与之关联的Selector对象。
**/
public Selector selector()
/**
判断这个SelectionKey是否有效
当SelectionKey创建后,就处于有效状态,如果调用了cancel方法,或者关闭了
与他关联的SelectableChannel或Selector对象,它就失效
**/
public boolean isValid()
/**
使SelectionKey失效。
该方法把SelectionKey对象加入与他关联的Selector对象的cancelled-keys集合中。
当程序下次执行Selector的select方法时候,会把SelectionKey对象从Selector对象的所有集合(all-keys,selected-keys,cancelled-keys)
中删除
**/
public void cancel()
/**
返回这个SelectionKey对象感兴趣的事件
**/
public int interestOps()
/**
为SelectionKey增减感兴趣的事件
**/
public SelectionKey interestOps(int ops)
/**
返回已经就绪的事件
**/
public int readyOps()
/**
判断与之关联的SocketChannel的读就绪事件是否已经发生。
该方法等价于:selectionKey.readyOps() & SelectionKey.OP_READ != 0
**/
public final boolean isReadable()
/**
判断与之关联的SocketChannel的写就绪事件是否已经发生。
该方法等价于: selectionKey.readyOps() & SelectionKey.OP_WRITE != 0
**/
public final boolean isWritable()
/**
判断与之关联的SocketChannel的连接就绪事件是否已经发生。
该方法等价于:  selectionKey.readyOps() & SelectionKey.OP_CONNECT != 0
**/
public final boolean isConnectable()
/**
判断与之关联的ServerSocketChannel的接收连接就绪事件是否已经发生。
该方法等价于:  selectionKey.readyOps() & SelectionKey.OP_ACCEPT != 0
**/
public final boolean isAcceptable()
/**
使SelectionKey关联一个附件,一个SelectionKey只能关联一个附件
**/
public final Object attach(Object ob)
/**
返回SelectionKey关联的附件,
**/
public final Object attachment()

8 Channels类

一个工具类,提供了通道与传统的基于IO流,Reader,Writer之间转换的静态方法

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
SocketChannel socketChannel = serverSocketChannel.accept();
// 将SocketChannel转换为输入流,接下来就可以按照输入流的方式来读取数据
InputStream inputStream = Channels.newInputStream(socketChannel);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值