TCP 是以流的方式来处理数据的,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。
BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理,线程开销大。是面向流的,阻塞流,流是单向的。
NIO: 一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。面向缓存区,非阻塞,channe是双向的。
AIO:一个有效请求一个线程,客户端的IO请求都是由OS先完成了再通知服务器应用去启动线程进行处理
Buffer:与Channel进行交互,数据从Channel读入缓冲区,从缓冲区写入Channel中
flip方法:反转缓冲区,将position给limit,然后将position置为0,就是读写切换
clear方法:清除此缓冲区,将position置为0,把capacity的值给limit
rewind方法:重置此缓冲区,将position置为0
DirectByteBuffer:可减少一次系统空间到用户空间的拷贝
Channel:与数据源的连接,是双向的,只能与Buffer交互
Selector:允许单个线程管理多个Channel
Pipe:两个线程之间的单向数据连接,数据会被写到sink通道,从source通道读取
NIO服务端建立过程:
- ServerSocketChannel.open 创建服务端Channel
- bind 绑定服务端端口
- 配置非阻塞模式
- Selector.open 打开一个selector
- 注册关注的事件到selector上
// 服务端代码
public class NioServer {
public static void main(String[] args) {
try(ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
serverSocketChannel.socket().bind(new InetSocketAddress(3388));
Selector selector = Selector.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器准备就绪,开始监听,端口3388");
while (true) {
int wait = selector.select();
if (wait == 0)
continue;
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} else if(key.isReadable()) {
SocketChannel server = (SocketChannel) key.channel();
int len = server.read(byteBuffer);
if (len > 0) {
byteBuffer.flip();
String content = new String(byteBuffer.array(), 0, len);
System.out.println(content);
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_WRITE);
}
byteBuffer.clear();
} else if (key.isWritable()) {
SocketChannel server = (SocketChannel) key.channel();
server.write(ByteBuffer.wrap("Hello Client!".getBytes()));
}
iterator.remove();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//客户端代码
public class NIOClient {
public static void main(String[] args) {
try(SocketChannel channel = SocketChannel.open()) {
channel.connect(new InetSocketAddress(3388));
//发送数据
if (channel.isConnected()) {
channel.write(ByteBuffer.wrap("Hello Server!".getBytes()));
}
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = channel.read(buffer);
String content = new String(buffer.array(), 0, len);
System.out.println(content);
}catch (Exception e) {
}
}
}一个高性能、异步事件驱动的NIO框架。
使用更高效的socket底层,处理了epoll空轮询引起的cpu占用飙升(Netty检测到空轮询的时候,主动重建Selector)
采用decoder/encoder 支持,对TCP粘包/拆包进行自动化处理
可配置IO线程数、TCP参数,TCP 接收和发送缓冲区使用直接内存代替堆内存,通过内存池的方式循环利用 ByteBuf
通过引用计数器及时申请释放不再引用的对象,降低了 GC 频率
使用单线程串行化的方式,高效的 Reactor 线程模型
大量使用了 volitale、使用了 CAS 和原子类、线程安全类的使用、读写锁的使用
Netty 通过 Reactor 线程模型基于多路复用器接收并处理用户请求,内部实现了两个线程池,boss 线程池和 work 线程池,
其中 boss 线程池的线程负责处理请求的 accept 事件,当接收到 accept 事件的请求时,把对应的 socket 封装到一个,
NioSocketChannel 中,并交给 work线程池,其中 work 线程池负责请求的 read 和 write 事件,由对应的 Handler 处理。
-
Direct Buffer
使用堆外内存进行Socket读写,避免JVM堆内存与内核缓存之间的拷贝
-
CompositeByteBuf
将多个缓存区“逻辑上”合并为一个缓冲区,避免物理上的数据拷贝
- 文件传输的零拷贝 FileRegion
利用操作系统的 sendfile 系统调用,直接在文件描述符和套接字之间传输数据,完全跳过用户态
- 缓存区包装 Wrapped Buffers
通过包装现有数据(如字节数组、ByteBuffer)创建 ByteBuf,避免数据贝,Unpooled.wrappedBuffer(...) 方法直接引用原始数据,而非复制
- 延迟缓冲区拷贝 Lazy Copy
在某些场景下(如缓冲区切片 slice()),仅记录原始缓冲区的引用和偏移量,不立即拷贝数据,直到必要时才执行拷贝。