Netty详解-01
Netty
讲解常用io模型及相关概念(包括IO基本概念、BIO、NIO、Epoll、Redis单线程模型、Netty线程模型、Reactor模型)、netty基本概念、核心组件
IO
IO概念
阻塞IO/非阻塞IO
- 阻塞IO:发起IO后线程挂起,直到IO完成才能继续其他操作
- 非阻塞IO:发起IO后线程不挂起,可立即执行其他操作,无需等待IO完成
同步IO/异步IO
- 同步IO:IO操作需主动检查完成状态,且需自行读取/写入数据
- 异步IO:IO操作由操作系统后台完成,完成后主动推送数据并回调通知
BIO模型
java.io包
基于流模型实现
传统IO,同步并阻塞的IO模式
提供:
- File抽象
- 输入输出流等
BIO基础示例
BIO中,相当于一个线程只能处理单个连接,虽然我们可以让当前线程通过new其他线程去帮处理连接,但可能导致线程数量太多,虽然我们可以限定线程池,但是就会出现同样的性能不足问题,即当线程用完后,依旧阻塞
BIO线程切换频繁,且阻塞消耗大量资源
BIO就是死等,来连接后一定要当时就处理好,才去管其他连接
/*** BIO TCP 服务端(阻塞式)* 特点:单线程,一次只能处理一个客户端连接,处理完一个再接收下一个*/
public class BioTcpServer {public static void main(String[] args) {int port = 8888; // 监听端口try (// 1. 创建 ServerSocket,绑定端口ServerSocket serverSocket = new ServerSocket(port)) {System.out.println("BIO 服务端启动,监听端口:" + port);while (true) {// 2. 阻塞等待客户端连接(accept() 阻塞,直到有客户端连接)Socket clientSocket = serverSocket.accept();System.out.println("客户端连接成功:" + clientSocket.getInetAddress());// 3. 处理客户端请求(阻塞读写)handleClient(clientSocket);}} catch (IOException e) {e.printStackTrace();}}/*** 处理客户端通信(阻塞式读/写)*/private static void handleClient(Socket clientSocket) {try (// 获取客户端输入流(读客户端数据)BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));// 获取客户端输出流(向客户端写数据)BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {String msg;// 阻塞读取客户端消息(readLine() 阻塞,直到客户端发送数据)while ((msg = reader.readLine()) != null) {System.out.println("收到客户端消息:" + msg);// 向客户端回复消息(阻塞写入)String response = "服务端已收到:" + msg + "\n";writer.write(response);writer.flush(); // 刷新缓冲区,确保消息发送// 约定:客户端发送 "exit" 则断开连接if ("exit".equals(msg)) {System.out.println("客户端断开连接:" + clientSocket.getInetAddress());break;}}} catch (IOException e) {e.printStackTrace();} finally {try {clientSocket.close(); // 关闭客户端连接} catch (IOException e) {e.printStackTrace();}}}
}
NIO模型
java1.4引入,java.nio包
面向缓冲,基于通道
同步非阻塞的IO模式
一个线程处理多个通道channel
提供:
- Channel
- Selector
- Buffer等
基础NIO
基础NIO中,NIO不会阻塞等待连接,而是当连接到来后,如果没有数据,则不处理该连接,直接保存当前连接,然后就去管理下一个连接。后续再轮询处理前面保存的所有连接,轮到之前的连接且数据就位时,就可以处理该连接
但在基础NIO中,如果将所有的连接都存起来,后续再轮询,会导致很多可能并不需要io操作的连接被轮询,导致浪费
基础NIO就是再BIO基础上,不等,连接来了我无法处理就存起来,后面统一处理
到了这里,你可能会想到,如果数据已经就位了,但NIO直到下次轮循到对应的连接才能处理,这样的客户等待的事件似乎会比BIO长一点
然而:
BIO 中客户等待时间 = 「数据传输时间」 + 「线程调度时间」 + 「IO 阻塞浪费时间」(线程池满了,需要等待线程)
NIO 中客户等待时间 = 「数据传输时间」 + 「轮询等待时间」 + 「IO 处理时间」
IO阻塞不可控制,且调度消耗很大,因此,极大情况都会比BIO快
简而言之
IO操作是茶水
BIO是客户一来,我就去服务,即使茶水没好,我也等着;多个客人我就安排多个人去等茶水
NIO是客户一来,我先不管,我先让所有的客户坐着等会,然后等茶水,哪个客户的茶好了,我就去服务谁
NIO多路复用
由于基础NIO可能轮询所有连接(包括不需要io的连接,也就是不需要喝茶的客户),因此就出现了多路复用,也就是将刚刚都需要被处理的连接中,筛选出需要用到io操作的连接,将这些连接保存到另一个位置,后续轮询则只用处理这些io
NIO多路复用即在基础NIO基础上,只轮询需要io的连接,避免轮询不需要io的连接
如果你对两种连接不理解:
-
不需要io的连接:
微信:你和朋友聊天,发送一条消息后,连接不会关闭,而是保持空闲,直到你发下一条消息,空闲情况下就是不需要io的连接
-
需要io的连接(需要被NIO处理的):
一旦你发送信息,当前连接就是需要io的连接
因此,其实NIO轮询中的连接中,是会不断加入新连接,且对于空闲连接不进行轮询处理,但也不会踢出空闲连接,因为要确保该空闲连接突然有io请求到来,此时要能快速响应
Epoll事件轮询模型
Linux 操作系统的多路复用机制,是 NIO 在 Linux 下的底层实现之一
epoll 就是Linux下的 NIO 中 Selector(多路复用器)的 “底层发动机”,负责高效监控大量连接,筛选出 “有 IO 事件就绪” 的连接,让 NIO 不用轮询所有连接就能精准处理有效请求
Redis线程模型
更加极致的NIO
单线程 + IO多路复用
你是否回想那为什么java中不也极致的NIO,就一个单线程呢
因为redis基于内存,他快,而java可能调用各种数据库MySQL等,而且还要处理各种复杂的后端业务逻辑,因此处理速度较慢,一旦使用单线程,java项目的效率将会极低,因为一旦查询Mysql慢了几秒,那其他所有的请求都要等着
而redis内存操作很快,单线程还避免了线程切换开销,因此更适用
Netty线程模型
NIO可以单线程也可以多线程,像我们java后端可能接触到的Tomcat、netty就是基于多线程的NIO
Netty 线程模型的本质是通过 BossGroup 处理连接、WorkerGroup 处理 IO、业务线程池处理耗时逻辑,既保留了 NIO 的高效性,又解决了 Java 复杂业务的阻塞问题,成为 Java 高并发网络编程(如 IM、电商、网关)的首选框架
EventLoop中会提到BossGroup、WorkerGroup
简单来说,netty基于NIO,但如果我们直接和NIO打交道,来编写网络通信应用,则会很困难,而netty帮我们解决了很多问题,且帮我封装好了大量API,方便我们开发网络应用程序
例如:
- 原生 NIO 要手动处理:Selector 空轮询、缓冲区溢出、半包 / 粘包、断线重连等一堆细节问题,稍不注意就出 bug
- 原生 NIO 没有现成的编码解码、心跳检测、流量控制等常用功能,都要自己写
Reactor模型
“Reactor” 翻译过来是 “反应器”,核心作用是:监控所有连接的 IO 事件(新连接、读 / 写数据),然后分发任务给对应的处理逻辑
Reactor是设计模式,基于 Selector/epoll 这类多路复用技术,才得以落地实现高并发事件处理
指导如何用 NIO 构建高效线程模型
Reactor 模型根据「Reactor 数量」和「线程数量」,分3种变体:
- 单线程 Reactor 模型(最简单,对应原生 NIO 基础用法)
- 多线程 Reactor 模型(优化单线程瓶颈,对应原生 NIO 手动多线程)
- 主从 Reactor 模型(工业级最优解,对应 Netty 线程模型)
总结
BIO太拉,NIO是机制,Reactor是设计模式,redis、netty线程模型基于nio以及reactor的设计模式,epoll是linux下nio实现
Netty
概念
Netty是:NIO客户端服务器框架,用于快速轻松地开发网络应用程序(如协议服务器、客户端),简化网络编程(如TCP、UDP套接字服务器)
一套框架就能搞定 “服务端监听连接、客户端发起连接” 的全场景
套接字服务器:Netty 封装后的 “底层网络服务基座”,是基于 TCP/UDP 协议的 “通信入口”,负责监听端口、接收客户端连接、底层数据传输
协议服务器:在 “套接字服务器” 基础上,集成了特定应用层协议(Http、WebSocket等)解析能力的服务器
客户端:主动向服务器发起连接、遵循相同协议与服务器通信的 “终端程序”,是服务器的交互方
套接字服务器:相当于 “开通了电话线路”,能接打电话,但不知道对方说的是什么语言
协议服务器:相当于 “确定用中文交流”,接电话后能听懂对方说的话(解析 HTTP 请求),也能用中文回应(返回 HTTP 响应)
客户端(浏览器):相当于 “打电话的人”,用中文(HTTP 协议)通过电话线路(TCP)和对方交流
应用层协议
TCP
“面向连接、可靠传输” 的传输层协议(会保证数据不丢失、不重复、按顺序到达)
| 协议 | 核心用途 | 传输层依赖 | 通信方式(单向 / 双向) | 连接特性 | 核心区别 & 关键细节 |
|---|---|---|---|---|---|
| HTTP | 网页访问、API 接口调用 | TCP | 单向(客户端请求→服务器响应) | 原本短连接(HTTP/1.1 支持长连接 keep-alive) | 1. 文本协议(如 GET /api HTTP/1.1);2. 无状态(默认不存客户端上下文,需 Cookie/Session);3. 核心场景:前后端数据交互、文件下载(非大文件最优) |
| FTP | 大文件批量传输 | TCP | 双向(命令 + 数据双通道) | 长连接(全程保持连接) | 1. 双端口工作:21 端口传命令(如 “上传文件”)、20 端口传数据;2. 支持断点续传、文件权限管理;3. 核心场景:服务器文件批量同步、大文件传输(比 HTTP 更高效) |
| SMTP | 邮件传输 | TCP | 单向(发件服务器→收件服务器) | 长连接(传输完邮件才断开) | 1. 专门用于 “邮件发送”(接收邮件用 POP3/IMAP 协议);2. 支持附件、邮件头(收件人 / 主题)解析;3. 核心场景:邮箱客户端发邮件(如 Outlook 用 SMTP 发邮件) |
| WebSocket | 即时双向交互 | TCP | 双向(客户端↔服务器实时推送) | 长连接(一次握手后持续通信) | 1. 基于 HTTP 握手(先发送 HTTP 请求升级协议),后续用二进制帧传输;2. 低延迟(无重复请求头开销);3. 核心场景:聊天软件、实时数据推送(如股票行情、直播弹幕) |
- 前后端数据交互 → 用 HTTP
- 实时聊天 / 行情推送 → 用 WebSocket
- 服务器大文件同步 → 用 FTP
- 邮件发送功能 → 用 SMTP
UDP
“无连接、不可靠传输”(不保证数据到达,也不保证顺序),但 “低延迟、开销小” 适合对实时性要求远超可靠性的场景
| 应用层协议 | 核心用途 | 为什么选 UDP? |
|---|---|---|
| DNS | 域名解析(比如把 www.baidu.com 转成 IP) | 1. 解析请求 / 响应数据量极小(几十字节),就算丢包,客户端重试一次成本极低;2. 实时性要求高(打开网页前必须先解析域名,延迟要低) |
执行流程
EventLoop 监听 Channel 上的 I/O 事件(如数据可读、连接建立)
一旦事件触发,就会调度该 Channel 绑定的 Pipeline
按顺序执行链中的 Handler 处理事件 / 数据
核心组件
Channel
网络通信的 “通道”,封装了底层 Socket,是数据读写的载体
(比如 NioSocketChannel 对应 TCP 客户端通道,NioServerSocketChannel 对应 TCP 服务端通道)
EventLoop与EventLoopGroup
EventLoop:“事件循环线程”,是 Netty 的核心调度器,负责监听 Channel 上的 I/O 事件
(如连接建立、数据可读、数据可写),并调用对应的 Handler 处理
一个EventLoop可管理多个Channel,每个Channel都有一个EventLoop(1:n)
一个EventLoop绑定一个线程,保证线程安全(1:1)
EventLoopGroup:EventLoop池
分两组(主从 Reactor 模型):
BossGroup(主线程组):负责接收客户端连接,不处理业务WorkerGroup(从线程组):负责处理已连接 Channel 的 I/O 事件
ServerBootStrap与BootStrap
引导类
对应用程序进行配置并使应用程序运行起来
BootStrap:
客户端的引导类,调用bing()连接UDP,调用connect()连接TCP时创建一个单独的channel
服务端的 “启动助手”,用于配置服务端核心参数(线程组、Channel 类型、Pipeline、端口等),简化启动流程
ServerBootStrap:
服务端的引导类,调用bing()创建一个ServerChannel管理多个子Channel用于同客户端之间的通信
客户端的 “启动助手”,功能和 ServerBootstrap 类似,差异是客户端无需绑定端口,只需配置连接服务端的参数
ChannelHandler与ChannelPipeline
ChannelHandler:处理器
业务处理的 “逻辑单元”,分入站(处理读事件)和出站(处理写事件),是实际处理数据 / 事件的代码载体
ChannelPipeline:“处理器链”,是 Handler 的容器,按顺序存储多个 Handler(分为 ChannelInboundHandler 入站处理器、ChannelOutboundHandler 出站处理器)。数据读写时会按顺序经过 Pipeline 中的 Handler,实现 “分层处理”(如解码、业务逻辑、编码)
ChannelFuture
异步操作的 “结果占位符”,Netty 所有 I/O 操作(绑定、连接、读写)都是异步的,用它获取操作结果或添加回调
