Java——SocketChannel / ServerSocketChannel / Selector
Channel
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中。 返回读取的字节数量,如果读取完毕,返回-1。 while(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 ssc = ServerSocketChannel.open(); // open并非打开,而是创建 ServerSocket ss = ssc.socket(); ss.bind(new InetSocketAddress(80)); 监听 .appcept(); 默认是 blocking 模式。 non-blocking模式下,.accept()在没有连接的情况下立刻返回null。需要使用 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()
当要处理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 线程可以并发处理 N 个客户端连接和读写操作, 这从根本上解决了传统同步阻塞 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