【Linux】【网络】传输层协议:TCP

news2024/12/22 23:17:58

文章目录

  • TCP 协议
      • 1. TCP 协议段格式
      • 2. TCP 报头解析
      • 3. TCP 的可靠性
      • 4. 面向字节流
      • 5. 粘包问题
      • 6. 连接队列维护
  • TCP 的 确认应答机制
  • TCP 的 超时重传机制
  • TCP 的 三次握手
  • TCP 的 四次挥手
      • setsockopt 函数:设置套接字选项,解决 TIME_WAIT 状态引起的 bind 失败
  • TCP 的 流量控制
  • TCP 的 滑动窗口
  • TCP 的 拥塞控制:慢启动机制 和 阈值
  • TCP 的 延迟应答
  • TCP 的 捎带应答
  • TCP 的 异常情况
  • 小结

TCP 协议

TCP(Transmission Control Protocol 传输控制协议)

  • 传输层协议。
  • 有连接:处于通信之前,也就意味着三次握手是不携带有效信息的。
  • 可靠传输:有确认机制如 收到应答、超时重传、三次握手、四次挥手…
  • 面向字节流:有对应的以字节为单位的缓冲区收发数据以供解析。

对比 UDP:

UDP(User Datagram Protocol 用户数据报协议)传输的过程类似于寄信。
- 传输层协议。
- 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接。
- 不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息。
- 面向数据报: 不能够灵活的控制读写数据的次数和数量。

1. TCP 协议段格式

TCP 的首部有一块定长大小为 20 字节的空间字段,用于报头信息的填写。后面接着的是一块储存选项的空间,也可能没有选项信息。

既然不确定选项字段的情况,如何分离报头和有效载荷呢?

  • 在 TCP 前 20 字节的定长字段之中有一个记录首部长度的空间,这部分空间大小是 4 个比特位,而其中记录的单位大小是 4 字节,即可以记录 60 字节的长度,前 20 是定长的,后 40 就是给头部选项的。

  • 所以整个首部的大小 = 首部长度字段 * 4 - 20,如果 == 0,则报头读取完毕,剩下的就是有效载荷
    在这里插入图片描述


2. TCP 报头解析

源 / 目的端口号

  • 表示数据是从哪个进程来,到哪个进程去

32 位序号 / 32 位确认号

  • 对报文的编号,和确认报文的编号(见 TCP 的可靠性)

4 位 TCP 报头长度

  • 表示该 TCP 头部有多少个 32 位 bit(有多少个 4 字节); 所以TCP头部最大长度是15 * 4 = 60

6 个重要标志位

  • ACK - - acknowledge:该报文是一个确认报文,确认报文可能会携带数据,携带数据的确认报文的应答叫做 捎带应答。

  • SYN – sync:请求建立连接,我们把携带 SYN 标识的称为 同步报文段

  • FIN:请求断开连接,通知对方,本端要关闭了,我们称携带 FIN 标识的为 结束报文段

  • RST - - reset:对方要求重新建立连接,我们把携带 RST 标识的称为复位报文段。

  • PSH - - push:提示接收端应用程序立刻从 TCP 缓冲区把数据读走。

  • URG - - urgent:标识紧急指针字段是否有效。

16 位窗口大小

  • 发送自己端接收缓冲区的剩余空间大小,以作 流量控制,不会导致对方发太快太多导致丢包,也可以避免发的太慢效率低下的问题出现。

16 位校验和

  • 发送端填充,CRC 校验。接收端校验不通过,则认为数据有问题,直接丢弃。此处的检验和不光包含 TCP 首部,也包含 TCP 数据部分。

16 位紧急指针

  • 是一个偏移量,标识哪部分数据是紧急数据(一个字节的状态码,这样的数据也叫做外带数据)。

40 字节头部选项:见后文。

其中 紧急数据 的处理,可以在 recv 和 send 中设置:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数 flags:

  • MSG_OOB:允许 接收 / 发送 非正常流中的 紧急数据
但在实际使用中 URG 使用的很少,因为他能携带的信息量实在有限,
更多的是多开一个端口号,拿到并处理特殊信息

3. TCP 的可靠性

不可靠的情况有很多,比如:丢包、乱序、重复、校验失败、发送太快/太慢、网络问题…

客户端发送请求,在收到服务器响应,才可以 100% 的确保对方是收到的,也就是说 可靠性,是通过收到应答机制保证的。如此也说明,我们无法保证任何报文都是可靠送达,但可以局部保持可靠性。

实际上客户端可以同时对服务器发送多个请求报文,经过网络的传输,到达服务器的情况却不一样,应对不同的报文送达情况,客户端有不同的应对机制,判断哪个报文是哪个报文就是很有必要的。所以报文里会携带两种编号:序号、确认序号。确认序号是收到序号 +1 来设定的。两个序号在一段报文中注定是更高效的,所以在报头中有各自不同的字段空间。

序号的设置保证了 TCP 的可靠性。

4. 面向字节流

创建一个 TCP 的 socket,相当于同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区

  • 调用 write 时,数据会先写入发送缓冲区中。

  • 如果发送的字节数太长,会被拆分成多个 TCP 的数据包发出;如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去。

  • 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区。

  • 然后应用程序可以调用 read 从接收缓冲区拿数据。

  • 另一方面,TCP 的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于这一个连接,既可以读数据,也可以写数据。这个概念叫做 全双工

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

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

5. 粘包问题

首先要明确,粘包问题中的 “包”,是指的应用层的数据包。

在 TCP 的协议头中,没有如同 UDP 一样的 “报文长度” 这样的字段,但是有一个序号这样的字段。

  • 站在传输层的角度,TCP 是一个一个报文过来的。按照序号排好序放在缓冲区中。
  • 站在应用层的角度,看到的只是一串连续的字节数据。那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包。

避免粘包问题, 归根结底就是, 明确两个包之间的边界

  • 对于定长的包,保证每次都按固定大小读取即可。例如上面的 Request 结构,是固定大小的,那么就从缓冲区从头开始按 sizeof(Request) 依次读取即可。

  • 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置。

  • 对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议, 是程序员自己来定的, 只要保证分隔符不和正文冲突即可)

对于 UDP 协议来说,是否也存在 “粘包问题” 呢?

  • 对于 UDP,如果还没有上层交付数据,UDP 的报文长度仍然在。同时,UDP 是一个一个把数据交付给应用层。就有很明确的数据边界。
  • 站在应用层的站在应用层的角度,使用 UDP 的时候,要么收到完整的UDP报文,要么不收,不会出现 “半个” 的情况。

6. 连接队列维护

Linux 内核协议栈为一个 TCP 连接管理使用两个队列。

  1. 半链接队列(用来保存处于 SYN_SENT 和 SYN_RECV 状态的请求)
  2. 全连接队列(accepted 队列)(用来保存处于established状态,但是应用层没有调用accept取走的请求)

全连接队列:管理已连接但还没发起任务的客户端。为了保证服务器的使用率,全连接队列的维护是必须维护的。也要控制这个队列不能太长。

  • 因为可能会过多消耗 OS 本来是给 server 使用的资源,且队列太长 client 会不愿意等。
  • 实际上更有效率的应该是 提高 网络吞吐量。
  • 至于队列究竟要维护多长,需要看各自应用场景。

半连接队列:管理半连接状态的队列。

TCP 协议,需要在底层维护 全连接队列,最大长度是:listen() 的第二个参数 +1


TCP 的 确认应答机制

在这里插入图片描述

在确认应答机制中,TCP 首先将每个发出的数据,在发送缓冲区中都以字节为单位进行了编号,即序列号。每一个 ACK 都带有对应的确认序列号(序列号 +1),意思是告诉发送者,我已经收到了哪些数据,下一次你从哪里开始发。

例如,主机 A 和主机 B 通信:
A to B:发送数据(1~1000)
B to A:确认应答(收到了,下一个从 1001 开始发)
A to B:发送数据(1001~2000)
B to A:确认应答(收到了,下一个从 2001 开始发)
...

在字节单位的缓冲区中不断的读取和放置,就是 TCP 的特征之一,面向字节流。


TCP 的 超时重传机制

主机 A 发送数据给 B 之后,可能因为网络拥堵等原因,数据无法到达主机B。如果主机 A 在一个特定时间间隔内没有收到 B 发来的确认应答,就会进行重发。

例如,主机 A 和主机 B 通信:
A to B:发送数据(1~1000)
// 一段时间没有收到 B 的确认应答,A 判定丢包,开始重传
A to B:发送数据(1~1000)
B to A:确认应答(收到了,下一个从 1001 开始发)

因此主机 B 会收到很多重复数据,那么 TCP 协议需要能够识别出那些包是重复的包,并且把重复的丢弃掉。这时候报头里的序列号,就可以很容易实现去重。

超时的时间如何确定呢?

  • 最理想的情况下,找到一个最小的时间,保证“确认应答一定能在这个时间内返回”。但是这个时间的长短,随着网络环境的不同,是有差异的:如果超时时间设的太长,会影响整体的重传效率;如果超时时间设的太短,有可能会频繁发送重复的包。

TCP 为了保证无论在任何环境下都能比较高性能的通信,此会动态计算最大超时时间。

  • Linux 中(BSD Unix 和 Windows 也是如此),超时以 500ms 为一个单位进行控制,每次判定超时重发的超时时间都是 500ms 的整数倍。如果重发一次之后,仍然得不到应答,等待 2500ms 后再进行重传。如果仍然得不到应答,等待 4500ms 进行重传。依次类推, 以指数形式递增。累计到一定的重传次数,TCP 认为网络或者对端主机出现异常,强制关闭连接。

TCP 的 三次握手

整个 TCP 三次握手建立连接,传递信息,四次挥手断开连接的过程,包括应用层的建立。我们需要关注的是传输和两端的状态变化,流程图如下:
在这里插入图片描述

三次握手的过程,由双方的 OS 系统中的 TCP 层自主完成。

客户端:connect,触发连接,等待完成
服务器:accept,等待建立完成,获取连接

为什么握手是三次?

  • 没有明显的设计漏洞(如果是 2 次会造成客户端零成本连接,服务器可能收到连接攻击而崩溃),一旦建立连接出现异常,成本嫁接到 client 端,server 端成本较低(因为最后一次发送可能丢失,如果偶数次握手,server 是不确定丢包与否即建立成功与否的)。

  • 可以验证双方通信信道的通畅情况,三次握手是验证全双工通信信道通畅的最小成本。

三次握手除了确认信道畅通建立连接,还有什么用?

  • 通过 16 位窗口大小进行流量控制。首先我们要知道,TCP 是面向连接的,处于正常通信之前,三次握手是不携带有效数据的,也就是说,两端第一次传输数据不是第一次通信。
  • 在三次握手阶段,首先,客户端在发起连接请求时,除了 SYN 标志位被置 1,客户端也一定会将自己的 16 位窗口大小通告给服务器,以便服务器知道该客户端的承载能力。
  • 其次,服务器响应 SYN + ACK 标志位被置 1,服务器的 16 位窗口大小也会被填上。这样双方的承载能力就都被对方知晓了,不会在第一次报文传输的时候出现流量异常导致的一系列问题。

服务器端状态转化:

  • [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] 客户端要等待一个 2 MSL(Max Segment Life,报文最大生存时间)的时间, 才会进入 CLOSED 状态。

TCP 的 四次挥手

综合上述的描述,对四次挥手进一步做些解释。

四次挥手对于主动断开的一方 进行最后一次确认后,要进入 TIME_WAIT 状态,这是一个临时性状态,持续一会就没了。

为什么挥手是四次?

  • 建立连接后,两端就是同等地位了,一方断开连接都需要另一方确认。

TIME_WAIT 状态的细节:

  • 当主动断开的一方进入 TIME_WAIT 状态时,连接确实就断开了,但底层这个连接还没有被彻底关掉,相应的端口号还在被 TIME_WAIT 状态占用,在被占用的这段时间里,这个端口号就是不能使用的。

  • 如果是 server 端发起断开连接并处于 TIME_WAIT 状态,而 server 端经不起等待或者更换端口时,就可以调用下面的接口,对套接字进行相应的选项设置,无视其TIME_WAIT 状态,重新使原端口可以被有效使用。

setsockopt 函数:设置套接字选项,解决 TIME_WAIT 状态引起的 bind 失败

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
                void *optval, socklen_t *optlen);  
int setsockopt(int sockfd, int level, int optname,
                const void *optval, socklen_t optlen);

参数 level:

  • 要设的属性在哪一层

参数 optname:

  • 要设的是哪一个属性

  • SO_REUSEADDR:无视网络中的 TIME_WAIT 状态。主动关闭连接的一方处于 TIME_WAIT 的时候,允许新的连接重新绑定与 TIME_WAIT 状态的连接有冲突的 IP + PORT(),并立即接收数据。

  • SO_REUSEPORT: SO_REUSEADDR 对完全相同的IP+PORT绑定(无论是具体的IP还是通配)仍然出现Address already in use的错误,使用SO_REUSEPORT选项可以避免此错误。

参数 optval:

  • 属性的值设成多少

参数 optlenl:

  • 属性长度是多少

·

TIME_WAIT 状态有什么用?

  • 当退出端退出时,或有正在传输的信息并未到达对端,TIME_WAIT 的存在,就是为了让已退出端尚未完成传输的信息消散,目的是不影响对端 ACK 的序号不被先前信息影响,保证下次同个端口号发送的数据能够被正常响应。

  • 另一方面,是为了保证退出端的最后一次 ACK 被对端收到。


TCP 的 流量控制

之前提到过,接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送就会造成丢包,继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做 流量控制(Flow Control)

  • 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段,通过ACK端通知发送端。窗口大小字段越大, 说明网络的吞吐量越高。

  • 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端。发送端接受到这个窗口之后,就会减慢自己的发送速度。

  • 如果接收端缓冲区满了,就会将窗口置为0,这时发送方不再发送数据,但是需要定期发送一个 窗口探测 数据段,使接收端把窗口大小告诉发送端。

  • 窗口探测:发送方定期发出一个 TCP 报头,接收方必须 对所有请求进行应答(TCP 的协议内容),于是返回应答中的 16位 窗口字段就可以让发送方知道,什么时候可以继续发送有效载荷。

在TCP首部中,有一个 16 位窗 口字段,用来存放窗口大小信息。

那么问题来了,16 位数字最大表示 65535, 那么 TCP 窗口最大就是 65535 字节么? 

实际上, TCP 首部 40 字节选项中还包含了一个 窗口扩大因子 M,实际 窗口大小是 窗口字段的值左移 M 位


TCP 的 滑动窗口

在这里插入图片描述
对应确认应答策略,对每一个发送的数据段,都要给一个ACK确认应答,收到ACK后再发送下一个数据段。如此串行工作,性能较差,尤其在数据往返的时间较长的时候更甚。

一发一收的方式性能较低,只要一次发送多条数据,就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)。

  • 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,上图的窗口大小就是4000个字节(四个段)。

  • 发送前四个段的时候,不需要等待任何 ACK,直接发送。

  • 收到第一个 ACK 后,滑动窗口向后移动,继续发送第五个段的数据,依次类推。

  • 操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉。

  • 窗口越大, 则网络的吞吐率就越高。

一般情况

在这里插入图片描述

  1. 滑动窗口往左边部分,是已经发送已经确认过的无效数据,可以被覆盖

  2. 滑动窗口部分,暂时不用等待收到应答,可以直接发送。其大小和对方的接受能力有关,即应答报文中的窗口大小。

  3. 滑动窗口往右边部分,是尚未发送的数据区域。

如何理解滑动窗口?

在这里插入图片描述

  • 所谓面向字节流的 TCP,他的接收和发送缓冲区都可以理解成一个个 char 类型的 数组。

  • 而滑动窗口在这个数组中的“滑动”,实际是依赖两个指针(这里做 winstart,winend)划定范围,通过 ++ 运算符完成的。

一些规则

  1. “窗口” 只会向右 “滑动”,左边是已经确认过的报文

  2. 一直向右移的规则并不会造成越界,因为发送缓冲区的结构是环状的。

  3. 滑动窗口的大小是浮动的,不是固定大小,而其 变大、变小、变 0 是根据对方给本端响应报头中 窗口大小 来调节的。

     窗口变大通过 winend+=xxx 来完成
     窗口变小 winend-=xxx 来完成
    
  4. 滑动窗口大小的更新,更具体来说,和对方发送的序列号 seq 有关,在应答也是按序到达的前提下:

    根据应答的 seq,winstart = seq;
    根据应答的 win,winend = winstart + win;
    
  5. 滑动窗口内部的报文可以直接发送,多个报文传输,肯定会发生丢失的问题。

    如果第一个丢失了
    		情况1:数据丢了,即使后面的收到了,winstart 也不会向后移动,只是等待发送方超时重传。
    		情况2:应答丢失,可以通过后面传来的响应报文确定,之前的一定接收到了。
    如果中间或最后的丢失了
    		随着 winstart 的右移都会转化成第一个丢失问题,按上述步骤处理。
    
  6. 数据要支持重传,就必须被保存起来,保存的位置就是滑动窗口


TCP 的 拥塞控制:慢启动机制 和 阈值

少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为 网络拥塞

当 TCP 通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降。

当网络拥塞出现大量丢包的情况:发送方不能一直超时重传也不能完全停发。总体策略是,保证网络拥塞不能加重,再网络拥塞有起色的情况下,尽快恢复网络通信。

慢启动 机制,先发少量的数据探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据。

此处引入一个概念程为 拥塞窗口

  • 发送开始的时候,定义拥塞窗口大小为 1

  • 每次收到一个 ACK 应答,拥塞窗口加 1

    只是这样的话,拥塞窗口的大小肯定是指数级上升的,可实际不止如此。
    
  • 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口

    滑动窗口 = min(对端主机的接受能力 win,网络的拥塞窗口)
    winstart = seq;
    winend = min(seq_win, 拥塞窗口);
    

正常来说拥塞窗口增长速度,是指数级别的。“慢启动” 只是指初使时慢,但是增长速度非常快。为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍,需要 慢启动的 阈值 进行控制。

在这里插入图片描述

  • 当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长。
  • 当 TCP 开始启动的时候,慢启动阈值等于窗口最大值
  • 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回 1

拥塞控制,归根结底是 TCP 协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案。


TCP 的 延迟应答

如果接收数据的主机立刻返回 ACK 应答,这时候返回的窗口可能比较小。

假设接收端缓冲区为 1M,一次收到了500K的数据,
如果立刻应答,返回的窗口就是500K。
但实际上可能处理端处理的速度很快,10ms 之内就把 500K 数据从缓冲区消费掉了

在上述情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来。
如果接收端稍微等一会再应答,比如等待 200ms 再应答,那么这个时候返回的窗口大小就是 1M。

在这里插入图片描述

窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。如何选择延迟应答的时机呢?

选择延迟应答的时机,也有不同的方案:

  • 数量限制:每隔 N 个包就应答一次
  • 时间限制:超过最大延迟时间就应答一次

具体的数量和超时时间,依操作系统不同也有差异,一般 N 取 2,超时时间取 200ms。


TCP 的 捎带应答

在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 “一发一收” 的。
意味着客户端给服务器说了 “你好吗”,服务器也会给客户端回一个 “我很好”。那么这个时候 ACK 就可以和服务器回应的 “我很好” 一起回给客户端。

在这里插入图片描述


TCP 的 异常情况

机器重启 / 进程终止:机器重启 / 进程终止会释放文件描述符,仍然可以发送 FIN,和正常关闭没有什么区别。

机器掉电 / 网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行 reset。即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在,如果对方不在,也会把连接释放。

另外,应用层的某些协议,也有一些这样的检测机制

例如 HTTP 长连接中,也会定期检测对方的状态,例如QQ,。
在 QQ 断线之后,也会定期尝试重新连接。

小结

TCP 这么复杂,是因为要保证 可靠性,同时又尽可能的提高性能。

可靠性
校验和
序列号(按序到达)
确认应答
超时重发
连接管理
流量控制
拥塞控制
提高性能:
滑动窗口
快速重传
延迟应答
捎带应答
其他:
定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)

另,基于TCP应用层协议有如下这些:

  • HTTP
  • HTTPS
  • SSH
  • Telnet
  • FTP
  • SMTP
    当然, 也包括我们自己写 TCP 程序时自定义的应用层协议

🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~


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

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

相关文章

力扣2861 补9.21

2861. 最大合金数 好蛮好蛮&#xff0c;我连题目都读不懂了&#xff0c;丝毫不明白咋做。 看了灵神题解&#xff0c;嗯&#xff0c;就好家伙&#xff0c;所有合金都需要由同一台机器制造。题目老是看漏&#xff0c;也就是只能选择其中一个机器造合金&#xff0c;这题能用二分也…

电脑WIFI突然消失

文章目录 1. 现象2. 解决办法1&#xff1a;重新启用无线网卡设置3. 解决办法2&#xff1a;更新无线网卡驱动4. 解决办法3&#xff1a;释放静电5. 解决办法4&#xff1a;拆机并重新插拔无线网卡 1. 现象 如下图&#xff1a;电脑在使用过程中WIFI消失 设备管理器中的无线网卡驱…

Redis安装部署与数据类型

目录 一、数据库类型 二、Redis简介 三、Redis 的优点 Redis 具有以下几个优点&#xff1a; Redis为什么这么快&#xff1f; 四、Redis安装部署 五、Redis 数据库常用命令 Redis 多数据库常用命令 六、Redis数据类型 String数据类型 List数据类型 Hash数据类型&…

【Python基础】常用模块学习:sys|os|pytest

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

数据库原理与分析实验三

目录 1、实验目的 2、实验预习与准备 3、实验内容及步骤 本博客为数据库课布置的实验二的作业。 1、实验目的 &#xff08;1&#xff09; 掌握Select子句的功能和检索数据的方法 &#xff08;2&#xff09; 掌握对查询结果排序的方法 2、实验预习与准备 &#xf…

Python爬虫技术系列-02HTML解析-xpath与lxml

Python爬虫技术系列-02HTML解析-xpath与lxml 2 XPath介绍与lxml库2.1 XPath概述2.2 lxml库介绍2.2.1 lxml库安装2.2.2 lxml库基本使用2.2.3 lxml案例a.读取数据并补全b.读取数据并选取节点&#xff1a; 2 XPath介绍与lxml库 参考连接&#xff1a; XPath教程 https://www.w3sch…

85、Redis连接相关的命令, key相关命令

本次讲解要点&#xff1a; Redis连接相关的命令&#xff0c; key相关命令&#xff0c; 启动redis服务器&#xff1a; 打开小黑窗&#xff1a; C:\Users\JH>e: E:>cd E:\install\Redis6.0\Redis-x64-6.0.14\bin E:\install\Redis6.0\Redis-x64-6.0.14\bin>redis-serve…

「UG/NX」Block UI 选择单元SelectElement

✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#

SpringBoot 学习(三)Web 开发

3. SpringBoot Web 开发 3.1 导入静态资源 (1) webjars 导入 jquery 依赖 <dependency><groupId>org.webjars</groupId><artifactId>jquery</artifactId><version>3.6.0</version> </dependency>访问 jquery.js 文件 http:/…

python将中文标点符号转换成英文标点符号然后再替换成回车符实现换行

一段文字如下&#xff1a; 你发现没,杭州亚运会首个比赛日上午&#xff0c;中国体育代表团竟然狂揽11金&#xff01;这一壮丽景象背后&#xff0c;是中国体育事业的坚实基础和精湛训练的见证。 标点符号都是中文状态下的。现在要替换成英文标点符号。参考了文章&#xff1a; …

Linux基本命令总结练习(过命令关)

1.新建网卡配置文件的软连接NIC1 [rootserver ~]# ln /etc/NetworkManager/system-connections/ens160.nmconnection NIC1 [rootserver ~]# stat /etc/NetworkManager/system-connections/ens160.nmconnection [rootserver ~]# stat NIC1 2.使用普通账户新建如下结构的2个目录&…

Vue中如何进行跨域处理

Vue中的跨域请求处理&#xff1a;解决前端开发中的常见问题 跨域请求是前端开发中常见的问题之一。Vue.js是一款流行的前端框架&#xff0c;如何在Vue中处理跨域请求是每个Vue开发者都需要了解的重要课题。本文将深入探讨什么是跨域请求&#xff0c;为什么它会出现&#xff0c…

burpsuite只有intruder中文乱码

burpsuite 只有intruder模块中文乱码 现象&#xff1a;解决方案 现象&#xff1a; 在proxy、repeater等模块下中文均可正常显示&#xff0c;如下图&#xff1a; 在intruder模块&#xff0c;中文显示乱码 解决方案 在payloads标签下payload processing中添加“Decode”

【跟小嘉学 Rust 编程】三十、Rust 使用 Slint UI

系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…

聊聊并发编程——多线程之synchronized

目录 一.多线程下数据不一致问题 二.锁和synchronized 2.1 并发编程三大特性 2.2引入锁概念 三.synchronized的锁实现原理 3.1 monitorenter和monitorexit 3.2synchronized 锁的升级 3.2.1偏向锁的获取和撤销 3.2.2轻量级锁的加锁和解锁 自适应自旋锁 轻量级锁的解锁…

位运算练习题(Java)

package KeepCoding.algorithm; //位运算知识点 /* 1. 0 ^ x x x ^ x 1 * 2. 位运算满足结合律和交换律&#xff0c;即运算顺序无关 */ //位运算练习题 //1.整数数组中有一个出现次数为奇数的整数&#xff0c;其余整数的出现次数均为偶数个&#xff0c;请找出数组中这位…

oracle

title: “Oracle” createTime: 2021-12-13T16:35:4108:00 updateTime: 2021-12-13T16:35:4108:00 draft: false author: “name” tags: [“oracle”] categories: [“db”] description: “测试的” 时间字段分析 timestamp 精确到秒后面6位 createTime: 2021-12-13T16:35:…

微积分学习笔记(2):用Go语言画函数图像

使用的Go库 gonum.org/v1/plotimage/color 待绘图函数 直接使用三角函 s i n sin sin: func f(x float64) float64 {return math.Sin(x) }绘图过程 1 创建一个绘图 p : plot.New()assert.NotNil(t, p)p.Title.Text "Function Image"p.X.Label.Text "X&qu…

基于微信小程序的中医知识库系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言用户微信小程序端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌…