基本介绍
Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到Selector(选择器) Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。【示意图】 只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程 避免了多线程之间的上下文切换导致的开销
特点再说明:
Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),可以同时并发处理成百上千个客户端连接。 当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。 线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。 由于读写操作都是非阻塞的,这就可以充分提升 IO线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。 一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
Selector类相关方法
public abstract class Selector implements Closeable {
public static Selector open ( ) ;
public int select ( long timeout) ;
public Set < SelectionKey > selectedKeys ( ) ;
}
注意事项
NIO中的 ServerSocketChannel功能类似ServerSocket,SocketChannel功能类似Socket selector 相关方法说明 selector.select()//阻塞 selector.select(1000);//阻塞1000毫秒,在1000毫秒后返回 selector.wakeup();//唤醒selector selector.selectNow();//不阻塞,立马返还
NIO 非阻塞 网络编程原理分析
当客户端连接时,会通过ServerSocketChannel 得到 SocketChannel Selector 进行监听 select 方法, 返回有事件发生的通道的个数. 3. 将socketChannel注册到Selector上, register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel 注册后返回一个 SelectionKey, 会和该Selector 关联(集合) 进一步得到各个 SelectionKey (有事件发生) 在通过 SelectionKey 反向获取SocketChannel , 方法 channel() 可以通过 得到的 channel , 完成业务处理 代码撑腰。。。
代码
服务端
package com. jhj. channel ;
import java. io. IOException ;
import java. net. InetSocketAddress ;
import java. nio. ByteBuffer ;
import java. nio. channels. * ;
import java. util. Iterator ;
import java. util. Set ;
public class NioServer {
public static void main ( String [ ] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel . open ( ) ;
Selector selector = Selector . open ( ) ;
serverSocketChannel. socket ( ) . bind ( new InetSocketAddress ( 666 ) ) ;
serverSocketChannel. configureBlocking ( false ) ;
serverSocketChannel. register ( selector, SelectionKey . OP_ACCEPT) ;
while ( true ) {
if ( selector. select ( 1000 ) == 0 ) {
System . out. println ( "服务器等待1秒,无连接" ) ;
continue ;
}
Set < SelectionKey > selectionKeys = selector. selectedKeys ( ) ;
Iterator < SelectionKey > iterator = selectionKeys. iterator ( ) ;
while ( iterator. hasNext ( ) ) {
SelectionKey next = iterator. next ( ) ;
if ( next. isAcceptable ( ) ) {
SocketChannel accept = serverSocketChannel. accept ( ) ;
System . out. println ( "客户端连接成功,生成了一个socketChannel" + accept. hashCode ( ) ) ;
accept. configureBlocking ( false ) ;
accept. register ( selector, SelectionKey . OP_READ, ByteBuffer . allocate ( 1024 ) ) ;
}
if ( next. isReadable ( ) ) {
SocketChannel channel = ( SocketChannel ) next. channel ( ) ;
ByteBuffer buffer = ( ByteBuffer ) next. attachment ( ) ;
channel. read ( buffer) ;
System . out. println ( "客户端发送的数据是" + new String ( buffer. array ( ) ) ) ;
}
iterator. remove ( ) ;
}
}
}
}
客户端
package com. jhj. channel ;
import java. io. IOException ;
import java. net. InetSocketAddress ;
import java. nio. ByteBuffer ;
import java. nio. channels. SocketChannel ;
public class NioClient {
public static void main ( String [ ] args) throws IOException {
SocketChannel open = SocketChannel . open ( ) ;
open . configureBlocking ( false ) ;
InetSocketAddress inetSocketAddress = new InetSocketAddress ( "127.0.0.1" , 666 ) ;
if ( ! open . connect ( inetSocketAddress) ) {
while ( ! open . finishConnect ( ) ) {
System . out. println ( "因为连接需要时间,客户端不会阻塞,可以做其他工作" ) ;
}
}
String str= "hello" ;
ByteBuffer wrap = ByteBuffer . wrap ( str. getBytes ( ) ) ;
open . write ( wrap) ;
System . in. read ( ) ;
}
}
SelectionKey
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;
public abstract class SelectionKey {
public abstract Selector selector ( ) ;
public abstract SelectableChannel channel ( ) ;
public final Object attachment ( ) ;
public abstract SelectionKey interestOps ( int ops) ;
public final boolean isAcceptable ( ) ;
public final boolean isReadable ( ) ;
public final boolean isWritable ( ) ;
}
ServerSocketChannel
ServerSocketChannel 在服务器端监听新的客户端 Socket 连接 相关方法如下
public abstract class ServerSocketChannel
extends AbstractSelectableChannel
implements NetworkChannel {
public static ServerSocketChannel open ( ) ,得到一个 ServerSocketChannel 通道
public final ServerSocketChannel bind ( SocketAddress local) ,设置服务器端端口号
public final SelectableChannel configureBlocking ( boolean block) ,设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
public SocketChannel accept ( ) ,接受一个连接,返回代表这个连接的通道对象
public final SelectionKey register ( Selector sel, int ops) ,注册一个选择器并设置监听事件
}
SocketChannel
SocketChannel,网络 IO 通道,具体负责进行读写操作。NIO 把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。 相关方法如下
public abstract class SocketChannel
extends AbstractSelectableChannel
implements ByteChannel , ScatteringByteChannel , GatheringByteChannel , NetworkChannel {
public static SocketChannel open ( ) ;
public final SelectableChannel configureBlocking ( boolean block) ;
public boolean connect ( SocketAddress remote) ;
public boolean finishConnect ( ) ;
public int write ( ByteBuffer src) ;
public int read ( ByteBuffer dst) ;
public final SelectionKey register ( Selector sel, int ops, Object att) ;
public final void close ( ) ;
作者声明
如有问题,欢迎指正!