Netty
传统IO(同步阻塞式)
传统IO的缺点就是一个线程只能处理一个socket,需要处理多个socket时只能使用线程池。但并不是连接的所有socket都在无时无刻传送数据,所以大多数时间处理socket的线程都在处于阻塞状态,效率低下,开销大。
单线程下只能有一个客户端,多线程可以有多个客户端,但十分消耗性能
阻塞点
-
在等待客户端连接时,发生阻塞
while(true){ //获取一个套接字(阻塞) final Socket socket = server.accept(); System.out.println("来个一个新客户端!"); newCachedThreadPool.execute(new Runnable() { @Override public void run() { //业务处理 handler(socket); } }); }
在final Socket socket = server.accept();处一直等待客户端连接
-
连接后在获取输入流时,发生阻塞
while(true){ //读取数据(阻塞) int read = inputStream.read(bytes); if(read != -1){ System.out.println(new String(bytes, 0, read)); }else{ break; } }
在int read = inputStream.read(bytes);处一直等待客户端发送数据
NIO(同步非阻塞)
NIO之所以是同步,是因为它的accept/read/write方法的内核I/O操作都会阻塞当前线程,与IO相比,NIO可以实现单线程处理多个客户端
NIO由三个主要部分组成:Channel(通道)、Buffer(缓冲区)、Selector(选择器)
与IO的对应关系
相同功能关键字
ServerSocketChannel 对应 ServerSocket
SocketChannel 对应 Socket
新增关键字
Selector,用于监听ServerSocketChannel和SocketChannel发生的事件
SelectionKey,每一个Channel都需要被注册到Selector上才能使用,而SelectionKey代表了此Channel在某一Selector上注册=。
具体流程
-
初始化一个NIOServer,并初始化一个Selector,将使用的端口号传入初始化方法
NIOServer server = new NIOServer(); server.initServer(8000);
-
初始化ServerSocketChannel,将对应的ServerSocket与传入的端口号绑定,并指明一个Selector为这个ServerSocketChannel服务。
ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 设置通道为非阻塞 serverChannel.configureBlocking(false); // 将该通道对应的ServerSocket绑定到port端口 serverChannel.socket().bind(new InetSocketAddress(port)); // 获得一个通道管理器 this.selector = Selector.open(); // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后, // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。 serverChannel.register(selector, SelectionKey.OP_ACCEPT);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
指明一个Selector监听ServerSocketChannel,当发生OP_ACCEPT 事件时,返回一个SelectionKey,类似于K-V,一个SelectionKey对应了一个事件。
-
服务端初始化后,开始监听客户端的消息,在注册的信息到达之前,selector.select()一直处于监听状态。当客户端成功与服务器建立连接后,触发了OP_ACCEPT,此时程序才会向下执行
selector.select();
-
触发事件后,将获取到一个迭代器,用于遍历触发的事件,也就是SelectionKey,然后按序逐一调用handler方法,根据事件类型的不同,转入不同的处理方法
public void handler(SelectionKey key) throws IOException { // 客户端请求连接事件 if (key.isAcceptable()) { handlerAccept(key); // 获得了可读的事件 } else if (key.isReadable()) { handelerRead(key); } }
-
用户连接时触发的是OP_ACCEPT,用key.isAcceptable()接收,转入OP_ACCEPT的具体处理方法,在具体的方法中,先从key中获取到SocketServerChannel,再从server中获取到SocketChannel,指明一个Selector监听SocketChannel,当发生OP_READ事件时,Selector将会把此事件放入容器中按序处理
public void handlerAccept(SelectionKey key) throws IOException { ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 获得和客户端连接的通道 SocketChannel channel = server.accept(); // 设置成非阻塞 channel.configureBlocking(false); // 在这里可以给客户端发送信息哦 System.out.println("新的客户端连接"); // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。 channel.register(this.selector, SelectionKey.OP_READ);
}
这时,Selector中监听的事件有ServerSocketChannel的OP_ACCEPT事件 和 SocketChannel的
OP_READ事件
- 当Selcetor内存在事件时,便使用selector.select()进行处理