传输层协议TCP

news2024/9/20 12:35:52

        本篇详细的讲解了有关传输层协议中 TCP 的常见知识,其中主要包括:TCP 协议段格式、确认应答机制、超时重传机制、连接管理机制(三次握手和四次挥手)、TIME_WAIT 和 CLOSE_WAIT 状态、滑动窗口、流量控制、延迟应答、捎带应答、面向字节流、粘包问题、TCP 异常情况、基于 TCP 的应用层协议,最后比较了 TCP 协议和 UDP 协议。 目录如下:

目录

TCP 协议段格式

确认应答机制(ACK)

捎带应答

超时重传

三次握手建立连接

三次握手建立连接的来源

四次挥手断开连接

流量控制

滑动窗口

拥塞控制

延迟应答

面向字节流

粘包问题

TCP连接异常情况

TCP、UDP对比

TCP可靠性与性能提升

基于TCP的应用层协议

TCP 协议段格式

        TCP 协议的全称为:传输控制协议(Transmission Control Protocol),从字面意思理解就是需要对数据的传输进行一个详细的控制。TCP 协议段的格式如下:

        如上所示的 TCP 首部,分别由 20 字节的固定首部和选项的可变首部组成。 

        源/目的端口:表示数据从哪个进程来,要到哪个进程去(一个端口对应一个进程)。

        4 位 TCP 首部长度:表示该 TCP 头部有多少个 32bit(4 字节),所以对于 TCP 的最大长度为 15 * 4 = 60 字节。(通过 4 位 TCP 首部长度就可以成功的将 TCP 数据报解包,将首部与数据分离

        32 位序列号:TCP 将每个字节的数据都进行了编号,即为序列化。

        32 位确认序列化:一个携带 ACK 标志位的数据报高数发送者我已经收到了哪些数据,下一次你应该从哪里开始发送数据。

        16 位窗口大小:由发送端填充,CRC 校验,接收端校验不通过,则认为数据有问题。此处的校验和不光包含 TCP 首部,也包含 TCP 数据部分。

        16位窗口大小:表示整个 TCP 报文的长度,最大不超过 65535 字节。

        16 位紧急指针:标识出哪部分是紧急数据。当标志位 URG 被置为一的时候,16 位紧急指针才会有效,该指针标识出在这个报文中的那个位置的数据是紧急数据,并且该数据只有一个字节,同时当 TCP 接收端接收到这样一个数据的时候会忽略在接收缓冲区等大接收的数据优先读取该数据,这样的数据通常是用于表示暂停、取消某项任务的数据(这样的数据也被称为带外数据 out of band data)。

        6 位标志位:

URG:紧急指针是否有效
ACK:确认号是否有效
PSH:提示接收端应用程序立刻从  TCP 缓冲区把数据读走
RST:对方要求重新建立连接(把携带 RST 标志位的报文称为复位报文段)
SYN:请求建立连接,在请求建立连接时发送(带有 SYN 标志位的报文称为同步报文段)
FIN:通知对方,本段即将关闭(携带 FIN 标识的为结束报文段)

        在接收 TCP 发送的数据的时候,TCP 数据报类型有:请求建立连接的报文、请求断开连接的报文、确认应答报文、正常的数据报文等等,那么我们如何识别当前这个数据属于那种数据报文呢?即使用 6 个标志位,每一个标志位都是在 TCP 首部中的一个 bit 位,当这个 bit 位置1的时候,则表明当前的这个数据报属于哪种数据报文类型(可以存在多个标志位同时置一)。

确认应答机制(ACK)

        如上图,上图中的每一个 ACK 都带有对应的确认序列号(这个序号表示的意思为:这个序号前的所有数据我都已经收到),意思是告诉发送者,我已经收到了哪些数据,下一次你应该从哪来开始发送数据。

        当客户端向服务器端发送信息的时候,由于是远端进行发送信息,想要知道对方是否已经收到消息,那么只能有对方在向客户端发送一个确认应答的信息(携带 ACK 标志位的信息),当客户端接收到对应的确认应答信息之后就可以确定对方已经收到了自己发送出去的信息(注:这个时候客户端不需要发送数据给服务器端表明已经收到了你的确认应答信息, 因为如果不采取这样的方法,服务器端和客户端就会因为确认应答消息而陷入无限循环 --> 服务器响应客户端的确认应答,客户端响应服务器的确认应答,若服务器端发送过来的确认应答消息丢失,客户端会有重传机制)。

        所以对于单向通信而言,发送端只管发送信息,接收端只管发送确认应答信息,一旦有一方出现错误会有对应的处理机制。当双方需要进行通信的时候,其实就是单向通信的翻版,双方分别发送信息,双方分别发送对应信息的确认应答消息。所以:双方采用确认应答机制就可以保证两个朝向上数据通信的可靠性

捎带应答

        我们会发现在 TCP 首部中,同时存在 32 位序号和 32 位确认信号,这两个字段同时存在,这是因为 TCP 连接之间的通信是全双工的,在发数据的同时也可以收消息,一方既可以作为发送消息方也可以作为接收信息方,那么当我们对某个信息需要发送确认应答消息的同时,还要向对方发送我自己想要发送的数据呢,这个时候就可以将两个报文组合成一个数据,这时只需要修改发送数据的确认应答字段就可以达到发送自己数据和发送确认应答数据的效果,提高发送效率,如下:

        原本发送两个报文,现在只需要发送一个报文即可。

超时重传

        对于超时重传的发生,只要在特定时间内没有收到来自对方的确认应答消息,那么就会被判定为丢包,然后就会触发发送端超时重传机制,重新发送数据。所以会有两种情况触发超时重传,一种是发送端的数据报丢失,另一种是接受方的确认应答丢失

        但是对于第二种接收方的确认应答丢失的情况,当接收方再一次收到消息的时候,通过序号来识别这个消息已经接收过了,所以不会再次接收消息,而是直接将消息丢掉,然后将确认应答再发生一遍。

        对于超时重传的时间间隔而言,没有特定的时间间隔,因为有的时候网速会比较好,有的的时候网速会比较慢,那么对于超时重传的要求也会不一样,网速太快适合将超时重传的时间间隔设置短一点,网速较慢适合将间隔时间设置时长长一点。超时重传的时间间隔太短会导致确认应答还没发回来就再次发送出去,间隔时间太长会导致丢包很久了还没有重传。

        所以,TCP 为了保证无论在任何环境下都能比较高性能的通信,因为会动态计算这个最大的超时时间。在 Linux 中,超时以 500ms 为一个单位进行控制,每次判定超时重发的时间都是 500ms 的整数倍,第一次间隔 500 ms 之后,发送一次,若还是得不到应答,那么就等待 2 * 500ms 之后在重传,若还是不行,在等待 4 * 500ms 之后在重传……当累计到一定的次数之后,TCP 会认为网络或者对端主机已经出现异常,强制关闭连接

三次握手建立连接

        对于三次握手建立连接的流程如下:

        如上图所示,对于服务器端的 TCP 层,是一直设置为 LISTEN 状态的,等待其他的客户端来申请连接,此时会设置一个 listenfd 监听描述符。

        当客户端想要对服务器申请建立连接的时候,会使用 connect 函数(传入自己的文件描述符 fd 和服务器的地址和端口)向服务器发起请求,一旦发送请求之后,接下俩的三次握手操作将完全由 TCP 协议自动完成。

        connect 发送连接请求之后,会发送一个携带 SYN 标志位且只含首部的 TCP 报文,发送出去后将自己的状态设置为 STN_SENT,当服务器接收到对应的报文之后,会调用 accept 函数阻塞的等待客户端连接,然后 TCP 协议向客户端发送一个带有  SYN + ACK 的 TCP 首部报文,同时服务器端的状态被设置为 SYN_RCVD,当客户端接收到来自服务器的 SYN + ACK 的报文之后,会向服务器发送确认应答报文,然后就会将自己的状态设置为 ESTABLISHED(表示建立连接成功),服务器收到之后就会将自己的状态也设置为 ESTABLISHED 状态,同时之前调用 accept 函数返回,给新建立的连接分配一个 connectfd 文件描述符用户客户端和服务器的通信

        其实我们的三次握手也可以被称为四次握手,因为当客服端发送携带 SYN 的TCP 报文的时候,服务器可以先发送 ACK 确认应答,然后发送 SYN 给客户端,不过服务器在这个过程中使用了捎带应答将两个报文结合在一起发送了。 

        但是对于最后一个从客户端发送出去的 ACK,并没有应答,也就是说对于最后一个 ACK 很可能会在发送的过程中丢包,所以建立连接的本质就是在堵,堵最后一个 ACK 服务器一定能收到。三次握手的目的不是一定将连接建立成功,但是一旦三次握手一旦成功,那么连接一定建立。

        当最后一个由客户端发送出去的 ACK 丢包之后:因为当客户端将最后一个 ACK 报文发送出去之后,客户端自己就已经认为连接建立,这个时候就可以向服务器发送消息,但是当服务器收到了来自客户端的消息的时候(此时服务器端没有收到最后一个 ACK),服务器就可以推导出一定是最后一个 ACK 丢失了,然后在给客户端发送的报文中将 RST(连接重置标志位)标志位置一,当客户端收到这个报文之后就意识到最后一个确认应答报文丢失,然后将原异常连接释放,重新进行三次握手建立连接,如下网页就是出现了这样的状况:

        对于以上建立连接的过程,和这篇 TCP 套接字编程博客的连接建立流程一样,建议看一下下面这篇文章,与编程相结合,便于理解:

UDP/TCP --- Socket编程-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/m0_74830524/article/details/141218715?spm=1001.2014.3001.5501

三次握手建立连接的来源

        对于一个服务器而言,同时会维持许多的连接,那么服务器就需要对这些连接给管理起来,一旦涉及管理,那么就需要设计出对应的结构体,也就是对应的内核数据结构用来描述连接。

        所以我们需要对连接 malloc 出对应的内核数据结构,也就需要对应的内核数据结构空间,同时还会花开辟空间的时间也就是占用时间空间资源。

        假若是一次握手或者两次握手我们就建立连接,那么只要当服务器端将请求发送过来,就可以建立对应的连接,假若这个时候有人恶意的提出无数个连接请求(SYN 洪水),服务器都将连接请求给应答了,只要数量达到一定程度,就会占完服务器的内容,让服务器崩掉。

        但是当我们选择三次握手的时候,虽然也可能会收到来自大量恶意的连接请求,但是三次握手的前提是需要客户端先建立出连接,才能让服务器建立连接,所以即使真正的收到 SYN 攻击,也会使得对方自损八百,也就表明三次握手没有相对于一次、两次握手那样明显的漏洞。

        并且三次握手可以用最少的报文来验证全双工(验证网络的连通信),客户端可以发送 SYN,也可以接收 SYN + ACK 的消息,说明客户端既可以发消息也可以收消息,服务器端既可以发送 SYN + ACK,也可以收到 ACK,那么服务器也具备全双工的功能。

        同时三次握手还可以建立双方通信的共识,我们在上文中提到三次握手其实也可以被称为四次握手:客户端发送一个 SYN 请求,服务器应答一个 ACK,服务器也发送一个 SYN 请求(服务器端将 SYN 和 ACK 捎带应答只发送一个报文),客户端发送 ACK 确认应答建立连接。

四次挥手断开连接

        对于四次挥手断开连接的过程如下:

        四次挥手断开连接的过程如上,当客户端想要断开连接的时候,先调用 close 函数将文件描述符给关闭掉,然后发送带有 FIN 标志位的报文,发送出去后客户端的状态被设置为 FIN_WAIT_1,当服务器收到对应的消息之后,就会发送出确认应答消息,然后将自己的状态设置为 CLOSE_WAIT 状态,当客户端收到 ACK 消息之后将自己的状态设置为 FIN_WAIT_2,此时客户端还没有完全与服务器断开连接。当服务器端想要和客户端断开连接的过程和以上类似,不过发送消息后的状态不一样,当最后一个 ACK 消息被接收之后,两端就彻底的断开了连接

        那么为什么是四次挥手呢?因为通信的双方的地位是对等的,所以每一方都需要向对方提交断开连接申请,并且一端想要断开只代表:我不想发送我的信息数据了,但是还可以发送 ACK 等类的回复信息,所以存在一方断开连接之后,另外一方还可以向对方不断的发送消息,对方也在不断发送会 ACK 消息。

        但是对于上图的流程,为什么对于客户端在应用层都已经调用 close 将对应的文件描述符给关掉了(当使用 close 将文件描述符关闭之后,客户端既不能读也不能写了),客户端还是可以向服务器端发送对应的 ACK 消息呢?这是因为有协议的存在,协议可以规定当在四次挥手中的断开连接过程中,客户端调用 close 之后只关闭写端(写自己的消息,但是 TCP 还是可以本能的发送应答 ACK 消息,只关闭写端也可以理解为调用系统调用 shutdown(fd, SHUR_WR) 函数),当双方都调用 close 之后,才彻底的关闭连接。

        对于四次挥手而言,是使用最小的通信成本,建立断开连接的共识(双方都不和对方通信了,并且也知道对方不和我通信了)

        同时对于四次挥手我们也可以将其称为三次挥手,这种情况的发生是服务器端的 FIN 和 ACK 捎带应答发送给对应的客户端,这个时候就变成了三次挥手。那么平时为什么更常见的叫三次握手四次挥手呢?这时因为对于建立连接时,服务器都是直接同意,所以可以将 SYN + ACK 个捎带应答,但是对于断开连接的时候,只有当一端没有数据需要发送的时候才会断开连接,一般同时没有数据发送的情况较少,所以更多见到的是四次挥手。

        CLOSE_WAIT 状态:当一端发送 FIN 请求的时候,然后另一端发送 ACK 应答,这个时候另一端的状态就会变为 CLOSE_WAIT 状态。

        LAST_ACK 状态:当另一端使用 close 关闭自己的文件描述符的时候,就会从 CLOSE_WAIT 状态转化为 LAST_ACK 状态。

        TIME_WAIT 状态:主动断开连接的一方,会在第四次挥手完成,等待一定的时长(2 * MSL:maximum segment lifetime 报文最大存活时间),我们可以通过指令 cat /proc/sys/net/ipv4/tcp_fin_timeout  查看对应的最大数据报存活时间,如下:

 使用两个 MSL 时间可以保证在两个传输方向上的尚未被接收或者迟到的报文都消失,若不等待这个时间直接重启,可能会收到来自上一个进程的迟到数据,但是这种数据是错误的数据。同时也可以在理论上保证最后一个 FIN 报文可以到达,假若最后一个 ACK 丢失,那么服务器会重发一个 FIN,虽然这个时候客户端的进程部件了,但是 TCP 仍然存在,可以重发 LAST_ACK。

流量控制

        TCP 传输数据的本质就是一端将数据通过网络传输到对端的 TCP 报文接收缓冲区,但是当我们传输的速率太快的时候就会导致对方的接收缓冲区来不及接收,来不及接收的报文就会被丢弃,丢弃之后就会重传, 重传还是会导致占用网络带宽,浪费自己的流量。

        所以,TCP 支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就被称为流量控制。接收端的接收能力就是接收端的接收缓冲区剩余空间的大小

        那么发送端如何知道接收端的接收能力呢?当发送端发送对应的信息之后,接收端就会对于发送过来的信息会有 ACK 确认应答报文,确认应答报文中的 16 位窗口大小中就填写了接收端缓冲区给的剩余空间大小。同时,对于如上的操作也是全双工的,因为双方都可以给对方发送报文。

        同时对于建立连接后的第一个报文应该发送多大的报文数据呢?对于该报文数据而言,已经在建立连接的三次握手阶段就已经协商好双发的接收能力接收缓冲区的大小。

        但是当一端的接收能力为 0 了呢?接收端一直不从接收缓冲区中的拿出数据,那么对于发送端就只能一直等待下去,等待接收缓冲区有位置可以发送的时候在发送数据,但是发送端该如何得知接收端的接收缓冲区又可以接收消息了呢(因为发送端没有发送消息给接收端,接收端也就没有应答报文然后携带窗口大小)?这个时候就有两种处理策略,一种是每隔一段时间发送端发送一个窗口探测报文,探测对方的接收缓冲区是否存在空间,另一种是当接收缓冲区中有空间之后,接收端主动发送个发送端一个窗口更新通知报文,通常这两种方法会同时使用。若想要尽快让接收端处理接收缓冲区的数据,发送端发出去的窗口探测报文就可以携带 PSH 标志位(该标志位的作用就是催促接收端尽快处理报文,其实很多场景都有使用 PSH 标志位,并非只有这种场景在使用)。

        TCP 报文中的 16 位窗口大小最大也只能表示 65536 个字节的数据,那么 TCP 窗口大小就最大就只能是 65535 个字节吗?实际上,TCP 首部的 40 字节选项中还包含了一个窗口扩大因子M,实际窗口的大小是窗口字段的值左移 M 位。

滑动窗口

        对于发送端发送数据和接收端接收数据,通常不是发送端发送一个,接收端应答之后再一个一个的发送,这样发送的性能较差,所以这个时候就出现了另一种传输数据的方式,发送端一次发送多个数据,接收端一个应答多个数据,如下:

        如上右图的方式被称为滑动窗口, 对于在滑动窗口以内的数据,可以直接发送并且暂时不用收到应答,如下:

        在滑动窗口左边的数据为已经确认的数据,滑动窗口内的数据为可以直接发送且不用理解应答的数据,右边的数据则是等待发送的数据。当滑动窗口内的数据接收到应答的时候滑动窗口就会开始向右边滑动,对于滑动窗口只能向右边滑动并不能向左边滑动,因为左边的数据已经确认了。

        对于滑动窗口的大小会是一直不变的吗?滑动窗口的大小是根据接收端的接收能力来确定的,也就是接收端接收缓冲区的大小。所以,对于滑动窗口的大小既可以很大,也可以为 0。滑动窗口滑动的原理如下:

        所以对于滑动窗口的大小完全由接收端的接收缓冲区决定, 所以滑动窗口既可以很大,也可以为 0。

        但是假若我们发送出去的报文丢包了呢?丢包的情况可以分为:最左侧的数据丢包、中间数据丢包、最右侧数据丢包。我们先已最左侧数据丢包为例:也就是在一个滑动窗口内的数据之后的数据都已经被接收到了,但是最左侧的数据报没有被接收,如下图:

        如上图,当最左侧的数据丢失之后,之后的确认应答报文都会是丢失的报文前一个的确认应答序号,因为在 TCP 首部的 32 位确认序号的含义是:序号前的数据都是已经收到的数据,然而对于最左侧的数据报丢失,即使之后的数据也都收到,也不能保证确认序号前的数据都收到,所以发送出的 ACK 报文都还会是丢失报文前一个的确认应答数据,当这样的确认应答数据报连续发三个的时候,TCP 协议就会发现丢包了,所以这个时候就会重新发送(快重传)。所以对于最左侧出现丢包的情况,会出现以下情况:

        1. 由于确认序号规定的约束,滑动窗口左侧不会移动

        2. 使用快重传或者超时重传机制对最左侧报文进行补发

        那么假若是中间的或者最右侧的数据报丢失了呢?这样的情况和最左侧的情况其实一样,对于丢失前的报文为已经接收到对应的 ACK 报文,所以中间、最右侧的报文也会被转化为最左侧报文丢失的情况。

        超时重传和连续三次收到同样的 ACK 数据报都是重传的机制,互相不矛盾,超时重传更像一种兜底机制,因为连续三次收到同样的 ACK 数据报重传的时间小于超时重传。

        如上是发出去的报文丢失了,但是还有种情况,那就是确认应答报文的丢失,如下图:

        当应答的某个报文丢失之后也不同担心,只要之后的 ACK 报文能到达即可,因为 ACK 报文的含义为:当前确认序号前的报文全都收到。

        我们的窗口一直在向右边滑动,那么窗口是否会在向右滑动的过程中滑出边界呢?肯定是不会的,对于滑动窗口滑动的数据区(发送缓冲区)而言,我们可以把它当做一个环形队列,所以怎样都不会越界。并且我们不需要将左侧已经发送的数据给清除,因为按照环形队列的逻辑,这一部分的数据迟早都会被覆盖。

拥塞控制

        当发送大量的报文中只有几个报文丢失的时候,这种情况属于正常情况,但是当大量的报文发送出去,然而又丢失了大量的报文,并且 TCP 也在严格的进行流量控制、滑动窗口、快重传等机制,却还是出现了意外,所以这个时候就只能是因为网络出现了问题,出现了网络拥塞,需要进行拥塞控制。

        在网络出现拥塞之后,我们丢失的大量报文不能立即将其进行重传,因为这个时候的网络情况已经很糟糕了,若大家都还是不断的将报文塞到网络中,就会使得网络的状况变得更加糟糕,所以这个时候我们就只能减少我们的报文发送量(解决网络拥塞问题,最大的价值在于让多个使用同一个网络进行同行的主机又拥塞控制的共识),也就是 TCP 中的慢启动机制。

        慢启动机制:先发送少量的数据探探路,摸清当前的网络拥堵情况,再决定按照多大的速度传输数据,如下:

        如上所示,每一次发送的数据是上一次数据的一倍,就这样一直探索,直到当前发出去的数据报不能被收到为止。同时引入一个拥塞窗口的概念:一开始的时候定义拥塞窗口的大小为 1,每次收到一个 ACK 应答拥塞窗口加一,直到报文在网络中丢失为止(也就是相当于使用拥塞窗口探测当前网络的报文承受压力)。

        既然叫做慢启动,那么为什么我们要使用每次增加一倍的 2^n 这样的增长函数呢?这可是指数爆炸的函数。这是因为这样的函数启动慢,增幅快,这样的函数在一开始增长的时候不会很大,可以慢慢的让网络恢复,并且每一次增加的量相对来说却很多,可以很快的检测到当前网络情况下可以发送最大报文的数量

        我们在发送数据报的时候是按照滑动窗口来发送数据报的,滑动窗口多大一次就发送多大的数据,但是当网络状况很差的时候并不能这样,因为网络可能承受不住一下发送这么多的数据报,所以滑动窗口的大小应该等于应答窗口大小和拥塞窗口大小的最小值(滑动窗口 = min(应答窗口,拥塞窗口)

        网络状态是浮动的,那么拥塞窗口的大小也必然是浮动的,所以现在就存在一个问题,拥塞窗口的大小该如何动态的计算呢?如下图:

         对于慢启动的增长我们也不会让其一直增长,单纯的让其加倍,所以会引入一个慢启动的阈值(ssthresh),当拥塞窗口超过这个阈值的时候,就不再按照指数方式增长,而是按照线性方式增长。这样线性增长的方式被我们称为加法增大,当增长到一定值而发生网络拥塞的时候,这个时候就探测出了我们的拥塞窗口大小,同时会更新下一次阈值的值(为当前拥塞窗口大小的一半,这样的方式叫做乘法减小)。

        当在探测拥塞窗口大小的时候发生了拥塞的时候就已经探测出当前窗口值的大小,这个时候就会从一再次开始探测(原理按照上图)。会不断重复这样的探测过程,也就能达到动态调整拥塞窗口的大小。

        为什么要设立 ssthresh 阈值来进行线性增长呢?因为当增长到一定值的时候若还是一倍增长,很可能会直接超过当前拥塞窗口的值,导致拥塞窗口测试不准,所以需要改为线性探测。

延迟应答

        当我们在使用滑动窗口向接收方发送数据的时候,数据报被接收方的接收缓冲区接收,但是这个时候接收方也许不会立即向发送端发送 ACK 报文,而是等待一定的时间之后(当然这个数据肯定不会超过超时重传的时间)在发送 ACK 数据。因为在等待一端时间再发送 ACK 数据的同时,接收端的上层也会不断从接收缓冲区中拿走数据,这也就表明,接收端是在等待通告发送端一个更大的接收窗口,所以这就叫延迟应答。

        延迟应答可以提高传输数据的并发度,也就是提高传输效率。延迟应答的效率提升对于单个主机而言可能是概率性的提高效率,因为假若当前网络状况不是很好,即使接收窗口再怎么大,发送的数据大小也就是拥塞窗口的大小,但是假若将延迟应答作用的效率作用在整个互联网中呢?互联网中有着无数的主机,对于某些主机提升了效率,也就相当于提升了效率。(目标就是在保证网络不拥塞的情况下尽量提高传输效率

        不过不是所有的包都可以延迟应答,因为有的数据报就是希望尽快应答出来。

        对于延迟应答的策略有以下两种:

        1. 数量限制:每隔 N 个包就应答一次

        2. 时间限制:超过最大延迟时间就应答一次

        对于数量限制和时间限制的值,不同的操作系统有不同的要求:一般 N 取值 2,延迟应答时间取 200ms。

面向字节流

        我们创建一个 TCP 连接,同时还会创建一个文件描述符、接收缓冲区和发送缓冲区。由于缓冲区的存在,我们读和写不需要一一匹配,比如:写 100 个字节数据的时候,可以调用一次 write 写 100 个数据,也可以调用 100 次 write,每次只写一个字节。当然在读取数据的时候,想要读取数据的大小和次数也可以由自己制定。

        同时我们将数据放入到发送缓冲区和接收缓冲区中的时候,我们可以等待缓冲区中的数据先变长一点在发送或者接收,也可以将其拆分接收或者发送。

        对于以上的数据操作我们并没有固定的用法,就像是我们使用水管放水一样,想放多少,想接多少都随意。

粘包问题

        因为 TCP 协议传输报文是面向字节流的,所以对于上层在从接收缓冲区中拿走数据的时候,就可能出现拿到的数据不足一个完整报文,刚好一个报文或刚好多个报文,超过一个报文,这就是粘包问题,我们并不知道拿出的数据是否刚好是一个完整的数据。

        对于不足一个完整报文我们肯定需要等待其有了一个完整报文才读取,刚好一个完整报文或刚好多个报文就可以直接取走,超过一个报文时就只能等待按照报文的边界将其切割取走。

        所以对于粘包问题的解决方法就只能是明确两个包之间的边界,如下:

        1. 规定每个报文定长,所有报文的长度都一样。

        2. 报文携带首部,让首部记录报文的长度,按长度来判断当前报文是否是一个完整报文。

        3. 使用特殊字符来分隔每个报文

        除了以上方法外还存在许多其他方法。

TCP连接异常情况

        当客户端和服务器正在通信的时候突然客户端的进程被终止或者客户端的机器突然重启:对于正在同行的进程也会释放对应的文件描述符,仍然会发送对应的 FIN,和正常关闭没有什么区别。

        但是当机器突然掉电、突然断网:这个时候通信的另一方还认为通信连接还存在,但是对于断网的一方却已经掉线,所以对于这个通信而言就会存在一方长时间不会发送任何的报文,当没有断掉的一方发送消息出去之后,没有 ACK 报文就会发现连接此时已经不在,就会进行 reset。同时 TCP 内也内置了一个保活定时器,会定期的询问对方是否还在,若对方不在,就会把连接释放。

        但是对于如上的断开连接的机制,若主动发现已经断开还好。若使用的是 TCP 内的保活定时器机制发现断开连接,这个过程将会非常的漫长,短则几十分钟,长则几小时,会长期占用资源很不方便。

        所以对于判断连接是否还存在最好是由应用层来实现,因为应用层来实现判断连接是否存在,可以自己控制保活时间,可以设定一定时间之后向对方询问是否还在,若没有应答,说明已经断开了连接。

TCP、UDP对比

        对于 TCP 和 UDP 协议而言,各有各的特点,TCP 协议是可靠的,那么就一定优于 UDP 吗?答案并不是,我们在对于 UDP 和 TCP 时并不能简单绝对的进行比较,而是需要考虑应用场景、网络情况等环境来判断。

TCP 用于可靠传输的情况,应用于文件传输、重要状态更新场景等
UDP 用于对高速传输和实时性较高的通信领域,如:视频传输、直播或者广播

        若我们当前需要实现的通信对于丢包问题并不能容忍,那么我们可以选择 TCP,若想使用更简单的协议,那么可以选择 UDP 协议。

TCP可靠性与性能提升

        对于 TCP 协议的可靠性和提高性能的机制如下:

可靠性:
校验和、序列号(实现按序到达)、确认应答ACK、超时重发、连接管理、
流量控制、拥塞控制

提高性能:
滑动窗口、快重传、延迟应答、捎带应答

        假若想要将我们的 UDP 协议的连接变得可靠,则可以采取以上的可靠性策略添加在 UDP 中。

基于TCP的应用层协议

        常见的协议如下:

HTTP
HTTPS
SSH
Telnet
FTP
SMTP
……

        当然,也包括自己实现的基于 TCP 协议的应用层协议。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2118829.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

宠物空气净化器352和希喂哪个品牌比较好?352、希喂宠物空气净化器终极PK

家里猫咪掉毛太厉害了怎么办?我家长毛、短毛猫都有,短毛还是猫界蒲公英银渐层,不会再有比曾经的我更受掉毛困扰的铲屎官了!我曾经为了减少我家猫咪掉毛,鱼油、维生素A、维生素B5、碘营养物质都在喂,没啥用&…

table表格左键双击,单元格可编辑效果

1) 效果,修改内容后数据同步修改: 2) 思路 1、el-table提供了左键双击事件。 2、左键双击后,该单元格更改为input框后,input框需要获取焦点。 3、输入内容后,(回车按钮或者点击其他位置input框失去焦点),数据需要更…

Django学习实战篇三(适合略有基础的新手小白学习)(从0开发项目)

前言: 在上一章中,我们对Django的Model层有了比较全面的认识,本章就来配置Django自带的admin。这里需要认识到,Django的Model层是很重要的一环,无论是对于框架本身还是对于基于Django框架开发的大多数系统而言。因为一…

私域电商 IP 化发展的探索与优势

摘要:本文聚焦于私域电商与社交电商的区别,重点探讨私域电商的 IP 属性。深入分析其在获取流量、转化用户以及挖掘用户价值方面的独特优势。同时引入链动 2 1 模式、AI 智能名片、S2B2C 商城小程序源码等元素,详细阐述这些元素在私域电商 IP…

前端:JavaScript中的this

前端:JavaScript中的this 1. this的指向2. 指定this的值3. 手写call方法4. 手写apply方法5. 手写bind方法 1. this的指向 在非严格模式下,总是指向一个对象;在严格模式下可以是任意值。 开启严格模式,如果是为整个脚本开启&#…

mapActions辅助函数的使用

什么是mapActions? mapActions 是 Vuex 提供的一个辅助函数,它允许你将组件中的方法映射为 Vuex 中的 Actions,以便于你可以直接从组件内部调用这些 actions。通过使用 mapActions 你可以在组件中以函数的方式引用 Vuex 中的 Actions,从而避…

个人随想-一道简单的AI面试题

大模型的兴起,很多公司现在都开始进入AI开发的新篇章,那么或多或少​也需要招聘一些AI的开发人员。 其实很多公司需要的,说白了就是一个AI开发工程师或者架构师,但是在招聘过程中,或多或少对要求写的太过夸张&#xf…

​在乙游热潮中,Soul App创新社交玩法,寻找年轻人的精神共鸣

经过8月份的连续事件,社会公众对于乙女游戏(下文简称乙游)有了全新的认知。 起初,是女子举重奥运冠军罗诗芳在社交平台与热门乙游《恋与深空》的意外互动,引发了广泛关注。“奥运冠军也玩乙游”成为社交平台热搜话题,众多玩家讨论奥运冠军是否会与他们有共同的虚拟角色喜好。 …

链动2+1:高效用户留存与增长的商业模式解析

大家好,我是吴军,任职于一家致力于创新的软件开发企业,担任产品经理的职位。今天,我打算深入分析一个历经时间考验且依旧充满活力的商业模式——“链动21”模式,并通过一个具体的案例和相关数据,展示它如何…

检测Meaven是否安装成功

一.配置本地仓库 1.创建一个文件夹用来存放jar包 2.解开第53行的注释,将存放jar包的路径复制 二.配置阿里云 三.配置jdk环境 1.JAVA_HOME 2.path地址里面配置BIN目录 四.配置Meaven_HOME 1.Meaven_HOME 2.path地址里面配置BIN目录 五.检查是否安装成功 1.mvn-v mvn -v 2.…

Redis 篇-深入了解分布式锁 Redisson 原理(可重入原理、可重试原理、主从一致性原理、解决超时锁失效)

🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 本章目录 1.0 基于 Redis 实现的分布式锁存在的问题 2.0 Redisson 功能概述 3.0 Redisson 具体使用 4.0 Redisson 可重入锁原理 5.0 Redisson 锁重试原理 6.0 Redisson WatchDo…

盘点:当养生茶遇上互联网,都有哪些打法?

健康行业电商大战早已拉开序幕,作为健康行业的一个大类——养生茶还能缺席么?三好夫人、同仁堂、东韵、九芝堂、余庆堂等等各路豪杰齐聚养生茶电商,看他们如何各显神通吧! 三好夫人——一生只送一人 三好夫人以爱之名创立&#x…

Python进程间网络远程通讯方式:socket、pipe、RPC详解!

背景 最近在进行开发工作的时候,遇到了一个场景: pc程序需要和安卓设备进行通讯和接口调用。 此时就需要进行远程调用方法。然而大学时代有关于远程过程调用的知识都还给了老师……所以在此进行一个复习,并进行实战演练! 网络…

风趣图解LLMs RAG的15种设计模式-第三课

设计模式9-重新排名以优化搜索结果 设计模式10-使用上下文压缩优化搜索结果 设计模式11-使用纠正RAG对检索文档打分和过滤 今天先讲这些吧

java网络编程TCP通信实战:共享聊天室

目录 创建服务端 建立ServerSocket服务端。 接下来就是服务端线程的编写 前端ui登录界面 客户端线程 群聊界面 package server;import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Map;public class Server {//定义一个集…

DBC中一种特殊的特殊的Signal—多路复用Signal

前言: DBC设计中一般设计Signal时其实存在三种类型,如下图所示: **1)步骤1,鼠标单击展开Message,选中底下的Signal **2)步骤2,弹出dialog中选择 map signal **3)得到…

深入解读Docker核心原理:Cgroups资源限制机制详解

在容器化技术中,除了资源的隔离,如何有效地控制和分配系统资源同样至关重要。Cgroups(Control Groups) 是Linux内核提供的一个强大机制,允许限制、监控和隔离进程组的系统资源使用情况。Cgroups是Docker实现容器资源限…

用RNN(循环神经网络)预测股票价格

RNN(循环神经网络)是一种特殊类型的神经网络,它能够处理序列数据,并且具有记忆先前信息的能力。这种网络结构特别适合于处理时间序列数据、文本、语音等具有时间依赖性的问题。RNN的核心特点是它可以捕捉时间序列中的长期依赖关系…

【项目】云备份

云备份 云备份概述框架 功能演示服务端客户端 公共模块文件操作模块目录操作模块 服务端模块功能划分功能细分模块数据管理热点管理 客户端模块功能划分功能细分模块数据管理目录检查文件备份 云备份 概述 自动将本地计算机上指定文件夹中需要备份的文件上传备份到服务器中。…

【网络原理】❤️Tcp 核心机制❤️ 通晓可靠传输的秘密, 保姆式教学, 建议收藏 !!!

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人…