从epoll事件的视角探讨TCP:三次握手、四次挥手、应用层与传输层之间的联系

devtools/2025/1/14 16:46:14/

目录

一、应用层与TCP之间的联系

二、 当通信双方中的一方如客户端主动断开连接时,仅是在客户端的视角下连接已经断开,在服务端的眼中,连接依然存在,为什么?——触发EPOLLRDHUP事件:对端关闭连接或停止写操作

 三、EPOLLIN:可读事件 和 EPOLLOUT:可写事件 的触发时机,什么时候设置读事件关心?什么时候设置写事件关心?

四、EPOLLPRI:优先处理数据事件 —— 当TCP报文携带的是紧急数据时触发(URG = 1, 紧急指针有效)


我们来回顾使用epoll的流程:

1、首先,创建监听套接字,将其设置为读事件关心并挂载到epoll的红黑树中。监听套接字读取事件就绪的条件是:TCP全连接队列不为空,在LT模式下,只要全连接队列中有数据就满足读时间就绪;在ET模式下,只有当全连接队列中有新连接到来时,才会触发一次读时间就绪。

2、当触发监听套接字读时间就绪后,使用epoll_wait从epoll模型的就绪队列中将就绪事件及其对应的套接字提取出来,根据套接字类型的不同和就绪事件的不同进而执行不同的回调函数。对于listen套接字而言,当读事件就绪后,它需要调用accept从TCP全连接队列中获取新连接的文件描述符。并将其挂载到epoll中,设置读事件关心。

3、当该连接接收到请求数据后,进行业务处理——根据请求构建响应数据放入该连接的用户级缓冲区中,并设置该连接的文件描述符为写事件关心,等待写事件的就绪。当epoll_wait获取到该连接的写事件就绪后再去执行写事件对应的回调函数。当写入完成后,需要关闭该连接的对写事件的关心。

4、当客户端主动关闭连接时,服务端需要先将连接接收缓冲区中的请求数据处理完毕,并且将数据处理的结果返回给客户端(这样做是出于逻辑的完善,具体情况看要看具体是怎样设计的)。当服务端发现客户端已经主动断开连接后,服务端也要断开连接,并清除文件描述符和移除epoll中对该文件描述符的关心,以及其他相关的资源。

在应用层中,当调用close函数关闭套接字文件描述符时视为发起断开连接。

一、应用层与TCP之间的联系


接下来开始我们真正的问题讨论: 

二、 当通信双方中的一方如客户端主动断开连接时,仅是在客户端的视角下连接已经断开,在服务端的眼中,连接依然存在,为什么?——触发EPOLLRDHUP事件:对端关闭连接或停止写操作

套接字通信是全双工的,这意味着服务端和客户端之间可以同时进行通信,而要使得两者之间通信所传输的数据互不干扰,这就要求要为通信提供两个缓冲区,一个用来存储接收的数据,一个用于存储需要发送的数据。TCP为每个连接提供了两个独立的缓冲区:接收缓冲区和发送缓冲区。而客户端与服务端之间建立连接的实质就是通信双方是否能正常进行通信——即1、客户端是否能向服务端发送信息;2、服务端能否接收到客户端所发送的信息并且能否能否向客户端发送信息;3、客户端能否接收到服务端发送的信息。

以上过程实质上就是三次握手的验证过程,进行三次握手的目的就是验证通信双方是否具备通信的能力,即双方是否能够正常接收数据。可见,客户端与服务端之间连接的建立实质上就是两者发送缓冲区与接收缓冲区之间的联系。

当客户端主动调用close关闭连接时,实际上仅仅是客户端的发送缓冲区到服务端的接收缓冲区这条路线被切断了。对服务端而言,服务端的发送缓冲区到客户端的接收缓冲区之间的联系依旧存在。

所以,当客户端主动关闭连接时,我们需要及时调用read等函数将服务端接收缓冲区中的剩余数据读取至应用层并进行处理。数据处理完成后,如果有需要,可以继续发送给客户端。在数据清理完成后,服务端需要清理与该连接相关的资源。


 三、EPOLLIN:可读事件 和 EPOLLOUT:可写事件 的触发时机,什么时候设置读事件关心?什么时候设置写事件关心?

epoll所要做的就是等待资源就绪。

对于一条连接而言,在连接成功建立之初,我们就应对其设置读事件关心。——因为通信中的一方始终需要接收另一方发送的信息,继而要去对信息进行处理。这就需要我们调用read/recv等读取函数去读取接收缓冲区中的内容。但,缓冲区中如果没有数据,我们调用读取函数就会阻塞(BLOCK模式,连接关闭时返回0)或者读取失败(NONBLOCK模式,错误码为EAGAIN 或 EWOULDBLOCK,表示资源暂时不可用,连接关闭时返回0)。

那么读就绪的条件就是:在LT模式下,接收缓冲区中有数据即为读就绪。在ET模式下,接收缓冲区中接收到新数据即为读就绪。

类似的,写事件的就绪:在LT模式下,发送缓冲区中有空间即为写就绪。在ET模式下,发送缓冲区中有新空间即为写就绪。

但是,我们并不能在连接成功建立之初对其设置写事件关心!连接建立之初,发送缓冲区中一定是有空间的,因为此时我们并未将数据处理的结果写入到发送缓冲区中。如果此时设置了写事件关心,那么写事件是常就绪的,然而我们大多数的事件可能是在进行读取数据与业务处理,并不一定需要发送数据。同时,不必要的写事件常就绪还会造成epoll模型中红黑树和就绪队列资源的占用和不必要的资源浪费。

所以,我们要在需要写数据的时候将设置关心写事件——即当应用层处理完读取上来的请求数据并生成响应数据需要发送给另一方时,我们再将该连接描述符的写事件设置为关心。必要的,在数据发送完毕后,及时关闭对该连接的写事件关心。


四、EPOLLPRI:优先处理数据事件 —— 当TCP报文携带的是紧急数据时触发(URG = 1, 紧急指针有效)

 当TCP报头中的保留位字段中的URG字段设置为1时,发送应用进程就告诉发送方的TCP有紧急数据要传送——该报文数据中包含紧急数据,于是发送方TCP就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍是普通数据。这时URG需要搭配首部中的紧急指针字段配合使用。

当接收方的接收缓冲区根据TCP报头识别到该段报文的数据包含紧急数据时,会通知与其相关的系统调用,epoll会返回EPOLLPRI事件。说明有紧急数据到来,需要优先处理。


http://www.ppmy.cn/devtools/150454.html

相关文章

LabVIEW驱动电机实现样品自动搜索

利用LabVIEW控制电机驱动相机在XY平面上进行扫描,以检测样品位置。样品最初可能位于相机视野范围之外,需要实现自动搜索样品位置并完成精确定位扫描的功能。该系统需具有以下特点: 高效搜索:能够快速确定样品位置,缩短…

电脑之故障检测(Computer Fault Detection)

电脑之故障检测 在日常使用电脑的过程中,我们难免会遇到各种各样的故障。从简单的软件冲突到复杂的硬件损坏,这些问题往往让人头疼不已。然而,掌握一些基本的电脑故障检测方法,可以帮助我们快速定位问题所在,并采取相…

CHAIN OF RESPONSIBILITY(职责链)—对象行为型模式

1. 意图 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。 2. 动机 考虑一个图形用户界面中的上下文有关的帮助机制。用户在界面的任一部分…

1.组件的三大组成部分注意点(结构/样式/逻辑)scoped解决样式冲突/data是一个函数2.组件通信组件通信语法父传子子传父

学习目标 1.组件的三大组成部分注意点(结构/样式/逻辑) scoped解决样式冲突/data是一个函数 2.组件通信 组件通信语法 父传子 子传父 非父子通信(扩展) 3.综合案例:小黑记事本(组件版) …

两分钟解决 :![rejected] master -> master (fetch first) , 无法正常push到远端库

目录 分析问题的原因解决 分析问题的原因 在git push的时候莫名遇到这种情况 若你在git上修改了如README.md的文件。由于本地是没有README.md文件的,所以导致 远端仓库git和本地不同步。 将远端、本地进行合并就可以很好的解决这个问题 注意:直接git pu…

Markdown中甘特图的使用

Markdown中甘特图的使用 1. 前言2. 语法详解2.1 甘特图语法 3. 使用场景及实例4. 小结5. 其他文章快来试试吧🖊️ Markdown中甘特图的使用 👈点击这里也可查看 1. 前言 Markdown 的原生语法不支持绘制图形,但通过扩展模块,我们可…

【Rust】常见集合

目录 思维导图 一、Rust常用集合 1. Rust标准库中的集合概述 2. 常用集合类型 2.1 向量(Vector) 2.2 字符串(String) 2.3 哈希映射(Hash Map) 二、向量(Vec) 1. 向量的概述…

Mysql--基础篇--视图,存储过程,触发器

1、视图 MySQL视图(View)是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表&#…