Java——SocketChannel / ServerSocketChannel / Selector

Channel

 
Channel用来操作数据块,写入buffer或者从IO中读到buffer。
 

SocketChannel
创建
// 创建,并阻塞等待连接
SocketChannel sc = SocketChannel.open(new InetSocketAddress("abc.com",80));
 
// 创建,使用connect()连接
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("abc.com",80)); // blocking
 
// 创建,非阻塞
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false);     // set no-block
sc.connect(new InetSocketAddress("abc.com",80)); // no-blocking
 
// 非阻塞模式的一般处理方式——轮询!
while(true)
{
     if(sc.finishConnect() /* 立即返回,已经连接返回true,否则false */){
          break; 
          /* connected,start write */
     }
}
 
.isConnected()
.isConnectionPending() // 正在setup,还未open

读 Read
int sc.read(ByteBuffer dst)
尽可能多的读数据到dst中。
返回读取的字节数量,如果读取完毕,返回-1while(buffer.hasRemaining() && channel.read(buffer)!=-1 )
{
}
 
Scatter 读取并复制到多个Buffer
 
ByteBuffer[] buffers = new ByteBuffer[2];
buffers[0] = ByteBuffer.allocate(1000);
buffers[1] = ByteBuffer.allocate(1000);
while(buffer[1].hasRemaining() && channel.read(buffer)!=-1 )
{
}
 
写 Write
while(buffer.hasRemaining() && channel.write(buffer)!=-1 )
{
}
 
gather 从多个buffer读取,写入到同一个socket中
 
channel.write(ByteBuffer[] dsts);
 
 

关闭
if( channel.isOpen() )
{
     channel.close();
}

 



 
ServerSocketChannel
 
创建
ServerSocketChannel ssc = ServerSocketChannel.open(); // open并非打开,而是创建
ServerSocket ss = ssc.socket();
ss.bind(new InetSocketAddress(80));
 
监听
.appcept();
默认是 blocking 模式。
non-blocking模式下,.accept()在没有连接的情况下立刻返回null。需要使用 Selector 处理。

 



Selector
 
Selector selector = Selector.open();
 
向selector注册channel
ServerSocketChannel ssc = ... ;
ssc.register(selector,SelectionKey.OP_ACCEPT/* 监听事件 */|SelectionKey.OP_CONNECT);
ssc.register(selector,OP,object state);
 
事件类型
SelectionKey.OP_ACCEPT
SocketChannel无本事件,ServerSocketChannel有
          
SelectionKey.OP_CONNECT          
SelectionKey.OP_READ
SelectionKey.OP_WRITE
 
各个channel注册到selector后,随时可以轮询哪些channel有了事件需要处理
selector.selectNow() // no-blocking ,没有则返回0
selector.select()     // blocing,直到至少有一个事件
selector.select(long timeout)
 
有事件准备好处理之后,获取对应的channel
Set selector.selectedKeys();
 
关闭
selector.close()

 

SelectionKey
是 selector + channel + 事件类型
当要处理selectedKey的时候,先判断是哪个事件
SelectionKey key = selector.selectedKeys().get(0);
if(key.isAcceptable()){}
if(key.isConnectionable()){}
 
// 获取对应的channel
SelectableChannel c = key.channel();
 
// 获取附加状态
key.attachment()
 
// 不再跟踪
key.cancel()

 

 

1) Java 的 NIO, 用非阻塞的 IO 方式。 可以用一个线程, 处理多个的客户端连接, 就会使用到 Selector(选择器)
2) Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个Selector), 如果有事件发生, 便获取事件然后针对每个事件进行相应的处理。 这样就可以只用一个单线程去管
理多个通道, 也就是管理多个连接和请求。
3) 只有在 连接/通道 真正有读写事件发生时, 才会进行读写, 就大大地减少了系统开销, 并且不必为每个连接都创建一个线程, 不用去维护多个线程
4) 避免了多线程之间的上下文切换导致的开销

 

 1) Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器, 也叫多路复用器), 可以同时并发处理成百上千个客户端连接。
 2) 当线程从某客户端 Socket 通道进行读写数据时, 若没有数据可用时, 该线程可以进行其他任务。
 3) 线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作, 所以单独的线程可以管理多个输入和输出通道。
 4) 由于读写操作都是非阻塞的, 这就可以充分提升 IO 线程的运行效率, 避免由于频繁 I/O 阻塞导致的线程挂起。
 5) 一个 I/O 线程可以并发处理 个客户端连接和读写操作, 这从根本上解决了传统同步阻塞 I/O 一连接一线程模型, 架构的性能、 弹性伸缩能力和可靠性都得到了极大的提升 

 

 Selector 示意图和特点说明

Selector 类相关方法

1) NIO 中的 ServerSocketChannel 功能类似 ServerSocket, SocketChannel 功能类似 Socket

2) selector 相关方法说明
  selector.select()//阻塞
  selector.select(1000);//阻塞 1000 毫秒, 在 1000 毫秒后返回
  selector.wakeup();//唤醒 selector
  selector.selectNow();//不阻塞, 立马返还

 

 

NIO 非阻塞 网络编程原理分析图
NIO 非阻塞 网络编程相关的(Selector、 SelectionKey、 ServerScoketChannel 和 SocketChannel) 关系梳理图

 

 对上图的说明:
1) 当客户端连接时, 会通过 ServerSocketChannel 得到 SocketChannel
2) Selector 进行监听 select 方法返回有事件发生的通道的个数.

3) 将 socketChannel 注册到 Selector , register(Selector sel, int ops), 一个 selector 上可以注册多个 SocketChannel
4) 注册后返回一个 SelectionKey, 会和该 Selector 关联(集合)
5) 进一步得到各个 SelectionKey (有事件发生)
6) 在通过 SelectionKey 反向获取 SocketChannel , 方法 channel()
7) 可以通过 得到的 channel , 完成业务处理。

 

SelectionKey

1) SelectionKey, 表示 Selector 和网络通道的注册关系共四种:
int OP_ACCEPT: 有新的网络连接可以 accept, 值为 16
int OP_CONNECT: 代表连接已经建立, 值为 8
int OP_READ: 代表读操作, 值为 1
int OP_WRITE: 代表写操作, 值为 4
源码中:
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;

 

参阅:

https://www.cnblogs.com/cb1186512739/p/12773959.html

 

 

posted @ 2022-07-15 18:55  会飞的斧头  阅读(666)  评论(0编辑  收藏  举报