• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

无信不立

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【网络编程】五ServerSocketChannel,SocketChannel,Selector,SelectionKey,ByteBuffer

一、线程阻塞的原因

1、Thread.sleep() ,放弃Cpu调度权

2、执行同步代码,无法获得同步锁,阻塞

3、线程执行一个对象的wait()方法,进入阻塞状态。需要其他线程执行该对象的notify()或notifyAll()方法

4、线程执行I/O操作或远程通信时,会因为等待相关资源而进入阻塞状态。输入流进行read()时会阻塞。输出流write()

5、请求与服务器建立连接时,Sockect.connet(),直到与服务器连接成功,才会返回。 服务器ServerSocket.accpet()等待客户端连接也会阻塞。

二、什么是阻塞式I/O和非阻塞式I/O

无论服务器程序还是客户程序中,当通过Socket的输入流和输出流来读写数据,都可能进入阻塞状态。这种称为阻塞式I/O, 如果执行输入和输出操作时,不会发生阻塞,则称为非阻塞式I/O.

 

三、非阻塞式I/O的API

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

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

3、Selector:为ServerSocketChannel监控连接的就绪事件,为ServerSocketChannel监听连接就绪,读就绪和写就绪事件。

4、SelectorKey:代表ServerSocketChannel以及SocketChannel向Selector注册事件的句柄,当一个SelectorKey 位于Selector的Selected-Keys集合中。就表示与这个SelectorKey相关联的事件发生。

  • SelectorKey.OP_CONNECT: 连接就绪事件,表示客户与服务器的链接已经建立成功。

  • SelectorKey.OP_READ:读就绪事件,表示输入流已经有了可读数据,可以执行读操作了。

  • SelectorKey.Op_WRITE: 写就绪事件,表示已经可以向输出流写数据了。

5、ByteBuffer:表示字节缓冲区。SocketChannel的read,write方法都会操纵ByteBuffer类。Charset表示字符编码。 字节流到字符串为解码过程。字符串到字节流为编码过程。

 

四、缓冲区ByteBuffer

4.1、缓冲区从两个方面提高I/O的操作效率

  • 减少实际的物理读写次数

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

4.2、缓冲区拥有的属性

4.2.1、常见的buffer类有

ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer,MappedByteBuffer(缓冲区和文件某个区域映射)

 

4.2.2、常用的api方法如下

1、clear():把极限设置为容量,把位置设置为0

2、flip():把极限设置为位置,把位置设置为0

3、rewind():不改变极限,把位置设置为0

4、remaining():返回缓冲区的剩余容量。取值等于 极限 - 位置

5、compact():删除缓冲区从0到当前位置position的内容,再把从当前位置position到极限的内容Limit的内容copy到 0 到 limit-position的区域内。当前位置position和极限limit的取值也做了相应的变化。如下图。

6、allocate(int capacity) : 创建一个byteBuffer对象,capacity指定了缓冲区的容量大小。

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

8、缓冲区可支持相对读写,和绝对读写。

get():相对读,从当前位置读取一个单元的数据,读完后位置加1。

get(int index):绝对读,从参数index指定的位置读取一个单元的数据。

put(单元数据类型 data):相对写,向缓冲区的当前位置写入一个单元的数据,写完后位置加1。

put(int index, 单元数据类型 data):绝对写,向缓冲区的指定位置写入一个单元的数据。

9、Charset字符编码

ByteBuffer encode(String str) : 对str按特定字符编码格式,进行编码,返回byteBuffer

ByteBuffer encode(CharBuffer char) : 对char中的字符按特定编码格式,进行编码,返回byteBuffer

CharBuffer decode(ByteBuffer bb):对bb中的字节序列,进行解码。

Charset Charset.forName(String name): 生成一个指定字符编码格式的字符编码对象。

Charset Charset.defaultCharset():返回本地平台默认的字符编码对象。

4.2.3、缓冲区视图

CharBuffer charBuffer = byteBuffer.asCharBuffer();

以上的charBuffer视图 和 底层byteBuffer 共享相同的数据,修改charBuffer视图的数据,会反映到底层的byteBuffer上。

不过charBuffer和byteBuffer有各自独立的 capacity,position,limit

五、通道Channel

5.1、通道是用来连接缓冲与数据源和数据汇(即数据目的地)的

5.2、Channnel的核心类图的继承关系

5.2.1、Channel

close():关闭通道

isOpen():判断通道是否打开,一旦通道关闭,就不能再打开。

5.2.2、ReadableByteChannel和WritableByteChannel

read(ByteBuffer dst): 从远端读入数据,放入到bygeBuffer中

write(ByteBuffer src):将byteBuffer中的数据写到远端

5.2.3、ScatteringByteChannel和GatheringByteChannel

ScatteringByteChannel:允许分散地读取数据。分散读取数据指的是单个读取操作能填充多个缓冲区。提高读效率。read(ByteBuffer[] dsts, int offset, int length), read(ByteBuffer[] dsts)

GatheringByteChannel:允许集中写数据,依次把参数指定的byteBuffer数组写入到远端。提高写效率。write(ByteBuffer[] srcs, int offset, int length),write(ByteBuffer[] srcs)

5.2.4、ByteChannel

同时支持读写操作

5.2.5、FileChannel

同时继承了多个接口类,代表一个与文件相连的通道。没有提供公开的构造方法。因此客户端程序没有办法通过new 来构造实例。

不过,在FileInputStream和FileOutPutStream,RandomAccessFile类中提供了getChannel()方法,该方法返回响应的FileChannel对象。

5.2.6、SelectableByteChannel

它不仅支持阻塞I/O操作,还支持非阻塞的I/O操作。

它可以向Selector注册读就绪和写就绪事件。

SelectableChannel configureBlocking(boolean block):将通道设置为是否阻塞I/O, true代表阻塞I/O, false代表非阻塞I/O

SelectionKey register(Selector sel, int ops):注册监听事件。

SelectionKey register(Selector sel, int ops, Object att) : 注册监听事件和附件。其中附件,可以是一个配置信息或策略类信息。最终会通过SelectionKey得到。

Handle handle =new Handle();
SelectionKey selectionKey = socketChannel.register(selector,SelectionKey.OP_READ|SelectionKey.OP_WRITE,handle);

等同于

SelectionKey selectionKey1 =  socketChannel.register(selector,SelectionKey.OP_READ|SelectionKey.OP_WRITE);
Handle handle =new Handle();
selectionKey1.attach(handle);

5.2.7、ServerSocketChannel

可以看作是ServerSocket对象的替代品。该对象的创建,通过静态方法open()创建。绑定端口的代码。

ServerSocketChannel socketChannel = ServerSocketChannel.open();

ServerSocket socket(); 返回一个ServerSocket对象。每一个ServerSocketChannel都会关联一个ServerSocket对象。

configureBlocking(boolean isBlock):设置通道是否为阻塞或非阻塞

SelectionKey register(Selector sel, int ops,Object att) 向Selector中注册网络事件

int validOps():返回所能产生的事件,该类总是返回SelectionKey.OP_ACCEPT

SocketChannel accept(): 返回SocketChannel对象,如果是阻塞模式,则会阻塞。非阻塞模式,如果有连接通道,则返回SocketChannel。如果没有连接通道,则返回null.

5.2.7、SocketChannel

可以看作是Socket对象的替代品。但比Socket具有更多的功能。

SocketChannel open():开启一个SocketChannel对象。返回的SocketChannel处于阻塞模式。

SocketChannel open(SocketAddress remote):开启一个SocketChannel对象,并与远程服务端建立连接。

int validOps():返回SocketChannel会产生那些事件,总是返回SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT

Socket socket():每一个SocketChannel对象总是关联一个Socket

boolean isConnected():是否已经建立远程连接。

boolean isConnectionPending():判断是否正在进行远程连接。如果远程连接操作已经开始,但还没有完成,则返回true.否则返回false.(也就是说无论底层Socket还没有开始连接,或者已经连接成功,都会返回fasle)

boolean connect(SocketAddress remote):时底层Socket建立远程连接。

  • 当SocketChannel处于非阻塞模式,如果立即连接成功则返回true.如果不能立即建立成功,则返回false.稍后可以通过finishConnect()方法来完成连接。

  • 当SocketChannel处于阻塞模式,如果立即连接成功则返回true.如果不能立即连接成功,则该方法返回false.

boolean finishConnect():

  • 非阻塞模式下,建立连接从调用SocketChannnel的connect()方法开始,到调用finishConnect()方法结束。如果finishConnect()方法顺利完成连接,或调用此方法前连接已经建立,则直接返回true . 如果连接还没建立完成,则返回false.

  • 在阻塞模式下,会阻塞住,直到连接建立完成。或抛出异常。

int read(ByteBuffer byteBuffer): 从远端读入流写入byteBuffer中。 返回值为实际读取的字节数。

  • 如果缓冲区满了,则直接返回0 。

  • 非阻塞模式下,read方法总会立即返回,会返回实际读取的字节数。也有可能为0,如果返回-1,则表示读到流的末尾。

  • 阻塞模式下,争取读到byteBuffer剩余容量的字节。如果没有数据准备好会阻塞下去,或者读到足够数据返回,或者读到字节流末尾。或者抛出异常。

int write(ByteBuffer byteBuffer)

  • 在阻塞模式下,争取将byteBuffer中从positiond到剩余字节的位置的所有字节,写入远端。如果底层网络的输出缓冲区不能容纳下要写的字节,就会阻塞主。直到输出完成。或抛出异常。

  • 在非阻塞模式下,奉行能写出多少数据,写多少数据。有可能写完,有可能写不完,有可能一个都没写。返回值是实际写出的字节数。

     

 

5.2.7、Selector

该对象叫多路复用器,向其中注册ServerSocketChannel和SocketChannel的网络事件。在注册事件时,该方法会返回一个SelectionKey.表示跟踪注册时间的句柄。

其有3个集合。

  • all-keys集合: 当前向Selector注册的SelectionKey的集合。Selector.keys()方法会返回该集合。

  • selected-keys集合:相关网络事件,已经被Selector捕获的的SelectionKey的集合。Selector.selectedKeys()方法返回的集合。 当方式的事件需要程序显示移除。通过返回的集合remove() 或其迭代器Interator的remove()方法删除。

  • cancelled-keys集合:已经被取消的SelectionKey的集合。没有提供访问该中集合的方法。如果SelectionKey关联的channel对象被关闭,或者是SelectionKey.cancel(),其会被加入cancelled-keys集合。会在Selector.select()方法时隐式清除取消的SelectionKey。

open():建立一个Selector对象。

isOpen():判定是否处于打开状态,如果调用了colse()方法,则返回false.

selectNow():返回已经发生网络事件的SelectionKey对象的数目。采用非阻塞方式工作。

select()和select(long timeout):返回已经发生的网络事件的数据,采用阻塞方式工作。触发select()方法返回的情况如下

  • 至少有一个selectionKey的相关事件已发生。

  • 其他线程调用Selector.wakeup()方法。导致执行select()方法的线程立即从selector()方法返回。

  • 当前执行select()方法的线程被其他线程中断。

  • 如果超出等待时间,单位是ms,就会正常返回,但不会抛出超时异常。

wakeup():该方法每次调用,只能唤醒一次selector.select()方法。

colse():会释放所有的资源。丢弃关联的SelectionKey

 

5.2.7、SelectionKey

用来跟踪网络事件的句柄。

什么情况下SelectionKey会失效

  • 程序调用SelectionKey.cancel方法

  • 关闭与SelectionKey关联的channel

  • 与SelectionKey关联的Selector被关闭

监听的网络事件

  • public static final int OP_READ = 1 << 0;

  • public static final int OP_WRITE = 1 << 2;

  • public static final int OP_CONNECT = 1 << 3;

  • public static final int OP_ACCEPT = 1 << 4;

public final boolean isReadable():关联的socketChannel的读就绪

public final boolean isWritable():关联的socketChannel的写就绪

public final boolean isConnectable():关联的socketChannel的连接就绪

public final boolean isAcceptable():关联的serverSocketChannel的读就绪

public abstract SelectableChannel channel(); 返回关联的channnel对象

public abstract boolean isValid();当前追踪句柄是否有效

public abstract Selector selector();返回关联的多路复用器

public abstract void cancel();取消该句柄

public abstract int interestOps();返回感兴趣的的网络事件

public abstract SelectionKey interestOps(int ops);添加感兴趣的网络事件

public abstract int readyOps();已经就绪的网络事件

public final Object attach(Object ob) :添加一个附件
public final Object attachment():返回一个附件

 

 

 

 

 

 

 

 

posted on 2022-03-19 16:08  无信不立  阅读(435)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3