netty焦点流程(二):客户端与服务端的读写过程

打印 上一主题 下一主题

主题 902|帖子 902|积分 2706

连接成功建立后,客户端是如何向服务端发送哀求的?

由于内部源码的调用过于复杂,我们只分析有代表性的代码。在 AbstractChannel 类中我们可以看到:

 
wirte() 方法末了会把发送的数据 msg 放入 addMessage() 方法中,这个方法是做什么的呢?

 
原来把要发送数据放入一个缓冲链表中,当要发送一个新的数据时,并不是直接把数据发送出去,而是会把这个数据放入一个叫做 unflushedEntry 缓冲链表的尾部,等待发送。
那么,真正发送时,肯定是要把发送数据从缓冲链表中拿出来的,我们再往下看。

 
flush() 方法中的 outboundBuffer.addFlush() 才是从缓冲链表中读取数据哀求的地方。
类 NioSocketChannel:

 
可以看到 addFlush() 方法会从 unflushedEntry 里面取出哀求数据,然后再把哀求数据放入 flushedEntry 缓冲链表里。也就是说,发送数据时需要两个链表缓冲来支撑。

 
末了一步就简单了,其实就是调用 Java NIO 底层写数据的操作。
我们通过流程图再回顾一下以上流程:

 
只看跟上述流程相干的左下脚的红字和虚线。客户端把要发送的哀求数据放入一个缓冲链表,然后拿到缓冲列表的元素来做具体的数据发送。
Netty 客户端是如何吸收服务端发来的相应的

我们照旧跟踪相干的代码:

 
走到这步其实就是调用我们在 demo 中本身定义的处理读的逻辑,代码如下:

 
好了,我们通过流程图再回顾一下以上流程:

 
只看跟上述流程相干红字和虚线。首先,服务端通过线程 NioEventLoop 把相应数据放入 SocketChannel 中,并发送给客户端。客户端线程 NioEventLoop 对应的 Selector 监听到 OP_READ 事件后,客户端对应的 NioEventLoop 会把相应数据读出来,然后再调用用户本身定义好的处理逻辑来处理读出来的数据。
Netty 工作流程及设计思想简介

Netty 的整个流程都具体地介绍完了。接下来给介绍一下 Netty 的工作流程及设计思想。首先讲解一下 Netty 工作流程。
Netty 工作流程图

前面已经具体地画了 Netty 处理网络事件的流程图。但是这些图比较具体复杂,倒霉于同砚们明白 Netty 内部组件的工作机制。以是,画了个可以或许更好体现的 Netty 工作机制的流程图,如下:

 
分为如下几步:

  • 服务端对外暴露端口,供客户端连接。
  • 客户端向服务端对应的接口发出连接的哀求。
  • 服务端收到客户端的连接哀求后,Boss 线程池会用一个线程去使用 ServerSocketChannel 完成三次握手,从而连接建立。
  • 建立完连接后再创建一个 SocketChannel 实例,这个 SocketChannel 实例就代表这个客户端和服务端的连接,以后全部的读写操作都会发生在这个 SocketChannel 实例里。
  • 连接建立后,把连接分配给 Worker 线程池中的一个线程,同时会把这个 SocketChannel 上的读事件注册到线程中的 Selector 上。
  • 当客户端向服务端发送数据后,Worker 线程中的 selector 会监听到读事件,然后 Worker 线程把数据读出来。
  • Worker 线程把读出来的数据交给自定义业务线程池中的一个线程处理业务逻辑。
  • 业务逻辑处理完后,业务线程把相应返还给 Worker 线程。
  • 最终,worker 线程把相应写入 SocketChannel 进而把相应发给客户端。
这里的 Boss 线程池和 Worker 线程池都是我们实例化的 EventLoopGroup,即事件轮询组。事件轮询组是来轮询处理网络事件的一组线程,不同的事件轮询组处理的网络事件有大概不同,比如 Boss 线程池专门用来处理连接事件的,而 worker 线程池是专门处理读写事件的。
也就是说,我们在 Netty 编程里,应该有三种线程。

  • Boss 线程:负责实现 TCP 三次握手,实现客户端和服务端的连接,并把创建好的连接交给 worker 线程。
  • Worker 线程:监听读写事件,并实现网络读写的线程,一个线程可以负责多个连接上的读写。
  • 自定义业务线程:主要工作是非网络 IO 的工作,比如数据的编码、解码、业务逻辑处理、访问数据库,等等。
下面讲解 Netty 编程时两个重要的参数 BackLog 和 KeepAlive,先看看这两参数是在那里配置的:

 
BackLog

要想讲清晰 BackLog 这个参数,我们需要重温 TCP 连接三次握手,如下图所示:

 
我解释一下执行步骤:

  • 客户端初始状态为 Closed,这时客户端会给服务端发送 syn 哀求来告知服务端有 TCP 连接哀求,并把客户端的状态改为 SYN-SENT。
  • 服务端收到客户端发来的 syn 哀求后,向客户端发送包含 syn 和 ack 的数据包,syn 表现服务端也要发起连接,ack 是确认已收到客户端发来的 syn 哀求,并把服务端的状态改为 SYN-RCVD,服务端会认为这时是半连接,并把半连接放入 syn_queue 库中。
  • 客户端收到服务端的 ack 确认后,会把本身的状态改为 ESTABLISHED,然后向服务端发送 ack 确认数据包。
  • 服务端收到 ack 确认后,这时服务端任务连接建立成功了,于是把 syn_queue 库中对应的半连接拿出来放入 accept_queue 中,这时执行 accept() 方法,成功后把连接从 accept_queue 库中删除。
BackLog 这个参数会影响到 accept_queue 里连接数量,accept_queue 表现的是已经建立好但还没有被上层代码获取连接数。也就是说,这个连接数不能太多,太多了会影响性能。
KeepAlive 参数的解释

接下来,我们来看一下 KeepAlive 参数是用来做什么用的。
其实,这是个保活参数,当 TCP 的两端长期没有数据交互的时候,有大概对方已经挂了,于是在世的一端会向另一端尝试发送 KeepAlive 保活哀求,假如有 ACK,那么我们认为连接正常;假如没有,我们就认为连接断了,并关闭还存活的这一端的 socket,这样可以或许节省系统的 TCP 连接资源。
但是,Netty 默认并没有打开 KeepAlive,这是为什么呢?主要有下面几个原因:

  • 偶然候会出现短暂的连接故障,而探活会把连接误杀。
  • 假如一个设备有大量的 TCP 连接,但是不活跃的 TCP 连接特别多的话,探活哀求就会浪费大量的带宽。
TCP 的 KeepAlive 和 HTTP 的 Keep-Alive 是什么关系?
答案是没有任何关系。HTTP 的 Keep-Alive 设置为 true 后表现一个哀求相应事后,HTTP 不会关闭 TCP 连接,这个 TCP 连接会被后面的哀求相应复用。而 TCP 的 KeepAlive 则是前面说的 TCP 探活。
总结

本章讲解了连接成功建立后,客户端是如何向服务端发送哀求的,以及 Netty 客户端是如何吸收服务端发来的相应的。讲解完流程后,又简单介绍了 Netty 的设计思想,包括 Netty 流程图。末了,讲解了两个 Netty 上重要的参数 BackLog 与 KeepAlive。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

络腮胡菲菲

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表