2. Netty初认识--NIO入门---服务端处理概述
BIO (Blocking)在处理的时候,每一个客户端请求,新启动一个线程进行操作,当并发量大的时候扛不住。
伪异步IO采用线程池和任务队列的方式有所改进,避免每个请求都创建一个新的线程;但是此方法底层通信依然采用同步阻塞模型,无法从根本上解决问题,
BIO是同步的,服务器或者客户端有一方响应慢,就会造成对方也在等待。
NIO可以提供异步操作。
NIO弥补了原来同步阻塞I/O的不足,提供了高速的,面向快的I/O. 了解一下NIO类库的相关概念:
1. 缓冲区Buffer
缓冲区Buffer是一个对象,包含一些要写入或者要读出的数据. 在面向流的I/O中,可以将数据直接写入或者将数据直接读到Stream对象中。
在NIO库中,所有数据都是用缓冲区处理的. 缓冲区实质上是一个数组.通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit)等信息。事实上每一种Java基本类型(除了Boolean类型) 都对应一种缓冲区:
ByteBuffer: 字节缓冲区
CharBuffer: 字符缓冲区
ShortBuffer: 短整形缓冲区
IntBuffer: 整形缓冲区
LongBuffer: 长整形缓冲区
FloatBuffer: 浮点型缓冲区
DoubleBuffer: 双精度浮点型缓冲区
2. 通道Channel
Channel是个通道,可以通过它读取和写入数据,它就像自来水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,流只是在一个方向上移动(InputStream 或者 OutputStream),而且通道可以用于读、写或同时用于读写。
因为Channel是全双工的,所以它可以比流更好的映射底层操作系统的API。Channel可以分为两大类,用于网络读写的SelectableChannel和用于文件操作的FileChannel.
3. 多路复用器Selector
Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel集合,进行后续的I/O操作。一个多路复用Selector可以同时轮询多个Channel.
下面以服务端创建进行说明:(以下看做伪代码,不可以运行)
package club.wujingjian.com.wujingjian.nio;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class ServerPoint {
public static void main(String[] args) throws IOException {
//1.打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道,
ServerSocketChannel acceptorSvr = ServerSocketChannel.open();
//2. 绑定监听端口,设置连接为非阻塞模式,
int port = 8989;
acceptorSvr.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"),port));
acceptorSvr.configureBlocking(false);
//3. 创建Reactor线程,创建多路复用器并启动线程
Selector selector = Selector.open();
new Thread(new ReactorTask()).start();
//4. 将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT事件
SelectionKey key = acceptorSvr.register(selector, SelectionKey.OP_ACCEPT, ioHandler);
//5. 多路复用器在多线程run方法的无线循环体内轮训准备就绪的Key
int num = selector.select();
Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key1 = (SelectionKey) it.next();
// .... 处理 IO 事件.....
}
//6. 多路复用监听到有新的客户端接入,处理新的接入请求,完成TCP 三次握手,建立物理链路
SocketChannel channel = acceptorSvr.accept();
//7. 设置客户端链路为非阻塞模式,
channel.configureBlocking(false);
channel.socket().setReuseAddress(true);
//8. 将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作
SelectionKey key2 = channel.register(selector, SelectionKey.OP_READ,ioHandler);
//9. 异步读取客户端请求消息到缓冲区
int readNumber = channel.read(receivedBuffer);
//10. 对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装为Task,投递到业务线程池中,进行业务逻辑编排
Object message = null;
while (buffer.hasRemain()) {
byteBuffer.mark();
Object message = decode(byteBuffer);
if (message == null) {
byteBuffer.reset();
break;
}
messageList.add(message);
}
if(!byteBuffer.hasRemain()) {
byteBuffer.clear();
} else {
byteBuffer.compact();
}
if (messageList != null && !messgeList.isEmpty()) {
for (Object messageE : messageList) {
handlerTask(messageE);
}
}
//11. 将POJO对象encode成ByteBuffer,调用SocketChannel 的异步write接口,将消息异步发送给客户端,
socketChannel.write(buffer);
//注意:如果发送区TCP缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,直到整包消息写入TCP缓冲区,
}
}
标题:2. Netty初认识--NIO入门---服务端处理概述
作者:码农路上
地址:http://wujingjian.club/articles/2020/02/26/1582714725734.html