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

16位源端口号: 表示数据从哪个进程来。
16位目的端口号: 表示数据到哪个进程去。
32位序号: todo
32位确认序号: todo
4位TCP报头长度: 表示该TCP报头有多少个32bit(4字节),所以TCP报头的最大长度是 4 ∗ 15 = 60 4*15=60 4∗15=60 字节。
6位标志位:
- URG: 紧急指针是否有效。
- ACK(Acknowledge character):确认号是否有效。
- PSH: 提示接收端应用程序立即从TCP缓冲区把数据读走。详细->PSH在流量控制的应用
- RST(Reset,连接重置标志位):对方要求重新建立连接。收到该标志的主机,要对异常连接重新释放,重新建立。(携带 RST标识的报文称为 复位报文段)
- SYN(Synchronize 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为每个需要传输的数据的字节都进行了编号,主机向对方传输了多少数据,只需要告诉对方编号的范围即可。

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

但是在网络通信中,每一份发送端报文或接收端报文可能不会都安全地到达。如果发生了丢包,TCP要如何应对呢?
3. 超时重传机制
两个主机之间在进行网络传输时,可能会因为网络拥堵等原因造成 TCP 报丢失。在上面确认应答的机制的情况下,如果其中一份 TCP 报丢失了,TCP 就需要对丢失的 TCP 报进行补发。但首先,发送端要先知道接收端丢失了哪一份数据。
3.1 如何处理重传?

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

如果丢失的是应答报文,那么主机 A 迟迟等到不到带有下一份应发序列号的应答,就会认为先前发的报文丢失了(主机A并不知道丢失的是自己的报文还是对方的应答报文),向 B 主机重新发送一份与上一份相同的报文。
所以,无论是发送端报文丢失,还是接收端的应答报文丢失,其处理方法都是一样的。都是发送端重新传一份没有正常经过确认应答机制的报文。
并且如果是应答报文丢失,主机 B 可能会接收到多个相同的报文,TCP 协议就需要具有去重的功能,而这个去重的功能就是由上面(确认应答机制)提到的 TCP 为每个需要传输的数据的字节都进行了编号来实现的。
现在我们知道了超时重传的重传机制,那么 TCP 又是如何判断、处理超时的呢?
3.2 如何处理超时?
最理想的情况是找到一个最小的时间,保证 “确认应答一定能在这个时间内返回”。但这个时间的长短一定是动态的,因为随着网络环境的不同,网络通信的速度肯定也不同。如果超时时间设置得太长,会影响整体重传的效率;如果超时时间设置得太短,有可能发送端会频繁发送重复报文。
Linux 中(BSD Unix 和 Windows 也是如此),超时以500ms为一个单位进行控制,每次判断超时重传的超时时间都是 500ms 的整数倍。
如果判断超时重传一次后,仍然得不到应答,下次超时重传就是
2*500ms以后,再下次就是4*500ms,以此类推。当重传累计到一定的次数后,TCP 认为网络或者对端主机出现异常,就会强制关闭连接。
4. 连接管理机制
TCP 是一个有连接的协议。那么 TCP 是如何保证连接是可靠的呢?
TCP 的通信全过程:

服务器端状态转换:
[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]服务器收到了对FIN的ACK,彻底关闭连接。
客户端状态转换:
[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]客户端要等待一个2MSL(Max Segment Life,报文最大生存时间)的时间,才会进入 CLOSED 状态。
4.1 三次握手
TCP 在进行建立连接时,假设是客户端主动向服务器建立连接:

- 首先客户端向服务器进行 SYN 请求建立连接。
- 服务器接收到客户端的请求后,向客户端发送报文,这个报文除了 SYN 以外,还有客户端对服务器的 ACK 。
- 客户端在接收到服务器的 SYN+ACK 后,此时对于客户端来说连接已经建立完成,所以在下一个报文中,对服务器的 SYN 的 ACK 报文中,是可以携带数据的(称为捎带报文,下面细讲)。
- 服务器在接收到客户端的 ACK 后,对于服务器来说,此时连接也建立完成。
注意在三次握手中,
accept()函数并没有参与到三次握手当中,当连接建立完成时,accept()才会返回。
服务器和客户端建立连接的时间,有一个非常短暂的时间差,客户端比服务器要早一个报文。那么假如最后一个 ACK丢包了怎么办?
此时对于客户端来说,连接已经建立完成(ESTABLISHED),是可以正常发数据的。客户端进入正常通信状态,向服务器发送第报文,随后服务器就受到报文,此时服务器还没有进入 ESTABLISHED 状态,但服务器端认得发送该报文的IP和端口,就是刚才还没有完成连接建立(对服务器来说)的客户端。于是服务器对客户端报文的应答就不会是 ACK ,而是 RST(reset,连接重置标志位),表示我服务器这里的三次握手还没有完成,需要重新走三次握手的操作。
如果客户端是浏览器,触发 RST 的效果是这样:
TCP三次握手的目的:
-
验证全双工——验证网络的连通性。
全双工,即同时既能读又能写。之所以要握三次手,就是因为双方都要验证自己既能对对方写(发送 SYN),又能对对方读(接收 ACK)。
-
建立双方通信的共识意愿。
并不是说客户端想要和服务器建立连接,服务器就必须要与其建立连接。在访问网页时,也能常常看到网页拒绝访问的错误。
4.2 四次挥手
四次挥手,别名连接终止协议,是TCP双方断开连接的一种机制。TCP的四次挥手是进行可靠断开连接的最小次数。

客户端主动退出连接(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 状态,等待两个 MSL(Maximum Segment Lifetime,报文最长存活时间)的时间后,才能回到 CLOSED 状态。
MSL 在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在 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 再发送下一个数据段”,这样效率太低。

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

一次发送窗口大小的数据段,然后等待接收 ACK,等到接收到第一个 ACK 后,窗口向后滑动,继续发送第五段数据段,这便是滑动窗口。操作系统内核为了维护这个滑动窗口,需要使用 发送缓冲区 来记录当前有哪些数据没有被应答,只有确认应答过的数据,才能从缓冲区删除。
窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,图示窗口大小为 4000 字节(四个段)。窗口越大,则网络的吞吐率就越高。但窗口大小也不是不变的(下面细谈)。

窗口滑动示图
在线性表描述的滑动窗口中,表中的下标表示字节序号下标。其中窗口左边表示已发送已接收应答的数据包,窗口中表示已发送待接收应答的数据包,窗口右边表示未发送的数据包。所以说,滑动窗口只能向右边移动。
在实际通信中,接收方是有接收缓冲区的,那么接收方的接收能力就一定是动态的,所以滑动窗口的大小也不是一成不变的,那么发送方如何得知接收方的接收能力呢?
5.1 滑动窗口丢包情况
在超时重传机制中提过,无论是数据包丢失还是 ACK 丢失,发送方都会补发一份数据包给彼端。那么在滑动窗口的机制下,TCP又要如何处理丢包呢?
5.1.1 ACK丢失
如果是应答部分丢失,那么发送端无需重新补发,因为根据后续 ACK 的情况已经可以知道,对方收到了数据包,只是对方的 ACK 丢失了而已。滑动窗口机制使得双方通信中由于 ACK 丢包引起的超时重传大大减少。

图中主机B 1001 的ACK丢失,但主机A接收到了 2001 的ACK,说明在主机A的视角中,主机B确实收到了 1-1000 的数据包,也就无需补发了。
5.1.2 数据包丢失(高速重发控制)
如果是数据包丢失,接收端一次接收多个数据包,固然知道数据包的序号在什么范围内有缺失。此时接收端会暂时放弃对正常接收到的数据包做 ACK 应答,而是反复发送丢包序号的 ACK ,告知发送端发送的数据包出现了丢包,需要补发。接收端接收到三次相同的ACK时,就知道自己的哪一个数据包发生了丢包,需要补发。
虽然说这段时间里接收端没有对正常接收到的数据做 ACK ,但这些数据确实被收到了,存储在接收端的接收缓冲区中。接收端接收到补发的数据包后,这个补发的数据包的 ACK 会跳过正常被接收到的数据包(只要其连续),直接告知发送端窗口滑动后的数据序号,所以发送端也无需对接收端正常接收到的数据包补发,因为根据上面 ACK 丢失的案例,补发数据包的ACK能证明其他数据包已经被正常接收。
这种机制被称为高速重发控制,也叫快重传。

图中发生数据包丢失时,接收端会发送三次以上相同的 ACK 来提醒发送端,我要的数据包是“1001-2000”的数据。接收端在接收到三次相同的 ACK 时(从丢包后开始算),就知道自己发送的数据包丢失了。不等接收到窗口大小数量的 ACK,就对丢包的 1001 − 2000 1001-2000 1001−2000 的数据包进行补发。等待接收到补发数据包的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_seq和int win_end = win_start + win分别记录了窗口大小的字节序范围。而窗口的向右移动,实际就是对win_start++、win_end++。所以想要控制滑动窗口的变大和变小,只需要控制这两个变量的差值即可。
在三次握手的过程中,双方发给对方的 ACK 中的窗口大小就已经交换了双方的接收能力,所以在第一轮数据包中,滑动窗口的大小就已经得知了。
其他:
6.2 探测窗口
当窗口大小变为 0 ,此时接收端的接收缓冲区已经满载。发送端要暂时停止发送数据包,接收端需要时间来给接收缓冲区腾出空间。当接收端的接收缓冲区腾出空间后,会给发送端发送一个窗口更新通知的报文,让发送端设置滑动窗口发送下一轮数据包。
但这个窗口更新通知有可能需要很长时间才能发出,或在网络传输的过程中丢包。所以发送端会等待一个超时重传时间,如果时间到了还没有收到窗口更新通知,就会给接收端发一个窗口探测,让接收端回应此时的窗口大小是多少。

7. 拥塞控制
TCP 有了滑动窗口来提升发送数据包的速度,也有流量控制机制来控制发送数据包的速度,但这两者只是解决了通信双方的效率和可靠性。在实际的网络中,存在很多很多的计算机,如果此时网络已经很拥堵,它们在不清楚网络状态的情况下,认为自己或对方的接收缓冲区还绰绰有余,就进行大量的数据发送,就有可能造成网络更加拥堵。
所以 TCP 还需要一个机制,用来控制或缓解多个 TCP 同时通信时造成的拥塞问题,这种机制就是拥塞控制。
7.1 慢启动
所谓慢启动,就是在刚开始通信时,先发送少量的数据,探探网络的拥塞情况,再决定按照多大的速度传输数据。

但是这种增长是指数级增长,一直按 2 n 2^n 2n 增长下去也不合理,且先前提到的窗口大小的概念也没用了。
所以这里还需要引入一个概念,拥塞窗口。在发送数据的开始,将拥塞窗口设为 1 ,发送一个数据包。每接收到一个 ACK 应答,拥塞窗口就增加 1 。在发送新一轮的数据包时,将此时的拥塞窗口大小和接收端反馈的窗口大小作比较,取较小值作为实际发送的窗口大小。
同时,慢启动的中后期增长幅度太大,不能单纯让拥塞窗口加倍。所以这里需要引入一个慢启动的阈值,当拥塞窗口超过这个阈值的时候,拥塞窗口就不再指数级增长,而是改为线性增长。

在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回 1。少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为是网络拥塞。
7.2 延迟应答
如果接收端在收到数据后立马返回 ACK ,此时返回的窗口大小可能就会比较小(因为接收缓冲区此时还什么数据都没读走),所以 ACK 的时机可以迟那么一会儿。一般延迟应答有两个因素要考虑:
数量限制:每个N个包就应答一次
时间限制:超过最大延迟时间就应答一次
对于数量限制和时间限制,依据操作系统不同而不同,但一般 N 取 2,超时时间取 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,带外数据)进行传输紧急数据。

相关文章:
网络传输层TCP协议
传输层TCP协议 1. TCP协议介绍 TCP(Transmission Control Protocol,传输控制协议)是一个要对数据的传输进行详细控制的传输层协议。 TCP 与 UDP 的不同,在于TCP是有连接、可靠、面向字节流的。具体来说,TCP设置了一大…...
我的前端面试笔记(React篇)
1.React16 的 Fiber 是什么 fiber是react16的一种数据结构,用来描述每一个React 组件或 DOM 节点,并采用双向链表的存储结构。遍历节点的方式采用迭代遍历(while循环),替代了以前的递归遍历。 fiber的优点 1ÿ…...
Qt 5.14.2 学习记录 —— 팔 QWidget 常用控件(3)
文章目录 1、cursor2、font3、toolTip4、focusPolicy5、styleSheeyt 1、cursor 改变鼠标光标形状。 在Qt Designer界面中,拖一个按钮过来,右边属性面用户可以自己改cursor属性。 代码方法,先拖一个按钮到界面上: #include <…...
HTTP协议和WebSocket协议
传统的HTTP协议是单向通信的,客户端若想要与服务器进行通信,首先需要通过三次握手与服务器建立TCP连接,然后再向服务器发送数据请求,当客户端收到服务器响应回来的数据后,就会断开TCP连接,每次数据请求都需…...
校园网断网自检测重链接
由于博主比较懒,所以经常喜欢在宿舍通过todesk远程控制工位电脑办公。但是由于交专的校园网经常断连,也不好意思一直麻烦同门帮忙连网,于是参考runepic博主的链接,实现了工位电脑的校园网断网自检测重链接功能。 参考链接…...
LAMP搭建
LAMP搭建 引子:本篇文章为LAMP的搭建流程,其中L(Ubuntu)、A(Apache)、M(Mysql)、P(PHP)。 一、L → Ubuntu Step 1:在Vmware Workstation中使…...
Sentinel-5P遥感数据下载及预处理教程【20250105】
Sentinel-5P是欧空局(Europe Space Agency,ESA)于2017年10月13日发射的一颗全球大气污染监测卫星。卫星搭载了对流层观测仪(Tropospheric Monitoring Instrument,TROPOMI),可以有效的观测全球各…...
手写@MapperScan
定义一个EnableMapperScan注解 Import(MapperProxyHandlerRegister.class) 标注将MapperProxyHandlerRegister导入到容器中。 Target({ElementType.METHOD,ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Import(MapperProxyHandlerRegister.class) public interface…...
【C++】深入理解迭代器(Iterator)
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 💯前言💯什么是迭代器?迭代器与指针的比较 💯std::string 中的迭代器示例代码与图示分析运行结果:图示说明: 小提示 💯正…...
后端Java开发:第十二天
第十二天:封装 - 理解与应用 欢迎来到今天的学习内容!今天,我们将一起深入探讨 Java 中的 封装(Encapsulation)。封装是面向对象编程的四大基本特性之一,它的核心思想是把对象的状态(属性&…...
记录一下Coding一直不能clone
配置 下载git客户端,进行配置 git config --list user.name姓名全称 user.emailIAM_xxxxxx.com ,这个就是你的邮箱地址 user.signingkey 注册coding平台的密码 一般不需要配置公钥私钥 下载TortoiseGit,配置这几个参数 配置凭据管理器 注意 这里用户名是…...
LLM加速方法,Adapter Tuning和Prompt Tuning的区别及原理举例
LLM加速方法 目录 LLM加速方法整体结构各分支内容Adapter Tuning和Prompt Tuning的区别及原理举例一、区别二、总结整体结构 基于模型微调、基于模型压缩、基于分布式并行处理。 各分支内容 基于模型微调: 包含Adapter Tuning、Prompt Tuning、LoRA三个子类别。这些技术主要…...
【SVN】版本发布快捷操作
摘要:因为每次发版都需要制作一份相同的文件夹,而大部分的包都不需要变更,但是文件又非常大,记录自己的操作经验。 首先在SVN Repository Browser 界面把上一次的版本复制一份,复制的时候重命名为新的版本号 右击要复…...
GitLab 创建项目、删除项目
1、创建项目 点击左上角图标,回到首页 点击 Create a project 点击 Create blank project 输入项目名称,点击Create Project 创建成功 2、删除项目 进入项目列表 点击对应项目,进入项目 进入Settings页面 拖到页面底部,展开Adva…...
STM32-笔记37-吸烟室管控系统项目
一、项目需求 1. 使用 mq-2 获取环境烟雾值,并显示在 LCD1602 上; 2. 按键修改阈值,并显示在 LCD1602 上; 3. 烟雾值超过阈值时,蜂鸣器长响,风扇打开;烟雾值小于阈值时,蜂鸣器不响…...
VisionPro软件Image Stitch拼接算法
2D图像拼接的3种情景 1.一只相机取像位置固定,或者多只相机固定位置拍图,硬拷贝拼图,采用CopyRegion工具实现 2.一只或多只相机在多个位置拍照,相机视野互相重叠,基于Patmax特征定位后,无缝 拼图ÿ…...
【从零开始入门unity游戏开发之——unity篇09】unity6基础入门——Unity游戏对象和组件的本质、Unity中的反射机制
文章目录 一、Unity游戏对象和组件的本质1、开发游戏的本质2、万物之根本——空对象2.1 什么是空对象?2.2 创建空对象 3、Unity游戏对象的本质4、 transform组件4.1 transform组件的重要性4.2 修改transform的值4.4 **Transform的作用**4.3 重置transform的值 5、总…...
【Linux】深入理解文件系统(超详细)
目录 一.磁盘 1-1 磁盘、服务器、机柜、机房 📌补充: 📌通常网络中用高低电平,磁盘中用磁化方向来表示。以下是具体说明: 📌如果有一块磁盘要进行销毁该怎么办? 1-2 磁盘存储结构 编辑…...
MoEs and Transformers 笔记
ref:https://huggingface.co/blog/zh/moe#%E7%94%A8router-z-loss%E7%A8%B3%E5%AE%9A%E6%A8%A1%E5%9E%8B%E8%AE%AD%E7%BB%83 MoEs and Transformers Transformer 类模型明确表明,增加参数数量可以提高性能,因此谷歌使用 GShard 尝试将 Transformer 模型…...
在Linux中,如何禁用root用户直接SSH登录?
在Linux中禁用root用户的直接SSH登录是为了增强系统的安全性,因为允许root用户通过SSH远程登录会增加服务器被暴力破解的风险。以下是在Linux系统中禁止root用户直接SSH登录的步骤: 编辑SSH配置文件: 打开/etc/ssh/sshd_config文件ÿ…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

