当前位置: 首页 > news >正文

网络传输层TCP协议

传输层TCP协议

1. TCP协议介绍

TCP(Transmission Control Protocol,传输控制协议)是一个要对数据的传输进行详细控制的传输层协议。 TCP 与 UDP 的不同,在于TCP是有连接、可靠、面向字节流的。具体来说,TCP设置了一大堆机制,来保证它的连接是可靠的,它的字节流传输是可靠的。

TCP协议结构:

TCP1

16位源端口号: 表示数据从哪个进程来。

16位目的端口号: 表示数据到哪个进程去。

32位序号: todo

32位确认序号: todo

4位TCP报头长度: 表示该TCP报头有多少个32bit(4字节),所以TCP报头的最大长度是 4 ∗ 15 = 60 4*15=60 415=60 字节。

6位标志位:

  • URG: 紧急指针是否有效。
  • ACKAcknowledge character):确认号是否有效。
  • PSH: 提示接收端应用程序立即从TCP缓冲区把数据读走。详细->PSH在流量控制的应用
  • RSTReset,连接重置标志位):对方要求重新建立连接。收到该标志的主机,要对异常连接重新释放,重新建立。(携带 RST标识的报文称为 复位报文段
  • SYNSynchronize Sequence Numbers):请求建立连接。(携带 SYN标识的报文称为 同步报文段
  • FIN: 通知对方,本端口要关闭了。(携带 FIN标识的报文称为 结束报文段

16位窗口大小: 标识了自己接收缓冲区的接收能力。详细-> 点击跳转

16位校验和: 发送端填充,CRC校验,接收端判断校验不通过,则认为数据有问题。(校验不仅包含TCP头部,也包含TCP数据)

16位紧急指针: 标识哪部分数据是紧急数据 (实际应用非常少) 。详细->点击跳转

40字节头部选项: todo

1.1 面向字节流

在应用层创建一个TCP的socket,在内核中会创建一个发送缓冲区和一个接收缓冲区。当调用write()时,数据会先写入发送缓冲区中,如果发送的数据太长,数据就会被拆分成多个 TCP 数据包发出;如果发送的数据太短,就会先在发送缓冲区里等待,等待发送缓冲区堆积得差不多了,或等待其他合适的时机再发出去。

在接收数据时,数据从网卡驱动程序到达内核的接收缓冲区,再通过应用层调用 read() 从接收缓冲区拿走数据。

由于一个TCP连接,既有发送缓冲区,也有接收缓冲区。所以对于一个连接,既可以读,也可以写,这个概念叫做全双工

由于缓冲区的存在, TCP 程序的读和写不需要一一匹配。

100 100 100 个字节数据时,可以调用一次 write()100 个字节,也可以调用 100 次 write(),每次写一个字节。

100 100 100 个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次 read() 100 100 100 个字节, 也可以一次 read() 一个字节,重复 100 100 100 次。

2. 确认应答(ACK)机制

我们在进行网络传输时,数据有可能会非常庞大,TCP 需要将一份数据拆分成多份,使用多个报文向对方端传输数据。那么 TCP 就需要确认对方端成功接收到了其中的所有报文而没有丢失。

但由于报文在网络中传输需要时间,主机双方不能实时确认每份TCP报是否被接收,每份报文都是过去发送的。所以需要 确认应答机制来保证数据成功被接收。确认应答机制认为,只要对方传来应答,就判断报文 100% 收到。

TCP为每个需要传输的数据的字节都进行了编号,主机向对方传输了多少数据,只需要告诉对方编号的范围即可。

TCP2

确认应答机制过程:

主机A向主机 B 发送数据,数据被拆分成多份 TCP 报分别发送。主机A发送第一份数据,在报头中包含此次传输的数据字节序列号为 1-1000,主机B接收到该TCP报后,向A发送确认应答的TCP报,且告知对方下一份数据的序列号应该从 1001 1001 1001 开始,这是因为主机B要让主机 A 明确我应答的是 传输 1-1000 数据的TCP报(因为实际传输中可能会有多个 TCP 报)。

TCP3

但是在网络通信中,每一份发送端报文或接收端报文可能不会都安全地到达。如果发生了丢包,TCP要如何应对呢?

3. 超时重传机制

两个主机之间在进行网络传输时,可能会因为网络拥堵等原因造成 TCP 报丢失。在上面确认应答的机制的情况下,如果其中一份 TCP 报丢失了,TCP 就需要对丢失的 TCP 报进行补发。但首先,发送端要先知道接收端丢失了哪一份数据。

3.1 如何处理重传?

TCP4

如果丢失的是发送端的报文。主机B接收不到报文,主机A迟迟等待不到应答,就会认为发送的报文丢失了,向主机B重新发送一个TCP报。

TCP5

如果丢失的是应答报文,那么主机 A 迟迟等到不到带有下一份应发序列号的应答,就会认为先前发的报文丢失了(主机A并不知道丢失的是自己的报文还是对方的应答报文),向 B 主机重新发送一份与上一份相同的报文。

所以,无论是发送端报文丢失,还是接收端的应答报文丢失,其处理方法都是一样的。都是发送端重新传一份没有正常经过确认应答机制的报文。

并且如果是应答报文丢失,主机 B 可能会接收到多个相同的报文,TCP 协议就需要具有去重的功能,而这个去重的功能就是由上面(确认应答机制)提到的 TCP 为每个需要传输的数据的字节都进行了编号来实现的。

现在我们知道了超时重传的重传机制,那么 TCP 又是如何判断、处理超时的呢?

3.2 如何处理超时?

最理想的情况是找到一个最小的时间,保证 “确认应答一定能在这个时间内返回”。但这个时间的长短一定是动态的,因为随着网络环境的不同,网络通信的速度肯定也不同。如果超时时间设置得太长,会影响整体重传的效率;如果超时时间设置得太短,有可能发送端会频繁发送重复报文。

Linux 中(BSD Unix 和 Windows 也是如此),超时以500ms为一个单位进行控制,每次判断超时重传的超时时间都是 500ms 的整数倍

如果判断超时重传一次后,仍然得不到应答,下次超时重传就是 2*500ms 以后,再下次就是 4*500ms ,以此类推。当重传累计到一定的次数后,TCP 认为网络或者对端主机出现异常,就会强制关闭连接。

4. 连接管理机制

TCP 是一个有连接的协议。那么 TCP 是如何保证连接是可靠的呢?

TCP 的通信全过程:

TCP6

服务器端状态转换:

[CLOSED -> LISTEN] 服务器端调用 listen() 后进入LISTEN状态,等待客户端连接。

[LISTEN -> SYN_RCVD] 一旦监听到连接请求(同步报文段),就将该连接放入内核等待队列中,并向客户端发送SYN确认报文。

[SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文,就进入ESTABLISHED状态,可以进行读写数据了

[ESTABLISHED -> CLOSE_WAIT]客户端主动关闭连接(调用close()),服务器会收到结束报文段,服务器返回确认报文段并进入CLOSE_WAIT

[CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据);当服务器真正调用close()关闭连接时,会向客户端发送FIN,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)。

[LAST_ACK -> CLOSED] 服务器收到了对FINACK,彻底关闭连接。

客户端状态转换:

[CLOSED -> SYN_SENT] 客户端调用 connect()发送同步报文段

[SYN_SENT -> ESTABLISHED] connect()调用成功,则进入 ESTABLISHED 状态,开始读写数据。

[ESTABLISHED -> FIN_WAIT_1] 客户端主动调用 close()时,向服务器发送结束报文段,同时进入FIN_WAIT_1

[FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认,则进入FIN_WAIT_2开始等待服务器的结束报文段

[FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段,进入TIME_WAIT,并发出LAST_ACK

[TIME_WAIT -> CLOSED] 客户端要等待一个2MSLMax Segment Life,报文最大生存时间)的时间,才会进入 CLOSED 状态。

4.1 三次握手

TCP 在进行建立连接时,假设是客户端主动向服务器建立连接:

TCP7

  1. 首先客户端向服务器进行 SYN 请求建立连接。
  2. 服务器接收到客户端的请求后,向客户端发送报文,这个报文除了 SYN 以外,还有客户端对服务器的 ACK
  3. 客户端在接收到服务器的 SYN+ACK 后,此时对于客户端来说连接已经建立完成,所以在下一个报文中,对服务器的 SYNACK 报文中,是可以携带数据的(称为捎带报文,下面细讲)。
  4. 服务器在接收到客户端的 ACK 后,对于服务器来说,此时连接也建立完成

注意在三次握手中, accept() 函数并没有参与到三次握手当中,当连接建立完成时,accept() 才会返回。

服务器和客户端建立连接的时间,有一个非常短暂的时间差,客户端比服务器要早一个报文。那么假如最后一个 ACK丢包了怎么办?

此时对于客户端来说,连接已经建立完成(ESTABLISHED),是可以正常发数据的。客户端进入正常通信状态,向服务器发送第报文,随后服务器就受到报文,此时服务器还没有进入 ESTABLISHED 状态,但服务器端认得发送该报文的IP和端口,就是刚才还没有完成连接建立(对服务器来说)的客户端。于是服务器对客户端报文的应答就不会是 ACK ,而是 RST(reset,连接重置标志位),表示我服务器这里的三次握手还没有完成,需要重新走三次握手的操作。

如果客户端是浏览器,触发 RST 的效果是这样:

TCP8

TCP三次握手的目的:

  • 验证全双工——验证网络的连通性。

    全双工,即同时既能读又能写。之所以要握三次手,就是因为双方都要验证自己既能对对方写(发送 SYN),又能对对方读(接收 ACK)。

  • 建立双方通信的共识意愿。

    并不是说客户端想要和服务器建立连接,服务器就必须要与其建立连接。在访问网页时,也能常常看到网页拒绝访问的错误。

4.2 四次挥手

四次挥手,别名连接终止协议,是TCP双方断开连接的一种机制。TCP的四次挥手是进行可靠断开连接的最小次数。

TCP9

客户端主动退出连接(FIN),此时在客户端的本地体现为用 scokfd调用了 close()。但既然文件描述符都关闭了,客户端为什么还能接收到服务器的 ACK呢?

实际上调用close()表示应用层的数据已经发送完成,缓冲区的内容也已经清理完毕了,但是对于操作系统来说,还需要维持一段时间的TCP可靠性,所以连接并没有完全关闭。

为什么是四次挥手而不是三次挥手?

在三次握手中,使用了SYN+ACK的方式保证了最小建立可靠连接的次数。那么,为什么断开连接不能使用 FIN+ACK的方式,使得四次挥手变成三次挥手呢?

由于断开连接是由应用层调用的,应用层此时并不知道底层的通信在进行着怎么样的数据传输,如果服务器端一接收到客户端发来的 FIN,就立马也准备关闭连接,那么可能会导致一方可能在确认关闭连接之前未能发送完所有数据。因为虽然说客户端知道自己已经没有数据要发送,但不代表服务器端就没有数据要发送给客户端,所以四次挥手确保一方发送完数据后再关闭连接,避免数据丢失和确保双方都完全关闭,维持TCP的可靠性和有序性。

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向上没有数据流动,一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

在套接字的库中,提供了一个关闭sockfd的函数 shutdown()

#include <sys/socket.h>

int shutdown(int sockfd, int how)

shutdown()colse() 的不同在于,shutdown()how 变量用于控制如何关闭文件。填入 SHUT_RD 表示关闭读,填入 SHUT_WR 表示关闭写,填入 SHUT_WDRW的效果与 cOlse()相同(完全关闭文件)。而且,shutdown() 没有计数器机制,一个进程使用shutdown() 对一个文件进行关闭操作,其他所有进程都会受到影响。

4.3 TIME_WAIT状态

TCP协议规定,使用TCP的程序如果使用 Ctrl+c终止程序或正常退出,属于主动关闭连接,而主动关闭连接的一方要处于 TIME_WAIT 状态,等待两个 MSLMaximum Segment Lifetime,报文最长存活时间)的时间后,才能回到 CLOSED 状态。

MSLRFC1122中规定为两分钟,但是各操作系统的实现不同, 在 Centos7、Ubuntu上默认配置的值是 60s

使用 cat /proc/sys/net/ipv4/tcp_fin_timeout 可以查看系统默认 MSL 的值。

之所以 TIME_WAIT 的时间是 2MSL,是因为:

  • MSL 是报文的理论上的最大存活时间,在 2MSL 内能保证双方所有尚未被接收的或迟到的都已经消失。

    否则服务器重启后,可能会收到上一个进程的迟到报文,虽然说这种数据很可能是错误的。

  • 理论上能保证最后一个报文可靠到达。

    假设最后一个 ACK 丢失, 那么服务器会再重发一个 FIN. 这时虽然客户端的进程不在了, 但是 TCP 连接还在, 仍然可以重发 LAST_ACK 。

TIME_WAIT状态在服务器关闭,但客户端还保持连接时,如果服务器想使用原来的端口号重启进程就会发生 bind 错误。但一个服务器端可能需要处理非常大量的客户端的连接,服务器主动关闭连接,就会产生大量的 TIME_WAIT 连接。如果新来的客户端连接的ip和端口号和 TIME_WAIT 占用的链接重复了,就会出现问题。

要想跳过 TIME_WAIT 状态,套接字也提供了一个函数接口,来设置socket的具体属性:

#include <sys/types.h>

#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

optname 设置为 SO_REUSEADDR (值为1),表示允许创建端口号相同但IP地址不同的多个 socket 描述符。

4.4 CLOSE_WAIT状态

如果TCP连接通信结束后,没有 close() 套接字,导致四次挥手无法完成。此时 TCP 连接就会处于 CLOSE_WAIT 状态。由于进程结束后,TCP连接还要处理善后工作,所以即使程序退出,处于 CLOSE_WAIT 状态的 TCP 并不会退出,造成内存泄漏。所以在进行编码时,必须要防止没有 close() 套接字的错误,如果系统中存在大量的 CLOSE_WAIT 连接,也要进行释放。

5. 滑动窗口

在先前的确认应答机制中提到,TCP 通信的双方,对于每发的一个数据段,都要有一个 ACK 应答。但真实的情况中,不可能是“接收到一个 ACK 再发送下一个数据段”,这样效率太低。

TCP10

真实的情况,一定是同时发送多个数据段,再同时接受多个 ACK 确认应答:

TCP11

一次发送窗口大小的数据段,然后等待接收 ACK,等到接收到第一个 ACK 后,窗口向后滑动,继续发送第五段数据段,这便是滑动窗口。操作系统内核为了维护这个滑动窗口,需要使用 发送缓冲区 来记录当前有哪些数据没有被应答,只有确认应答过的数据,才能从缓冲区删除。

窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,图示窗口大小为 4000 字节(四个段)。窗口越大,则网络的吞吐率就越高。但窗口大小也不是不变的(下面细谈)。

TCP12

窗口滑动示图

在线性表描述的滑动窗口中,表中的下标表示字节序号下标。其中窗口左边表示已发送已接收应答的数据包,窗口中表示已发送待接收应答的数据包,窗口右边表示未发送的数据包。所以说,滑动窗口只能向右边移动。

在实际通信中,接收方是有接收缓冲区的,那么接收方的接收能力就一定是动态的,所以滑动窗口的大小也不是一成不变的,那么发送方如何得知接收方的接收能力呢?

5.1 滑动窗口丢包情况

在超时重传机制中提过,无论是数据包丢失还是 ACK 丢失,发送方都会补发一份数据包给彼端。那么在滑动窗口的机制下,TCP又要如何处理丢包呢?

5.1.1 ACK丢失

如果是应答部分丢失,那么发送端无需重新补发,因为根据后续 ACK 的情况已经可以知道,对方收到了数据包,只是对方的 ACK 丢失了而已。滑动窗口机制使得双方通信中由于 ACK 丢包引起的超时重传大大减少。

TCP13

图中主机B 1001 的ACK丢失,但主机A接收到了 2001 的ACK,说明在主机A的视角中,主机B确实收到了 1-1000 的数据包,也就无需补发了。

5.1.2 数据包丢失(高速重发控制)

如果是数据包丢失,接收端一次接收多个数据包,固然知道数据包的序号在什么范围内有缺失。此时接收端会暂时放弃对正常接收到的数据包做 ACK 应答,而是反复发送丢包序号的 ACK ,告知发送端发送的数据包出现了丢包,需要补发。接收端接收到三次相同的ACK时,就知道自己的哪一个数据包发生了丢包,需要补发。

虽然说这段时间里接收端没有对正常接收到的数据做 ACK ,但这些数据确实被收到了,存储在接收端的接收缓冲区中。接收端接收到补发的数据包后,这个补发的数据包的 ACK 会跳过正常被接收到的数据包(只要其连续),直接告知发送端窗口滑动后的数据序号,所以发送端也无需对接收端正常接收到的数据包补发,因为根据上面 ACK 丢失的案例,补发数据包的ACK能证明其他数据包已经被正常接收

这种机制被称为高速重发控制,也叫快重传

TCP14

图中发生数据包丢失时,接收端会发送三次以上相同的 ACK 来提醒发送端,我要的数据包是“1001-2000”的数据。接收端在接收到三次相同的 ACK 时(从丢包后开始算),就知道自己发送的数据包丢失了。不等接收到窗口大小数量的 ACK,就对丢包的 1001 − 2000 1001-2000 10012000 的数据包进行补发。等待接收到补发数据包的ACK后,再进行滑动窗口(此时正常的数据包的 ACK 肯定到达或丢失),而这个补发数据包的ACK,是正常窗口滑动后的数据包的序号,因为参考上面 ACK 丢失的案例,这个 ACK 能证明其他的数据包已经正常被接收到了。

对于数据包的丢失,无所谓丢包的是在滑动窗口的最左边、中间、最右边。因为滑动窗口的滑动在收到对应字节序号时进行,无论是中间丢包,还是最右边丢包,随着时间推进窗口向右滑动,都会变成最左边丢包的情况。

当最左边丢包时,滑动窗口此时的左侧不再右移,等待最左侧的数据包补发后,才会更新位置。

高速重发机制是否和超时重传机制矛盾?

高速重发机制是接收到三次以上相同的 ACK 才会触发的重发机制,而超时重传的重发机制依据的是时间。在三次握手和四次挥手中,高速重发机制并不参与,也就没有矛盾的说法。

而在正常通信中,如果数据包丢失触发了接收端的高速重发机制,但告知需要重发的 ACK 丢失到不足三个,那么发送端就无法触发高速重发来补发数据包,此时就需要超时重传机制让发送方主动补发数据包。或者数据包发生了大量的丢失,不足以触发三次以上的ACK应答机制,也需要超时重传机制,让发送方主动补发数据包。所以两钟机制是可以共存的。

注意,数据包大量丢失会触发拥塞控制机制(慢启动、拥塞避免等),来缓解网络拥塞情况,所以实际情况会更复杂。

6. 流量控制

滑动窗口解决了数据包如何快速发送的问题,但接收端接收数据的速度是有限的,如果发送端发送的太快,导致接收端的接收缓冲区打满溢出,此后继续接收到的数据包将全部丢包。流量控制则是要解决怎么控制滑动窗口发送速度的问题。

TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就做流量控制(Flow Control)

6.1 16位窗口大小

在TCP报头中,16 位窗口大小用来描述自己的接收能力(缓冲区剩余空间大小),滑动窗口的窗口大小通过 ACK 应答的 16 位窗口大小计算出来,发送方就能控制下一轮窗口大小,不至于发送的数据包超过对方的接收能力造成丢包的问题。

窗口大小字段越大,说明网络的吞吐量越大。而当接收端发现自己的接收缓冲区快满了,就会将窗口大小设置成设置成一个更小的值发送给发送端,让发送端调小自己的发送量。如果接收端缓冲区满了,就会将窗口置为 0 。这时发送方不再发送数据,,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。

16 位窗口大小中的变量 int win_start = ack_seqint win_end = win_start + win 分别记录了窗口大小的字节序范围。而窗口的向右移动,实际就是对 win_start++win_end++。所以想要控制滑动窗口的变大和变小,只需要控制这两个变量的差值即可。

在三次握手的过程中,双方发给对方的 ACK 中的窗口大小就已经交换了双方的接收能力,所以在第一轮数据包中,滑动窗口的大小就已经得知了。

其他

6.2 探测窗口

当窗口大小变为 0 ,此时接收端的接收缓冲区已经满载。发送端要暂时停止发送数据包,接收端需要时间来给接收缓冲区腾出空间。当接收端的接收缓冲区腾出空间后,会给发送端发送一个窗口更新通知的报文,让发送端设置滑动窗口发送下一轮数据包。

但这个窗口更新通知有可能需要很长时间才能发出,或在网络传输的过程中丢包。所以发送端会等待一个超时重传时间,如果时间到了还没有收到窗口更新通知,就会给接收端发一个窗口探测,让接收端回应此时的窗口大小是多少。

TCP16

7. 拥塞控制

TCP 有了滑动窗口来提升发送数据包的速度,也有流量控制机制来控制发送数据包的速度,但这两者只是解决了通信双方的效率和可靠性。在实际的网络中,存在很多很多的计算机,如果此时网络已经很拥堵,它们在不清楚网络状态的情况下,认为自己或对方的接收缓冲区还绰绰有余,就进行大量的数据发送,就有可能造成网络更加拥堵。

所以 TCP 还需要一个机制,用来控制或缓解多个 TCP 同时通信时造成的拥塞问题,这种机制就是拥塞控制。

7.1 慢启动

所谓慢启动,就是在刚开始通信时,先发送少量的数据,探探网络的拥塞情况,再决定按照多大的速度传输数据。

在这里插入图片描述

但是这种增长是指数级增长,一直按 2 n 2^n 2n 增长下去也不合理,且先前提到的窗口大小的概念也没用了。

所以这里还需要引入一个概念,拥塞窗口。在发送数据的开始,将拥塞窗口设为 1 ,发送一个数据包。每接收到一个 ACK 应答,拥塞窗口就增加 1 。在发送新一轮的数据包时,将此时的拥塞窗口大小和接收端反馈的窗口大小作比较,取较小值作为实际发送的窗口大小

同时,慢启动的中后期增长幅度太大,不能单纯让拥塞窗口加倍。所以这里需要引入一个慢启动的阈值当拥塞窗口超过这个阈值的时候,拥塞窗口就不再指数级增长,而是改为线性增长。

TCP18

在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回 1。少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为是网络拥塞。

7.2 延迟应答

如果接收端在收到数据后立马返回 ACK ,此时返回的窗口大小可能就会比较小(因为接收缓冲区此时还什么数据都没读走),所以 ACK 的时机可以迟那么一会儿。一般延迟应答有两个因素要考虑:

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

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

对于数量限制和时间限制,依据操作系统不同而不同,但一般 N2,超时时间取 200ms

7.3 捎带应答

接收端不一定需要专门发一个空报头的 ACK 来应答,有时候 ACK 可以搭其他带有数据内容的报文坐顺风车,一起发给发送端。

8. TCP粘包问题

粘包,指的是数据包之间没有明确的边界。

对于UDP来说,由于UDP的报文有明确的长度,数据也是一个一个交给上层的,具有明确的数据边界。即UDP报数据要么是0个、1个、2个,没有半个数据。故使用UDP,无需在应用层解决粘包问题。

但是对于TCP来说,虽然站在传输层角度,TCP报文也是一个一个过来,按照序号排好放在缓冲区中等待读取或发送。但站在应用层的角度,看到的只是一串串连续的字节流,在应用层无法区分哪个部分从哪开始,是一个完整的应用层数据包。故使用TCP,需要在应用层解决粘包问题,规定两个包的边界。

  • 对于定长的数据包,保证每次都按固定大小读取即可。
  • 对于变长的数据包,则需要在包头位置,规定一个包总长度的字段,从而知道整个包的大小;也可以在包与包之间使用明确的分隔符。(如序列化和反序列化)

9. TCP异常情况

  • 进程终止:进程终止会释放文件描述符,也会发送 FIN ,对于TCP来说和正常关闭没有什么区别。

  • 机器重启/关机:正常关闭机器都需要等待操作系统处理善后工作,此时就是操作系统在关闭程序,也有关闭TCP连接的流程,和正常关闭没有区别。

  • 拔网线/死机:这种情况TCP连接就无法走正常关闭流程,没有四次挥手。此时对方端还认为这个TCP连接正常通信,只是我们一直没有给对方发送数据。在TCP协议中,即使双方没有什么通信往来,TCP连接也要保持很久(几十分钟到几个小时)。

    但是TCP也内置了一个保活定时器。当对方不再发报文给我们时,会定期询问对方连接是否还在,如果对方没有应答,也会把连接释放掉。

    另外,应用层的某些协议,也有一些这样的检测机制。例如 HTTP 长连接中,也会定期检测对方的状态。例如 QQ,在QQ断线之后,也会定期尝试重新连接。

10. 全连接队列

全连接队列是服务器端在TCP连接的三次握手成功后,临时存放已完成连接请求的一个队列。临时存放,直到服务器调用accept() 将连接从队列中提取出来,进行后续的处理操作。

三次握手建立连接的过程和用户是否调用 accept() 无关。实际上,三次握手是 conncet() 发起的,在服务器来不及调用accept()的时候,底层的TCP listen sock允许用户继续进行三次握手,建立连接,连接成功后存储在全连接队列里。

服务器有可能是太忙而没有调用 accept(),当服务器解除忙碌调用 accept() 后,会立马去全连接队列里,将新的 sockfd 提取出来。

每个服务器套接字(每个监听的端口)都有自己的全连接队列,全连接队列并不是无限的,具有一个上限,这个上限就是 backlog + 1。全连接队列的数量由 listen() 的第二个参数 int backlog 决定,全连接队列的节点不能为空(不能一个都没有),所以调用listen()设置全连接队列的数量时,其数量必是 backlog + 1

全连接队列不能为空,会增加服务的闲置率,减少给用户提供服务的效率和体验,也不能太长,浪费空间,用户体验不好。

11. 半连接队列

半连接队列,是TCP针对存放TCP三次握手未完成的连接的队列,这个队列里的连接一般只会存在几十秒到几分钟,并不是很重要。

12. 其他

12.1 16位紧急指针

16位紧急指针标识了哪部分数据是紧急数据,而TCP报头中的 URG 标志位标识了16位紧急指针是否有效。16位紧急指针的内容是报文的起始位置的偏移量,标识了从报文开始的多少字节之后是紧急数据。

注意这里的“指针”并不是c语言中的地址,单纯是一个具有指向性的概念。

TCP中的紧急数据只有一个字节。 一般之所以这么小,是因为它通常用来告知对方突然的变更。如应用层在下载文件时,传输层TCP在发送大量的数据包。此时用户突然进行“暂停下载”或“删除任务”操作,携带紧急指针和有效URG的报头就会在接收缓冲区中“插队”进入上层,直接中断传输。

recv()send() 函数中,它们的 flag 参数就可以设置为 MSG_OOB(out-of-band,带外数据)进行传输紧急数据。

TCP19

相关文章:

网络传输层TCP协议

传输层TCP协议 1. TCP协议介绍 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是一个要对数据的传输进行详细控制的传输层协议。 TCP 与 UDP 的不同&#xff0c;在于TCP是有连接、可靠、面向字节流的。具体来说&#xff0c;TCP设置了一大…...

Git 基础——《Pro Git》

⭐获取 Git 仓库 获取 Git 仓库有两种方式&#xff1a; 将未进行版本控制的本地目录转换为 Git 仓库。从其他服务器克隆一个已存在的 Git 仓库。 在已存在目录中初始化 Git 仓库 进入目标目录 在 Linux 上&#xff1a;$ cd /home/user/my_project在 macOS 上&#xff1a;$ c…...

数据结构与算法之二叉树: LeetCode 654. 最大二叉树 (Ts版)

最大二叉树 https://leetcode.cn/problems/maximum-binary-tree/ 描述 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值递归地在最大值 左边 的 子数组前缀上 构建左子树递归地在最大值…...

学习记录:C++宏定义包含多条语句,使用注意事项

应该使用 do - while(0) 结构的情况 在条件语句&#xff08;如 if - else、switch - case&#xff09;或循环语句&#xff08;如 for、while、do - while&#xff09;中使用宏&#xff1a; 当宏定义包含多条语句且会在上述语句中使用时&#xff0c;使用 do - while(0) 可确保…...

PHP 使用 Redis

PHP 使用 Redis PHP 是一种广泛使用的服务器端编程语言,而 Redis 是一个高性能的键值对存储系统。将 PHP 与 Redis 结合使用,可以为 Web 应用程序提供快速的读写性能和丰富的数据结构。本文将详细介绍如何在 PHP 中使用 Redis,包括安装、连接、基本操作以及一些高级应用。 …...

项目开发实践——基于SpringBoot+Vue3实现的在线考试系统(五)

文章目录 一、学生管理模块功能实现1、添加学生功能实现1.1 页面设计1.2 前端功能实现1.3 后端功能实现1.4 效果展示2、学生管理功能实现2.1 页面设计2.2 前端功能实现2.3 后端功能实现2.3.1 后端查询接口实现2.3.2 后端编辑接口实现2.3.3 后端删除接口实现2.4 效果展示二、代码…...

下载并安装MySQL

在Linux系统上下载并安装数据库&#xff08;以MySQL为例&#xff09;的步骤如下&#xff1a; 一、下载MySQL 访问MySQL官网 打开浏览器&#xff0c;访问MySQL的官方网站&#xff1a;https://www.mysql.com/。 进入下载页面 在MySQL官网首页&#xff0c;找到并点击“Downloads…...

【C++入门】详解(中)

目录 &#x1f495;1.函数的重载 &#x1f495;2.引用的定义 &#x1f495;3.引用的一些常见问题 &#x1f495;4.引用——权限的放大/缩小/平移 &#x1f495;5. 不存在的空引用 &#x1f495;6.引用作为函数参数的速度之快&#xff08;代码体现&#xff09; &#x1f4…...

计算机视觉算法实战——车道线检测

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​​​​ ​​​​​​​​​​​​ ​​​​​ 车道线检测是计算机视觉领域的一个重要研究方向&#xff0c;尤其在自动驾驶和高级驾驶辅助…...

基于http协议的天气爬虫

该系统将基于目前比较流行的网络爬虫技术&#xff0c; 对网站上的天气数据进行查询分析&#xff0c; 最终使客户能够通过简单的操作&#xff0c; 快速&#xff0c; 准确的获取目标天气数据。主要包括两部分的功能&#xff0c; 第一部分是天气数据查询&#xff0c; 包括时间段数…...

自然语言处理基础:全面概述

自然语言处理基础&#xff1a;全面概述 什么是NLP及其重要性、NLP的核心组件、NLU与NLG、NLU与NLG的集成、NLP的挑战以及NLP的未来 自然语言处理&#xff08;NLP&#xff09;是人工智能&#xff08;AI&#xff09;中最引人入胜且具有影响力的领域之一。它驱动着我们日常使用的…...

软件架构考试基础知识 002:进程的状态与其切换

进程状态转换的说明 在操作系统中&#xff0c;进程的状态表示其当前的执行情况和资源占用情况。进程状态的转换反映了操作系统如何管理和调度进程。以下是进程状态转换的说明&#xff1a; 1. 三态模型&#xff08;Three-state Model&#xff09; 三态模型是最基础的进程状态模…...

【Linux系列】Curl 参数详解与实践应用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

VsCode对Arduino的开发配置

ps&#xff1a;我的情况是在对esp32进行编译、烧录时&#xff0c;找不到按钮&#xff0c;无法识别Arduino文件&#xff0c;适合已经有ini文件的情况。 1.在vscode中安装拓展 2.打开设置&#xff0c;点击右上角&#xff0c;转到settings.json文件 3.复制以下代码并保存 {"…...

【Pandas】pandas Series rtruediv

Pandas2.2 Series Binary operator functions 方法描述Series.add()用于对两个 Series 进行逐元素加法运算Series.sub()用于对两个 Series 进行逐元素减法运算Series.mul()用于对两个 Series 进行逐元素乘法运算Series.div()用于对两个 Series 进行逐元素除法运算Series.true…...

VUE3 自定义指令的介绍

自定义指令的概述 在 Vue 中&#xff0c;自定义指令是一种机制&#xff0c;允许开发者在模板中直接操作 DOM 元素&#xff0c;执行一些低级别的操作。Vue 提供了几个内置指令&#xff08;如 v-if、v-for、v-model 等&#xff09;&#xff0c;但当我们需要一些特定功能时&#…...

RedisDB双机主从同步性能测试

安装redisDB 主节点 apt install redis-server修改配置 /etc/redis/redis.conf bind 0.0.0.0save "" # 禁止RDB持久化 #save 900 1 #save 300 10 #save 60 10000appendonly no # 禁止AOF持久化重启服务 systemctl restart redis-server从节点配置文件 bind 0.…...

【汇编】x86汇编编程寄存器资源心中有数

1. CPU状态及控制寄存器 TR&#xff0c;GDTR&#xff0c;LDTRcr0-cr3EFLAGS 等等 2. 业务计算寄存器&#xff08;我起的名字&#xff09; 业务寄存器用于访问内存、参数传递、数据传递、计算。 段寄存器6个&#xff1a; cs&#xff0c;ds&#xff0c;es&#xff0c;ss&…...

一.项目课题 <基于TCP的文件传输协议实现>

客户端代码 需要cJSON.c文件和cJSON.h文件 在这里插入代码片#include "myheadth.h" #include "myfun.h"#define TIME 10 int sockfd; void heartbeat(int signum) {cJSON* root cJSON_CreateObject();cJSON_AddStringToObject(root,"request"…...

【数据结构学习笔记】19:跳表(SkipList)

介绍 跳表是一个能在 O ( n l o g n ) O(nlogn) O(nlogn)时间完成查找、插入、删除的数据结构&#xff0c;相比于树形结构优点就是很好写&#xff08;所以也用于实现Redis ZSet&#xff09;。其核心思想就是维护一个元素有序的&#xff0c;能随机提升索引层数的链表。最下面一…...

Cocos Creator 3.8 修改纹理像素值

修改的代码&#xff1a; import { _decorator, Component, RenderTexture, Sprite, Texture2D, ImageAsset, SpriteFrame, Vec2, gfx, director, log, math, v2 } from cc;const { ccclass, property } _decorator;ccclass(GradientTransparency) export class GradientTrans…...

【Linux】网络层

目录 IP协议 协议头格式 网段划分 2中网段划分的方式 为什么要进行网段划分 特殊的IP地址 IP地址的数量限制 私有IP地址和公有IP地址 路由 IP协议 在通信时&#xff0c;主机B要把数据要给主机C&#xff0c;一定要经过一条路径选择&#xff0c;为什么经过路由器G后&…...

单片机Day1

目录 一.什么是单片机&#xff1f; 二.单片机的组成 三.封装形式 四.优势 五.分类 通用型&#xff1a; 专用型&#xff1a; 按处理的二进制位可以分为&#xff1a; 六.应用&#xff1a; 七.发展趋势 1.增加CPU的数据总线宽度。 2.存储器的发展。 3.片内1/0的改进 …...

django基于 Python 的考研学习系统的设计与实现

以下是对Django基于Python的考研学习系统的设计与实现&#xff1a; 一、系统概述 Django基于Python的考研学习系统是一个为考研学子提供一站式学习辅助的平台。它整合了丰富的学习资源、学习计划制定、学习进度跟踪以及交流互动等功能&#xff0c;旨在满足考生在备考过程中的…...

openCvSharp 计算机视觉图片找茬

一、安装包 <PackageReference Include"OpenCvSharp4" Version"4.10.0.20241108" /> <PackageReference Include"OpenCvSharp4.runtime.win" Version"4.10.0.20241108" /> 二、准备两张图片 三、编写代码 using OpenCv…...

深入学习 Python 爬虫:从基础到实战

深入学习 Python 爬虫&#xff1a;从基础到实战 前言 Python 爬虫是一个强大的工具&#xff0c;可以帮助你从互联网上抓取各种数据。无论你是数据分析师、机器学习工程师&#xff0c;还是对网络数据感兴趣的开发者&#xff0c;爬虫都是一个非常实用的技能。在本文中&#xff…...

【Web安全】SQL 注入攻击技巧详解:UNION 注入(UNION SQL Injection)

【Web安全】SQL 注入攻击技巧详解&#xff1a;UNION 注入&#xff08;UNION SQL Injection&#xff09; 引言 UNION注入是一种利用SQL的UNION操作符进行注入攻击的技术。攻击者通过合并两个或多个SELECT语句的结果集&#xff0c;可以获取数据库中未授权的数据。这种注入技术要…...

【DAPM杂谈之一】DAPM作用与内核文档解读

本文主要分析DAPM的设计与实现 内核的版本是&#xff1a;linux-5.15.164&#xff0c;下载链接&#xff1a; Linux内核下载 主要讲解有关于DAPM相关的知识&#xff0c;会给出一些例程并分析内核如何去实现的 /****************************************************************…...

计算机网络之---防火墙与入侵检测系统(IDS)

防火墙与入侵检测系统(IDS) 防火墙&#xff08;Firewall&#xff09; 和 入侵检测系统&#xff08;IDS, Intrusion Detection System&#xff09; 都是网络安全的关键组件&#xff0c;但它们的作用、功能和工作方式有所不同。 防火墙 防火墙是网络安全的一种设备或软件&#…...

HTML中meta的用法

学习网络空间安全专业&#xff0c;每个人有每个人的学法和选择。不论他选择什么&#xff0c;哪都是他自己的选择&#xff0c;这就是大多数视频教学的博主教学的步骤都不同原因之一。有人选择丢掉大部分理论直接学习网安&#xff0c;而我&#xff0c;选择了捡起大部分理论学习网…...

前端学习-事件流,事件捕获,事件冒泡以及阻止冒泡以及相应案例(二十八)

目录 前言 事件流与两个阶段说明 说明 事件捕获 目标 说明 事件冒泡 目标 事件冒泡概念 简单理解 阻止冒泡 目标 语法 注意 综合示例代码 总结 前言 梳洗罢&#xff0c;独倚望江楼。过尽千帆皆不是&#xff0c;斜晖脉脉水悠悠。肠断白蘋洲 事件流与两个阶段说明…...

国产OS移植工业物联网OPC-UA协议

国家对于工业互联网、基础软件等关键领域的重视程度不断提升&#xff0c;为工业领域的硬件与软件国产化提供了坚实的政策保障。国产操作系统对工业物联网的一些重要领域的适配支持一直在推进。本次通过国产UOS系统移植测试OPC-UA协议。 1、OPC UA通信协议 OPC UA 协议&#xf…...

第25章 汇编语言--- 信号量与互斥锁

信号量&#xff08;Semaphore&#xff09;和互斥锁&#xff08;Mutex&#xff0c;全称Mutual Exclusion Object&#xff09;是两种用于管理对共享资源的访问的同步机制。它们在多线程或多进程编程中非常重要&#xff0c;可以确保同一时间只有一个线程或进程能够访问特定的资源&…...

写个自己的vue-cli

写个自己的vue-cli 1.插件代码2. 发布流程3. 模板代码讲解3.1 vue2模板的运行流程:3.2 vue3模板的运行流程: 1.插件代码 写一个自己的vue-cli插件 插件地址&#xff1a;插件地址 流程&#xff1a; 实现简单版 vue-cli 步骤文档1. 项目初始化 - 创建项目文件夹 qsl-vue-cli - …...

使用new Vue创建Vue 实例并使用$mount挂载到元素上(包括el选项和$mount区别)

new Vue({...}) 是创建一个新的 Vue 实例的方式。你可以通过传递一个选项对象来配置这个实例。常见的选项包括&#xff1a; •data&#xff1a;定义组件的数据属性。 •el&#xff1a;指定 Vue 实例应该挂载到哪个 DOM 元素上&#xff08;通常是一个选择器字符串&#xff0c;如…...

【理论】测试框架体系TDD、BDD、ATDD、MBT、DDT介绍

一、测试框架是什么 测试框架是一组用于创建和设计测试用例的指南或规则。框架由旨在帮助 QA 专业人员更有效地测试的实践和工具的组合组成。 这些指南可能包括编码标准、测试数据处理方法、对象存储库、存储测试结果的过程或有关如何访问外部资源的信息。 A testing framewo…...

机器学习全流程解析:数据导入到服务上线全阶段介绍

目录 1. 数据导入 2. 数据预处理 3. 超参数搜索与优化 4. 模型训练 5. 模型评估 6. 模型压缩与优化 7. 模型注册与版本管理 8. 服务上线与部署 总结 1. 数据导入 数据源&#xff1a;数据库、文件系统、API等。数据格式&#xff1a;CSV、JSON、SQL 数据库表、Parquet …...

shell脚本练习

1、shell 脚本写出检测 /tmp/size.log 文件如果存在显示它的内容&#xff0c;不存在则创建一个文件将创建时间写入。 if [ -f /tmp/size.log ];thencat /tmp/size.logelsestat exist.sh | awk -F: "NR5" > /tmp/size.logfi ​ 2、写一个 shel1 脚本,实现批量添加…...

金山WPS Android面试题及参考答案

说说你所知道的所有集合?并阐述其内部实现。 在 Android 开发(Java 语言基础上)中有多种集合。 首先是 List 集合,主要包括 ArrayList 和 LinkedList。 ArrayList 是基于数组实现的动态数组。它的内部有一个数组来存储元素,当添加元素时,如果数组容量不够,会进行扩容操作…...

分类模型为什么使用交叉熵作为损失函数

推导过程 让推理更有体感&#xff0c;进行下面假设&#xff1a; 假设要对猫、狗进行图片识别分类假设模型输出 y y y&#xff0c;是一个几率&#xff0c;表示是猫的概率 训练资料如下&#xff1a; x n x^n xn类别 y ^ n \widehat{y}^n y ​n x 1 x^1 x1猫1 x 2 x^2 x2猫1 x …...

高等数学学习笔记 ☞ 单调性、凸凹性、极值、最值、曲率

1. 单调性 1. 单调性定义&#xff1a;设函数在区间上有定义&#xff0c;对于区间上任意两点&#xff0c;若&#xff1a; ①&#xff1a;当时&#xff0c;恒有&#xff0c;则称函数在区间上单调递增。 ②&#xff1a;当时&#xff0c;恒有&#xff0c;则称函数在区间上单调递减…...

【操作系统】详解操作系统及其结构

考察频率难度40%--60%⭐⭐ 这又是一类面试考察题&#xff0c;是关于操作系统的一些概念问题&#xff0c;很少会单独拎出来作为一个问题进行提问&#xff0c;但却是必须要掌握的。因为如果这个你不会&#xff0c;其他的问题就已经没有回答的必要了。 什么是操作系统&#xff1…...

primitive 的 Appearance编写着色器材质

import { nextTick, onMounted, ref } from vue import * as Cesium from cesium import gsap from gsaponMounted(() > { ... })// 1、创建矩形几何体&#xff0c;Cesium.RectangleGeometry&#xff1a;几何体&#xff0c;Rectangle&#xff1a;矩形 let rectGeometry new…...

自动化测试框架搭建-接口数据结构设计

目的 确认数据库如何保存接口数据&#xff0c;既有扩展性&#xff0c;数据又全又好用 根据用途设计数据库字段 区分环境&#xff1a;可以明确当前接口自动化用例&#xff0c;是在哪个环境需要执行的 模块&#xff1a;微服务架构&#xff0c;不同测试同学负责不同的模块&…...

Python自学 - 使用自定义异常

<< 返回目录 1 Python自学 - 使用自定义异常 在Python中&#xff0c; 不仅可以使用内置异常&#xff0c;用户还可以创建自己的异常。自定义异常需要继承自Exception类或其子类&#xff0c;如下是一个自定义异常示例&#xff1a; 示例1&#xff1a;一个简单的自定义异常…...

微信小程序-Docker+Nginx环境配置业务域名验证文件

在实际开发或运维工作中&#xff0c;我们时常需要在 Nginx 部署的服务器上提供一个特定的静态文件&#xff0c;用于域名验证或第三方平台验证。若此时使用 Docker 容器部署了 Nginx&#xff0c;就需要将该验证文件正确地映射&#xff08;挂载&#xff09;到容器中&#xff0c;并…...

“AI智能服务平台系统,让生活更便捷、更智能

大家好&#xff0c;我是资深产品经理老王&#xff0c;今天咱们来聊聊一个让生活变得越来越方便的高科技产品——AI智能服务平台系统。这个系统可是现代服务业的一颗璀璨明珠&#xff0c;它究竟有哪些魅力呢&#xff1f;下面我就跟大家伙儿闲聊一下。 一、什么是AI智能服务平台系…...

【PPTist】插入形状、插入图片、插入图表

一、插入形状 插入形状有两种情况&#xff0c;一种是插入固定的形状&#xff0c; 一种是插入自定义的形状。 插入固定的形状时&#xff0c;跟上一篇文章 绘制文本框 是一样一样的&#xff0c;都是调用的 mainStore.setCreatingElement() 方法&#xff0c;只不多传的类型不一…...

云集电商:数据库的分布式升级实践|OceanBase案例

电商行业对数据库有哪些需求 云集电商作为一家传统电商企业&#xff0c;业务涵盖了美妆个护、服饰、水果生鲜、健康保健等多个领域&#xff0c;在创立四年后在纳斯达克上市&#xff08;股票代码&#xff1a;YJ&#xff09;。与京东、淘宝、拼多多等电商平台不同&#xff0c;云…...

OOM排查思路

K8S 容器的云原生生态&#xff0c;改变了服务的交付方式&#xff0c;自愈能力和自动扩缩等功能简直不要太好用。 有好的地方咱要夸&#xff0c;不好的地方咱也要说&#xff0c;真正的业务是部署于容器内部&#xff0c;而容器之外&#xff0c;又有一逻辑层 Pod 。 对于容器和…...