重要概念
Channel
channel是Netty的核心概念之一,它是Netty网络通信的主体,由它负责同对端进行网络通信、注册和数据操作等功能。
每当Netty建立了一个连接之后,都会有一个对应的channel实例,并且还有父子channel的概念,服务器连接监听的channel ,也叫 parent channel。 对应于每一个 Socket 连接的channel,也叫 child channel。
在服务端中,通常有两个线程组,监听器连接的父channel工作在一个独立的线程组,而客户端连接成功后所建立的子channel工作在另一个线程组中。
channel的类型
- NioSocketChannel, 代表异步的客户端 TCP Socket 连接.
- NioServerSocketChannel, 异步的服务器端 TCP Socket 连接.
- NioDatagramChannel, 异步的 UDP 连接
- NioSctpChannel, 异步的客户端 Sctp 连接.
- NioSctpServerChannel, 异步的 Sctp 服务器端连接.
- OioSocketChannel, 同步的客户端 TCP Socket 连接.
- OioServerSocketChannel, 同步的服务器端 TCP Socket 连接.
- OioDatagramChannel, 同步的 UDP 连接
- OioSctpChannel, 同步的 Sctp 服务器端连接.
- OioSctpServerChannel, 同步的客户端 TCP Socket 连接.
EventLoop
多个channel可以注册到一个eventloop上,所有的操作都是顺序执行的,eventloop会依据channel的事件调用channel的方法进行相关操作,每个channel的操作和处理在eventloop中都是顺序的。
在Netty 中,每一个 channel 绑定了一个thread 线程。
一个 thread 线程,封装到一个 EventLoop , 多个EventLoop ,组成一个线程组 EventLoopGroup。
EventLoop 这个相当于一个处理线程,是Netty接收请求和处理IO请求的线程。 EventLoopGroup 可以理解为将多个EventLoop进行分组管理的一个类,是EventLoop的一个组。
Bootstrap
Bootstrap是Netty官方提供的一个工厂类,使用Bootstrap可以简单快速的完成Netty客户端或者服务端的初始化。如果不使用这个启动器的话就需要自己手动去创建channel以及其他设置和启动,比较复杂和困难。
Bootstrap有两种,一个是Bootstrap(客户端专用)和ServerBootstrap(服务端专用),他们都继承AbstractBootstrap。
Bootstrap流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LoG5klkr-1642490912772)(Netty%E4%B8%AD%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5_md_files/image_20220118123929.png?v=1&type=image&token=V1:HxuZQTdFjhMRHA2sTV0D83e1eAd-gnjLzFQU7Eqy2DA)]
首先创建一个Bootstrap实例:
ServerBootstrap b = new ServerBootstrap();
//还有声明两个线程组用于创建Reactor线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
-
bossLoopGroup 表示服务器连接监听线程组,专门接受新的客户端连接
-
workerGroup 表示处理每一条连接的数据收发的线程组
然后设置Reactor线程组
(关于Reactor模式,可以参考此文章:https://www.jianshu.com/p/eef7ebe28673)
b.group(bossGroup, workerGroup)
//也可以不设置两个线程组,只设置一个线程组。
//如果只设置一个线程组,具体的方法为
b.group( workerGroup)
配置完成一个线程组,则所有的 channel ,包括服务监听通道父亲channel 和所有的子channel ,都工作在同一个线程组中。
设置通道的IO类型
Netty支持多钟IO方式,在这里配置的是NIO,因为NIO在性能上通常有更好的优势。
注:Netty中的BIO叫做OIO
b.channel(NioServerSocketChannel.class)
设置通道参数
// TCP默认开启了 Nagle 算法,该算法的作用是尽可能的发送大数据快,减少网络传输。TCP_NODELAY 参数的作用就是控制是否启用 Nagle 算法。
b.childOption(ChannelOption.TCP_NODELAY, true)
// 是否开启 TCP 底层心跳机制
b.childOption(ChannelOption.SO_KEEPALIVE, true)
//表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,可以适当调大这个参数
b.option(ChannelOption.SO_BACKLOG, 128)
这里用到了childOption和option,两者的区别为:
childOption是给child channel进行相关的设置;
option是给parent channel进行相关的设置。
关于option参数
SO_RCVBUF ,SO_SNDBUF
设置TCP连接的两个buffer尺寸的。
SO_SNDBUF:TCP发送缓冲区的容量上限;
SO_RCVBUF:TCP接受缓冲区的容量上限;
每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区,TCP的全双工的工作模式以及TCP的滑动窗口
便是依赖于这两个独立的buffer以及此buffer的填充状态。
TCP_NODELAY
TCP参数,立即发送数据,默认值为Ture(Netty默认为True而操作系统默认为False)。
该值设置Nagle算法的启用,改算法将小的碎片数据连接成更大的报文来最小化所发送的报文的数
量,如果需要发送一些较小的报文,则需要禁用该算法。Netty默认禁用该算法,从而最小化报文传
输延时。
Nagle:该算法要求一个TCP连接上最多只能有一个未被确认的小分组,在该小分组的确认到来之
前,不能发送其他小分组。
这个参数,与是否开启Nagle算法是反着来的,true表示关闭,false表示开启。
SO_KEEPALIVE
对于面向连接的TCP socket,在实际应用中通常都要检测对端是否处于连接中,连接端口分两种情
况:
1、连接正常关闭,调用close() shutdown()连接优雅关闭,send与recv立马返回错误,select
返回SOCK_ERR;
2、连接的对端异常关闭,比如网络断掉,突然断电.
对于第二种情况,判断连接是否断开的方法有一下几种:
1、自己编写心跳包程序,简单的说就是自己的程序加入一条线程,定时向对端发送数据包,查看是否
有ACK,根据ACK的返回情况来管理连接。此方法比较通用,一般使用业务层心跳处理,灵活可控,但
改变了现有的协议;
2、使用TCP的keepalive机制,UNIX网络编程不推荐使用SO_KEEPALIVE来做心跳检测。
底层TCP协议的心跳机制。Socket参数,连接保活,默认值为False。启用该功能时,TCP会主动
探测空闲连接的有效性。可以将此功能视为TCP的心跳机制,需要注意的是:默认的心跳间隔是
7200s即2小时。Netty默认关闭该功能。
SO_REUSEADDR
当两个socket的address和port相冲突,而你又想重用地 址和端口,则旧的socket和新的
socket都要已经被设置了SO_REUSEADDR特性
Socket参数,地址复用,默认值False。以下可以使用:
(1)当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你希望启动的程序的
socket2要占用该地址和端口,比如重启服务且保持先前端口。
SO_LINGER
简单的说就是SO_LINGER选项用来设置延迟关闭的时间,等待套接字发送缓冲区中的数据发送完
成。没有设置该选项时,在调用close()后,在发送完FIN后会立即进行一些清理工作并返回。如果
设置了SO_LINGER选项,并且等待时间为正值,则在清理之前会等待一段时间。
SO_BACKLOG
Socket参数,服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝。默认值,
Windows为200,其他为128。
表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理
创建新连接较慢,可以适当调大这个参数.
SO_BROADCAST
Socket参数,设置广播模式。
装配流水
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcRequest.class));
ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcResponse.class));
ch.pipeline().addLast(new NettyServerHandler());
}
ChannelPipeline 这是Netty处理请求的责任链,这是一个ChannelHandler的链表,而ChannelHandler就是用来处理网络请求的内容的。
每一个channel ,都有一个处理器流水线。装配 child channel 流水线,调用 childHandler()方法,传递一个ChannelInitializer 的实例。
在 child channel 创建成功,开始通道初始化的时候,在bootstrap启动器中配置的 ChannelInitializer 实例就会被调用。
这个时候,才真正的执行去执行 initChannel 初始化方法,开始通道流水线装配。
流水线装配,主要是在流水线pipeline 的后面,增加负责数据读写、处理业务逻辑的handler。
绑定Server和port
// 绑定端口,阻塞等待绑定成功
ChannelFuture f = b.bind(port).sync();
Futrue多线程
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
ChannelFuture在Netty中的方法都是异步调用的,所以需要一个ChannelFuture实例来获取当前的IO状态,当IO返回之后channel关闭。
关闭EventLoopGroup,回收资源
finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
关闭所有的child channel,释放掉底层的资源,如TCP Socket 文件描述符,等等。
由于才疏学浅,难免有错漏之处,还望不吝赐教。
本文地址转载请注明:https://blog.csdn.net/elapse_/article/details/122561174
Reference
Netty学习四:https://www.cnblogs.com/TomSnail/p/6192885.html
Netty Bootstrap:https://www.cnblogs.com/crazymakercircle/p/9998643.html