前言

  从学习过BIO、NIO、AIO编程之后,就能很清楚Netty编程的优势,为什么选择Netty,而不是传统的NIO编程。本片博文是Netty的一个入门级别的教程,同时结合时序图与源码分析,以便对Netty编程有更深的理解。

  在此博文前,可以先学习了解前几篇博文:

  参考资料《Netty In Action》、《Netty权威指南》(有需要的小伙伴可以评论或者私信我)

  博文中所有的代码都已上传到Github,欢迎Star、Fork

一、服务端创建

Netty屏蔽了NIO通信的底层细节,减少了开发成本,降低了难度。ServerBootstrap可以方便地创建Netty的服务端

1.服务端代码示例

public void bind (int port) throws Exception {
// NIO 线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup(); try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
// Java序列化编解码 ObjectDecoder ObjectEncoder
// ObjectDecoder对POJO对象解码,有多个构造函数,支持不同的ClassResolver,所以使用weakCachingConcurrentResolver
// 创建线程安全的WeakReferenceMap对类加载器进行缓存SubReqServer
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 半包处理 ProtobufVarint32FrameDecoder
socketChannel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
// 添加ProtobufDecoder解码器,需要解码的目标类是SubscribeReq
socketChannel.pipeline().addLast(
new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance()));
socketChannel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
socketChannel.pipeline().addLast(new ProtobufEncoder());
socketChannel.pipeline().addLast(new SubReqServerHandler());
}
});
// 绑定端口,同步等待成功
ChannelFuture f = bootstrap.bind(port).sync();
// 等待所有服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully(); } }

2.服务端时序图

(1)创建ServerBootstrap实例

  是Netty服务端启动的辅助类,提供了一系列的方法用于设置服务端自动相关参数,降低开发难度;

(2)设置并绑定Reactor线程池

  Netty的Reactor线程池(I/O 复用 + 线程池)是EventLoopGroup,实际上就是EventLoop数组,EventLoop处理所有注册到本线程的多路复用器Selector上的Channel,Selector的轮询操作由绑定的EventLoop线程run方法驱动,在一个循环体内循环执行。EventLoop不仅执行I/O事件,也能执行用户自定义的Task和定时任务Task

(3)设置并绑定服务端Channel

需要创建ServerSocketChannel,对应的实现类就是NioServerSocketChannel。ServerBootstrap的channel方法用于指定服务端的Channel类型

通过反射创建NioServerSocketChannel对象

通过调用无参默认的构造方法生成channel

(4)创建并初始化ChannelPipeline

  本质上是一个负责处理网络事件的职责链,负责管理与执行ChannelHandler。 ChannelPipeline为ChannelHandler链提供了容器,并定义了用于在该链上传播入站和出站事件流的API。当Channel被创建时,他会自动的分配到它专属的ChannelPipeline。典型的网络事件包括:

  • 链路注册
  • 链路激活
  • 链路断开
  • 接收到请求消息
  • 处理请求消息
  • 发送应答消息
  • 链路发生异常
  • 发送用户自定义事件

(5)添加ChannelHandler

  这是Netty提供给用户定制与扩展的关键接口,利用此可以完成大部分的功能定制。如:码流日志打印LoggingHandler、基于长度的半包解码器LengthFiledBasedFrameDecoder...

(6)绑定并启动监听端口

  将ServerSocketChannel注册到Selector上监听客户端连接

(7)Selector轮询

  由Reactor线程NioEventLoop负责调度和执行Selector轮询操作,选择准备好就绪的Channel集合。

(8)调度执行ChannelHandler

  当轮询到准备就绪的Channel之后,就由Reactor线程NioEventLoop执行ChannelPipeline的相应方法,最终调度并执行ChannelHandler。

    

(9)执行网络事件ChannelHandler

  执行用户自定义的ChannelHandler或系统ChannelHandler,ChannelPipeline会根据事件类型,调度并执行ChannelHandler。

3.服务端源码分析

(1)创建NioEventLoopGroup线程组

 首先通过构造函数创建ServerBootstrap实例,随后创建两个EventLoopGroup:

   

  NioEventLoopGroup其实就是Reactor线程池,负责调度和执行客户端接入、网络读写事件,用户自定义任务和定时任务的执行,通过ServerBootstrap的group方法传入

 其中父NioEventLoopGroup被传入父构造函数中

 该方法主要是处理各种设置I/O线程、执行和调度网络事件的读写。

(2)创建NioServerSocketChannel

  线程组设置完成后,需要创建NioServerSocketChannel。根据Channel的类型(channelClass)通过反射创建Channel实例(调用newInstance()方法)

(3)设置TCP参数

作为服务端主要是设置TCP backlog参数:

int listen(int sockfd, int backlog);

  backlog指定了内核为此套接口排队的最大连接个数。在服务端要接收多个客户端发起的连接,因此必不可少要使用队列来管理这些连接。其中在TCP三次握手中有两个队列,分别是半连接状态队列和全连接队列

  • 半连接状态队列:每个客户端发来的SYN报文,服务器都会把这个报文放到队列里管理,这个队列就是半连接队列,即SYN队列,此时服务器端口处于SYN_RCVD状态。之后服务器会向客户端发送SYN+ACK报文。
  • 全连接状态队列:当服务器接收到客户端的ACK报文后,就会将上述半连接队列里面对应的报文转移(注:其实不是同一个结构,会新建一个结构挂到全连接队列里)到另一个队列里管理,这个队列就是全连接队列,即ACCEPT队列,此时服务器端口处于ESTABLISHED状态。

放一张来自网络的图:

  backlog被规定为两个队列总和的最大值,Netty默认的目的backlog为200

(4)为启动辅助类和其父类分别设置Handler

  childHandler是NioServerSocketChannel对应ChannelPipeline的Handler;父类中的Handler是客户端新接入的连接SocketChannel对应的ChannelPipeline的Handler

  本质区别就是:ServerBootstrap中的Handler是NioServerSocketChannel使用的,所有连接该监听端口的客户端都会执行它;父类AbstractBootstrap中的Handler是个工厂类,会为每个新接入的客户端创建一个新的Handler

二、客户端创建

1.客户端代码示例

public void connect (String host, int port) throws Exception {
// NIO 线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 处理半包的ProtobufVarint32FrameDecoder一定要在解码器前面
socketChannel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
// 添加ProtobufDecoder解码器,需要解码的目标类是SubscribeResp
socketChannel.pipeline().addLast(
new ProtobufDecoder(SubscribeRespProto.SubscribeResp.getDefaultInstance()));
socketChannel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
socketChannel.pipeline().addLast(new ProtobufEncoder());
socketChannel.pipeline().addLast(new SubReqClientHandler()); }
}); // 发起异步连接操作
ChannelFuture f = bootstrap.connect(host, port).sync();
// 等待所有服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
group.shutdownGracefully(); } }

负责处理网络读写、连接和客户端请求接入的Reactor线程就是NioEventLoop。

2.客户端时序图

(1)创建Bootstrap实例

(2)创建客户端连接,创建线程组NioEventLoopGroup(线程数默认为CPU内核数2倍)

(3)通过ChannelFactory工厂和指定的NioSocketChannel.class类型创建用于客户端连接的NioSocketChannel;

(4)创建默认的ChannelHandlerPipeline,用于调度与执行网络事件;

(5)异步发起TCP连接,判断连接结果,如果成功则将NioSocketChannel注册到Selector上并置selectionKey为OP_READ,监听读操作,如果没有立即成功,则可能是服务端还没有立刻返回ACK,所以此时将连接监听位注册到Selector上,同时selectionKey为OP_CONNECT,监听连接,等待结果;

(6)注册对应的监听状态位到Selector上;

(7)Selector轮询各NioSocketChannel,处理连接结果;

(8)如果连接成功则发送成功事件,触发ChannelPipeline执行;

(9)由ChannelPipeline调度执行ChannelHandler(包括系统与用户自定义),执行具体业务逻辑。

3.客户端源码分析

(1)客户端连接辅助类Bootstrap

Bootstrap是Netty提供的客户端连接工具类,用于简化客户端的创建

1)设置I/O线程组:

客户端相对于服务端,只需要一个处理I/O读写的线程组即可。由Bootstrap的group方法提供,主要设置EventLoopGroup:

2)设置TCP参数

创建客户端套接字的时候通常都会设置连接参数:接收和发送缓冲区大小、连接超时时间等。

主要的TCP参数如下:

3)指定Channel

对于TCP客户端连接,默认使用NioSocketChannel,创建过程跟服务端是大同小异的。

4)发起客户端连接

具体请看下面

(2)客户端连接操作

1)创建初始化NioSocketChannel,主要逻辑是initAndRegister方法

  

2)注册到Selector上,主要逻辑是register方法

3)链路成功后发起TCP连接

先获取EventLoop线程组

然后进入doConnect()方法,调用NioSocketChannel异步发起connection

Connect操作后有三种可能:

第一是连接成功

第二种是暂时没连接上,服务端没有返回ACK,结果暂时不确定,这时候需要将selectionKey设置为OP_CONNET,监听连接结果。

第三种是连接失败,直接抛出异常

异步连接成功以后,调用fulfillConnectPromise方法,触发链路激活事件,如果连接成功则触发ChannelActive事件

此时ChannelActive事件的主要作用就是将selectionKey设置为OP_READ事件

(3)异步连接结果通知

调用processSelectedKey方法,Selector轮询客户端连接Channel

当服务端返回握手应答以后,对连接结果进行判断,主要调用finishConnect方法

进入finishConnect方法:

doFinishConnect方法主要判断JDK的SocketChannel连接结果

连接成功后进入fullfillConnectPromise方法,调用fulfillConnectPromise方法,触发链路激活事件,如果连接成功则触发ChannelActive事件:

(4)客户端连接超时机制

JDK没有提供连接超时机制,Netty利用定时器提供客户端连接超时控制

在option方法中传入TCP超时配置

一旦定时器执行超时,说明客户端连接超时,这时候就构造超时异常,同时关闭客户端连接,释放句柄

  如果连接超时被设置,但是定时器执行的时候并没有超时执行(在超时时间内完成),则此时connectedTimeoutFuture是不会为null的,根据此判断是否在超时时间内完成,如果完成则取消,避免再次触发定时器,实际上不管连接成功与否,只要获取到连接结果,都会删除定时器。

三、选择Netty的好处

之所以选择Netty编程,主要Netty的以下几种优势:

(1)API使用简单,开发门槛低

(2)功能强大,预置了很多编解码功能,支持多种主流协议

(3)定制能力强,可以通过ChannelHandler对通信框架进行灵活扩展

(4)性能高

(5)成熟、稳定,修复了已知所有的JDK NIO BUG

(6)社区活跃

(7)经过了大规模的商业应用考验

当然,这些是显而易见的优势,但是需要从源码中分析其优势,比如Netty的零拷贝、基于内存池的ByteBuf、高性能的序列化框架等。

深入学习Netty(4)——Netty编程入门的更多相关文章

  1. HttpClient学习之 客户端HTTP编程入门

    说明 本文存在的原因,是想深入的学习下HttpClient.对应的网址是: http://hc.apache.org/httpcomponents-client-4.5.x/primer.html h ...

  2. python编程学习--Pygame - Python游戏编程入门(0)---转载

    原文地址:https://www.cnblogs.com/wuzhanpeng/p/4261015.html 引言 博客刚开,想把最近学习的东西记录下来,算是一种笔记.最近打算开始学习Python,因 ...

  3. Linux学习 : Socket 网络编程入门

    一.socket()函数 int socket(int domain, int type, int protocol); domain:即协议域,又称为协议族(family).常用的协议族有,AF_I ...

  4. DotNetty网络通信框架学习之初识Netty

    p{ text-align:center; } blockquote > p > span{ text-align:center; font-size: 18px; color: #ff0 ...

  5. Netty学习(一)-为什么选择Netty

    前面我们简单学习了NIO.我们知道java的I/O模型一共有四种,分别是:传统的BIO,伪异步I/O,NIO和AIO.为了澄清概念和分清区别,我们还是先简单的介绍一下他们的概念,然后再去比较优劣.以及 ...

  6. 3000字编程入门--附带Java学习路线及视频

    Title: 编程入门 GitHub: BenCoper Reference: 尚硅谷-2019 Study: 文字版+视频+实战(第一个自学的网站) Explain: 文末附带Java学习视频以及项 ...

  7. Python编程入门(第3版)|百度网盘免费下载|零基础入门学习资料

    百度网盘免费下载:Python编程入门(第3版) 提取码:rsd7 目录  · · · · · · 第1章 编程简介 11.1 Python语言 21.2 Python适合用于做什么 31.3 程序员 ...

  8. 【Netty】Netty入门之WebSocket小例子

    服务端: 引入Netty依赖 <!-- netty --> <dependency> <groupId>io.netty</groupId> <a ...

  9. 大数据学习day18----第三阶段spark01--------0.前言(分布式运算框架的核心思想,MR与Spark的比较,spark可以怎么运行,spark提交到spark集群的方式)1. spark(standalone模式)的安装 2. Spark各个角色的功能 3.SparkShell的使用,spark编程入门(wordcount案例)

    0.前言 0.1  分布式运算框架的核心思想(此处以MR运行在yarn上为例)  提交job时,resourcemanager(图中写成了master)会根据数据的量以及工作的复杂度,解析工作量,从而 ...

  10. 脑残式网络编程入门(六):什么是公网IP和内网IP?NAT转换又是什么鬼?

    本文引用了“帅地”发表于公众号苦逼的码农的技术分享. 1.引言 搞网络通信应用开发的程序员,可能会经常听到外网IP(即互联网IP地址)和内网IP(即局域网IP地址),但他们的区别是什么?又有什么关系呢 ...

随机推荐

  1. 07.ElementUI 2.X 源码学习:源码剖析之工程化(二)

    0x.00 前言 项目工程化系列文章链接如下,推荐按照顺序阅读文章 . 1️⃣ 源码剖析之工程化(一):项目概览.package.json.npm script 2️⃣ 源码剖析之工程化(二):项目构 ...

  2. 2021.5.22 noip模拟1

    这场考试考得很烂 连暴力都没打好 只拿了25分,,,,,,,,好好总结 T1序列 A. 序列 题目描述 HZ每周一都要举行升旗仪式,国旗班会站成一整列整齐的向前行进. 郭神作为摄像师想要选取其中一段照 ...

  3. 优雅关闭springboot应用

    1.添加钩子函数,钩子函数中指定要调用的方法 @PostConstruct public void run() { this.zkClient.start(this); this.schedulerS ...

  4. 夜晚场景图像ISP增强算法

    夜晚场景图像ISP增强算法 输入输出接口 Input: (1)图像视频分辨率(整型int) (2)图像视频格式(RGB,YUV,MP4等) (3)摄像头标定参数(中心位置(x,y)和5个畸变 系数(2 ...

  5. NVIDIA GPU自动调度神经网络

    NVIDIA GPU自动调度神经网络 对特定设备和工作负载进行自动调整对于获得最佳性能至关重要.这是有关如何使用自动调度器为NVIDIA GPU调整整个神经网络. 为了自动调整神经网络,将网络划分为小 ...

  6. MindSpore特性支持类

    MindSpore特性支持类 Q:请问MindSpore支持梯度截断吗? A:支持,可以参考梯度截断的定义和使用. Q:如何在训练神经网络过程中对计算损失的超参数进行改变? A:暂时还未有这样的功能. ...

  7. TensorRT 7.2.1 开发概要(下)

    TensorRT 7.2.1 开发概要(下) 1.2. Where Does TensorRT Fit? 一般来说,开发和部署深度学习模型的工作流要经过三个阶段. Phase 1 is trainin ...

  8. https://www.jianshu.com/writer#/notebooks/164311/notes/88906048/preview

    什么是 webassembly 在 2019 年 12 月之前,如果你要编写一个web页面,那一定离不开 html.css.js 这三个好兄弟.在 2019 年 12 月之后 W3C 宣布 webas ...

  9. 重新整理 mysql 基础篇————— 介绍mysql日志[二]

    前言 对于后端开发来说,打交道最多的应该是数据库了,因为你总得把东西存起来. 或是mongodb或者redis又或是mysql.然后你发现一个问题,就是他们都有日志系统,那么这些日志用来干什么的呢? ...

  10. JVM系列(五):gc实现概要01

    java的一大核心特性,即是自动内存回收.这让一些人从繁琐的内存管理中解脱出来,但对大部分人来说,貌似这太理所当然了.因为现在市场上的语言,几乎都已经没有了还需要自己去管理内存这事.大家似乎都以为,语 ...