目录
1、概述
1.1、什么是 Netty?
Netty 是由 Trustin Lee 提供的一个 Java 开源框架,现为 GitHub 上的独立项目。Netty 是一个基于 NIO 的客户、服务器端的编程框架。
Netty 是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端。
1.3、Netty 的地位
Netty 在 Java 网络应用框架中的地位就好比:Spring 框架在 JavaEE 开发中的地位。
以下框架都是用了 Netty ,它们都有网络通信需求!
- Cassandra - NoSQL 数据库
- Spark - 大数据分布式计算框架
- Hadoop - 大数据分布式存储框架
- RockMQ - 阿里开源的消息队列
- ElasticSearch - 搜索引擎
- gRPC - rpc 框架
- Dubbo - rpc 框架
- Spring 5.x - Fulx api 完全抛弃了 Tomcat,使用 Netty 作为服务端
- Zookeeper - 分布式协调框架
1.4、Netty 的优势
- Netty 对比 NIO(工作量大,BUG多)
- 需要自己构建协议
- 解决 TCP 传输问题,如黏包、半包
- epoll 空轮询导致 CPU 100%
- 对 API 进行增强,使之更易使用。如 FastThreadLocal => ThreadLocal、ByteBuf=>ByteBuffer。
- Netty 对比 其他网络应用框架
- Mina 由 Apache 维护。Netty API 更简洁,文档更优秀。
2、Hello World
2.1、初体验
开发一个简单的服务器和客户端
- 客户端向服务器发送 hello,world
- 服务器仅接收,不返回
2.2、导入依赖
1、netty 依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.42.Final</version>
</dependency>
2、日志依赖:
<!-- lombok,IDEA 需要配置插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<!-- slf4j 到 log4j 的实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.9.1</version>
</dependency>
3、log4j2.xml
文件配置
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > debugModeOpen > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="INFO" monitorInterval="30">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<!-- <PatternLayout pattern="[%d{MM-dd HH:mm:ss:SSS}] [%p] - %l - %m%n"/> -->
<PatternLayout pattern="%date{HH:mm:ss} [%-5level] [%thread] %logger{17} - %m%n"/>
</console>
<!--日志大于50M时,自动备份存档,生成新的文件-->
<!-- <RollingFile name="logfile" fileName="${sys:catalina.home}/logs/system.log" filePattern="${sys:catalina.home}/logs/$${date:yyyy-MM}/system-%d{yyyy-MM-dd}-%i.log">-->
<RollingFile name="logfile" fileName="G:/Typora/Netty/Netty/logs/system.log" filePattern="G:/Typora/Netty/Netty/logs/$${date:yyyy-MM}/system-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%date{HH:mm:ss} [%-5level] [%thread] %logger{17} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="50 MB"/>
</Policies>
<!--最多10个日志文件-->
<DefaultRolloverStrategy compressionLevel="0" max="10"/>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<!-- <loggers>
<!–过滤掉spring、apache和druid的一些无用的DEBUG信息–>
<logger name="org.springframework" level="ERROR"> </logger>
<logger name="com.alibaba.druid" level="ERROR"> </logger>
<logger name="org.apache" level="ERROR"> </logger>
<!–vm和jsp两种视图同时存在时,如果找不到vm,就会去找jsp文件。但是这里老是会打印error日志–>
<logger name="org.apache.velocity" level="FATAL"> </logger>
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="logfile"/>
</root>
</loggers>-->
<loggers>
<!-- com.example 包下打印日志 -->
<logger name="com.example" level="DEBUG" additivity="false">
<appender-ref ref="Console" />
</logger>
<root level="ERROR">
<appender-ref ref="Console" />
</root>
</loggers>
</configuration>
2.3、服务器端代码
public class HelloServer {
public static void main(String[] args) {
// 1、服务端启动器,负责组装 netty 组件,启动服务器
new ServerBootstrap()
// 2、BossEventLoop WorkerEventLoop(包含selector, Thread), group 组
.group(new NioEventLoopGroup())
// 3、选择 服务器的 ServerSocketChannel 实现 (EpollServerSocketChannel、OioServerSocketChannel)
.channel(NioServerSocketChannel.class)
// 4、boss负责处理连接 worker(child)负责处理读写。决定 worker(child) 能执行哪些操作(handler)
.childHandler(
// 5、Channel 代表和客户端进行数据读写的通道;Initializer 初始化,负责添加别的 handler
new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
// 6、添加具体的 handler
// 将 ByteBuf 转换为字符串
nioSocketChannel.pipeline().addLast(new StringDecoder());
// 自定义 handler
nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override // 读事件
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 打印上一步转换好的字符串
System.out.println(msg);
}
});
}
})
// 7、绑定监听端口
.bind(8888);
}
}
2.4、客服端代码
public class HelloClient {
public static void main(String[] args) throws InterruptedException {
// 1、客户端启动器
new Bootstrap()
// 2、添加 EventLoop
.group(new NioEventLoopGroup())
// 3、选择客户端 Channel 事件
.channel(NioSocketChannel.class)
// 4、添加处理器,
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override // 连接建立后被调用
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
}
})
// 5、连接到服务器
.connect(new InetSocketAddress("localhost", 8888))
.sync()
.channel()
// 6、向服务器发送数据
.writeAndFlush("hello, world");
}
}
2.5、执行流程图
2.6、一些理解
- 把 Channel 理解为数据的通道。
- 把 msg 理解为流动的数据,最开始输入 ByteBuf,但经过 pipeline(流水线) 的加工,会变成其他类型对象,最后输出又变成 ByteBuf。
- 把 handler 理解为数据处理的处理工序
- 工序有多道,合在一起就是 pipeline(流水线),pipeline 负责发布事件(读、读取完成…)传播给每个 handler,handler 对自己感兴趣的事件进行处理(重写了相应事件处理方法)
- handler 分 Inbound(入站) 和 Outbound(出站) 两类
- 把 EventLoop 理解为处理数据的工人
- 工人可以管理多个 Channel 的 IO 操作,并且一旦工人负责了某个 Channel,就要负责到底(绑定)。
- 工人既可以执行 IO 操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个 Channel 的待处理任务,任务分为普通任务、定时任务。
- 工人按照 pipeline 顺序,依次按照 handler 的规则(代码)处理数据。可以为每道工序指定不同的工人(非 IO 操作)。