【网络基础2】深入理解TCP协议:协议段、可靠性、各种机制

news2024/11/27 10:20:47

文章目录

  • 1. TCP协议段格式
    • 1.1. 如何解包 / 向上交付
      • 1.1.1. 交付
      • 1.1.2. 解包
    • 1.2. 如何理解可靠性
      • 1.2.1. 确认应答机制(ACK)
      • 1.2.2. 序号 与 确认序号
  • 2. TCP做到全双工的原因
    • 2.1. 16位窗口大小
    • 2.2. 6个标记位
  • 3. 如何理解连接
    • 3.1 连接管理机制
      • 3.1.1. 三次握手
      • 3.1.2. 四次挥手
        • 什么是 TIME_WAIT / CLOSE_WAIT
  • 4. TCP实现可靠性的方式
    • 4.1. 发送缓冲区
    • 4.2. 超时重传机制
    • 4.3. 流量控制
  • 5. TCP 提高性能的方式
    • 5.1. 滑动窗口
      • 5.1.1. 作用 与 本质
      • 5.1.2. 完善理解 - 模型
      • 5.1.3. 滑动窗口的部分问题
    • 5.2. 快重传(高速重发机制)
    • 5.3. 拥塞窗口(tmp)
    • 5.4. 延迟应答
    • 5.5. 捎带应答
  • 6. 如何理解 TCP面向字节流?
    • 6.1. 粘包问题
    • 6.2. TCP异常情况 状态
  • 7. TCP总结
  • 8. 基于TCP的应用层协议
  • 9. TCP / UDP 对比
  • 10. TCP相关实验 - listen的第二个参数理解

1. TCP协议段格式

下图为TCP协议段格式:

在这里插入图片描述

1.1. 如何解包 / 向上交付

1.1.1. 交付

TCP的报头前两项和UDP一样(交付过程也类似UDP),根据16位目的端口号(源端口号),可以将数据向上交付给进行(进程绑定了端口号)。

1.1.2. 解包

  • 首先根据TCP协议端格式,了解到TCP报头标准长度20字节
  • 在这20字节中存在名为 4位首部长度 的内容, 4位首部长度的范围即 0000 ~ 1111 转十进制即 0 ~ 15,而该字段的单位为4字节,得到TCP报头的长度范围为[20, 60]
  • 下面分析解包的过程
    1. 提取20字节(标准长度)
    2. 根据标准报头,提取4位首部长度 * 4,如果等于20,解包结束,否则继续
    3. 读取(4位首部长度 * 4 - 20)字节数据,即“选项”内容
    4. 此时报头读完,剩余的为有效载荷

根据上面的内容,引出一个疑问,我们知道UDP是面向数据报的,协议段中有整个报文的大小,那么对于TCP面向字节流,协议段中有没有整个报文或是有效载荷的大小呢


1.2. 如何理解可靠性

我们知道TCP是可靠的,而UDP是不可靠的,可靠就是好,不可靠就是坏吗?

  • 网络通信中,可靠性指的是数据传输的确保性和完整性:。
  • 即两台主机/进程通信时,双方能完整正确的获得对方通信的内容。
  • 举个例子
    在这里插入图片描述
    在这里插入图片描述
    此时我们就可以理解这一句话:可靠性指的是数据传输的确保性和完整性。

那么存不存在 100%可靠性的协议呢? —— 不存在!
再次举一个例子
在这里插入图片描述
在这里插入图片描述

  • 在实际的网络通信中,不存在通信一方可以保证自己发送的消息一定会被对方接收到,但是在局部上是可以做到可靠的
  • 在刚刚的例子中:一方发出的消息只要有对应的应答,就证明是被对方接收到了
  • 此时我们引入TCP的确认应答(ACK)机制:只要一个报文收到了对应的应答,就能保证发出的数据被对方收到了。

1.2.1. 确认应答机制(ACK)

我们知道,TCP是全双工 的:通信双方可以同时进行接收和发送数据。

  • 在通信的过程中,客户端向服务端发送了数个消息,服务端都一一应答,如何保证客户端接收到的消息能正确匹配呢?(如果不能正确顺序接收,会发生乱序,也是不可靠的一种)
    在这里插入图片描述
  • TCP协议段中有两个内容:32位序号与32位确认序号
  • 即可以通过序号(给报文编号),保证接收的顺序完整。
    在这里插入图片描述

1.2.2. 序号 与 确认序号

对于 序号 与 确认序号:
在这里插入图片描述

  1. 将请求与应答一一对应
  2. 确认序号表示:某段确认序号之前的数据全部收到
  3. 允许部分”确认“丢失,或不应答
  4. 为什么是两段数字(序号 与 确认序号):任何通信的一方都是全双工,两方在发送确认的同时会携带新的数据。
  5. 乱序问题:1000 -> 2000 -> 3000 ——> 2000 -> 1000 -> 3000
    • 在网络差的时候,发送给别人的消息在己方看着是乱序的。
    • 而实际上任何一方通信时都会收到报文,又报文携带着序号,最后会根据序号进行排序

2. TCP做到全双工的原因

在这里插入图片描述

2.1. 16位窗口大小

两台主机通信的时候,显然 发送方 应确保数据的发送不能过快 / 过慢,如何保证发送放发送数据的速度适中呢?

  • 接收方始终给发送方同步自己的接收能力
  • 我们知道TCP通信时,实际是将数据传输到缓冲区中,自然接收能力与接收缓冲区相关
  • 接收能力由TCP协议段中的 16位窗口大小决定
  • 16位窗口大小 即:接收缓冲区的剩余空间大小

通信双方同时向对方发送自身的接收能力,也是构成全双工的一点。


2.2. 6个标记位

标记位:1bit表示的某种含义

在这里插入图片描述

  1. URG(紧急指针 urgent pointer):当URG为1时,表明紧急指针字段有效,用来指示数据中的紧急数据。
  2. ACK(确认 Acknowledgment):当ACK为1时,表明确认号字段有效,用来确认收到的数据。
  3. PSH(推送 Push):当PSH为1时,表示接收方应该尽快将数据交给应用程序,而不是等到缓冲区满再交付。
  4. RST(复位 Reset):当RST为1时,表示连接复位,用来中断连接。
  5. SYN(同步 Synchronize):在建立连接时,SYN为1表示请求建立连接。
  6. FIN(结束 Finish):当FIN为1时,表示发送方已经发送完数据,并要求释放连接。

3. 如何理解连接

在这里插入图片描述

如上图所示,实际的连接过程中,会有大量的client连接server,自然服务器端会有大量的连接,操作系统如何管理?

  • 先描述,再组织
  • 连接的本质本质是内核的一种数据结构类型,建立连接成功时,在内存中建立相应的连接对象,再对多个连接对象进行某种数据结构组织

3.1 连接管理机制

连接管理机制主要包括 三次握手和四次挥手、以及客户端与服务端的状态变化。

3.1.1. 三次握手

三次握手的过程可以由下图解释:
在这里插入图片描述
不妨考虑一下,为什么TCP协议规定了三次握手,一次、两次、四次…有什么问题吗?

  • 我们知道 维护连接是有成本的(CPU + 内存)
  • 下面我们分析一次、两次、四次握手的缺陷或缺点,最好配合上面三次握手的过程图进行分析:
  • 对于一次握手
    1. 客户端发送连接请求给服务器,服务器接受请求并建立连接,然后发送确认给客户端。而显然存在一个明显的安全问题:无法验证客户端的身份
    2. 当有一台不断的发送SYN给服务端,会不断的在消耗耗服务器的TCP连接资源,最后导致服务不可用或严重延迟(SYN洪水)
  • 对于两次握手
    1. 客户端发送连接请求给服务器,服务器接受请求并建立连接,但服务器不发送确认给客户端。这样,客户端无法确认连接是否已经建立成功
    2. 可能导致客户端误以为连接已经建立成功,从而向服务器发送数据,而服务器并没有准备好接收数据,可能会导致数据丢失或混乱。

而TCP所采用的三次握手的机制较好的解决了这一问题:

  • 第一次握手:客户端发送连接请求给服务器,表明客户端想要建立连接。
  • 第二次握手:服务器接受连接请求,并发送确认给客户端,同时表明服务器也愿意建立连接。
  • 第三次握手:客户端收到服务器的确认,并向服务器发送确认,表明客户端也愿意建立连接。

显然TCP的三次握手:

  1. server端嫁接相同的成本给client端
  2. 验证了全双工

而在保证了客户端服务器通信功能完善的情况下,采用四次握手自然会造成:

  1. 额外的通信开销:四次握手需要额外的一轮通信,相比于三次握手,会增加一定的通信开销和时间延迟。

  2. 复杂性增加:四次握手会增加协议的复杂性,使得实现和管理起来更加繁琐,容易引入错误。

  3. 性能影响:每增加一轮握手,都会增加连接建立的时间延迟,可能对某些应用场景的性能产生影响。

且TCP的三次握手在大多数情况下已经被证明是足够可靠和安全的。


3.1.2. 四次挥手

下图为四次挥手的过程:
在这里插入图片描述

什么是 TIME_WAIT / CLOSE_WAIT

TIME_WAITCLOSE_WAIT分别表示连接已关闭但仍在等待一段时间以确保任何延迟数据包到达或确认

  1. TIME_WAIT:

    • 在TCP连接正常关闭后,主动关闭连接的一方(通常是客户端)会进入TIME_WAIT状态。
    • TIME_WAIT状态下,该端口将等待一段时间(通常是2倍的MSL,最长生存时间,保证历史数据从网络中消散),确保在网络中所有的数据包都已经到达目的地,而对端已经收到并确认了最后一个ACK。
    • 这个等待时间段的主要目的是确保之前的连接中的所有数据包都已经在网络中被处理完毕,避免新连接中混入旧连接的数据包
    • TIME_WAIT状态的连接不能再接收新的数据包,但仍然能够发送和接收一些控制报文(如RST报文)。
  2. CLOSE_WAIT:

    • 当一端关闭连接,但另一端仍然发送数据时,后者(通常是服务器端)会进入CLOSE_WAIT状态。
    • CLOSE_WAIT状态表示本地端已经收到远程端发送的FIN报文,并关闭了连接,但仍然需要等待本地应用程序处理完所有的数据后才能关闭套接字
    • 如果在CLOSE_WAIT状态持续过长时间,可能会导致资源泄漏或连接耗尽的问题。

4. TCP实现可靠性的方式

4.1. 发送缓冲区

如何理解TCP的发送缓冲区,可以将其看作一个char sendbuffer[NUM] 的数组

在这里插入图片描述

所以对于TCP,收到了重复报文,可以通过序号进行去重。


4.2. 超时重传机制

我们来看下图:
在这里插入图片描述

对于上面的情况,当主机A发送给主机B数据,且在特定的时间间隔未收到确认应答(网络拥堵、丢包等),就会重传数据

实际上,未收到确认应答,也可能是由于ACK(应答)丢失

在这里插入图片描述
对于超时重传,超时时间应该如何设置?

  1. 超时时间不能过长、不能过短
  2. 超时时间非固定:网络状况良好时,超时时间短一些;网络状况偏差时,超时时间长一些。
  3. 我们可以了解一下TCP是如何设置超时时长的:
    • Linux中(BSD Unix、Windows同), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍
    • 如果重发一次后, 仍得不到应答, 等待 2*500ms 后再进行重传
    • 如果仍然得不到应答, 等待 4*500ms 进行重传, 依次类推,以指数形式递增
    • 累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接

4.3. 流量控制

流量控制(Flow Control)用于确保发送端发送的数据不会超过接收端的处理能力,从而避免数据丢失、拥塞和重传等问题。

TCP的流量控制机制主要通过 滑动窗口 实现。

  • 接收端会在TCP报文的ACK中通知发送端自己的接收窗口大小,即可接收的数据量。
  • 发送端根据这个接收窗口大小来控制发送数据的速度,确保不会发送超出接收端处理能力的数据量。

首先我们介绍流量控制的相关事项和步骤,后面对滑动窗口进行深度了解:

  • 接收端 将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;
  • 窗口大小字段越大, 说明网络的吞吐量越高;
  • 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
  • 发送端接受到这个窗口之后, 就会减慢自己的发送速度;
  • 如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端

在这里插入图片描述

  • 接收端如何把窗口大小告诉发送端呢?
  • 回忆我们的TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息;
    • 另外,16位数字最大表示65535, TCP窗口最大就是65535字节吗?
    • 实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位

5. TCP 提高性能的方式

5.1. 滑动窗口

5.1.1. 作用 与 本质

首先通过下图理解滑动窗口的作用:将发送缓冲区分成三部分。
在这里插入图片描述
根据上面的图,引出一个问题:滑动窗口在哪里?

  • 滑动窗口在发送缓冲区中,属于发送缓冲区的一部分

滑动窗口的本质是什么?

  1. 滑动窗口内的大小意味着:发送方可以一次性向对方推送的数据上限
  2. 滑动窗口也应有上限:上限由对端的接受能力决定

5.1.2. 完善理解 - 模型

滑动窗口是什么结构?内部结构是什么样的?来看下图:
在这里插入图片描述
滑动窗口滑动的过程:

在这里插入图片描述

5.1.3. 滑动窗口的部分问题

  1. 滑动窗口一定右移吗?—— 不一定
    • 如果对端一直不取数据,滑动窗口的左侧会一直右移(缩小),而右侧不会继续移动。
  2. 滑动窗口可以为0吗?—— 可以
    • 同样的,只要对端一直不取数据,滑动窗口左端一直右移而右端不动,滑动窗口将所有数据发送给对端后,就逐渐减小至0
  3. 如果没有收到中间报文的应答,收到了后面序号的应答,有问题吗?—— 问题不大
    • 收到了后面的报文应答,可以确认前面的也收到了
    • 比如,主机A给主机B发送了1001~2000的数据,而主机B发送的应答是3001,根据确认序号的定义,可以直接令win_start=3001;
  4. 如果是丢了报文呢:
    • 主机A向主机B 发送 1001 ~ 4000,而2001~3000的报文丢失了,主机B会返回1001的确认应答(丢失的部分会暂时存储,等待超时重传)
  5. 滑动窗口一直右移,不会越界吗?—— 不会
    • 滑动窗口根据序号发送数据,前面发送过的数据占用的空间自然就空闲了,如何利用好空间?显然用一个结构就能解决这一问题
    • TCP的发送缓冲区 / 滑动窗口 是环状的,每次只需要执行模运算即可。
  6. 滑动窗口解决的是效率问题还是可靠性问题? —— 效率为主,可靠性为辅
    • 显然滑动窗口的功能是为了提高传输效率,配合着超时重传可以增强可靠性。

5.2. 快重传(高速重发机制)

我们知道,两台主机通信过程中,丢失了中间的ACK问题不大,只要有后面的确认应答(ACK)即可但丢失了数据包(报文)该怎么办呢?:

在这里插入图片描述
上图问快重传的过程,触发条件如图中文字表示:

  • 那么为什么有了快重传,还要有超时重传呢?
    1. 快重传是有触发条件的,两重传方式非对立,而是协作
    2. 上图只是一次报文丢失,如果出现了多次报文不同序号的丢失,此时滑动窗口可以根据两个重传进行移动

5.3. 拥塞窗口(tmp)

  • 通过上面的内容我们知道,有了滑动窗口,TCP可以效可靠的发送大量的数据. 但是如果在通信开始阶段就发送大量的数据, 仍然可能引发问题.

  • 两台主机在通信时,传输效率以及可靠性,需要考虑主机自身的各种因素,而网络更是不可忽略的一大因素:

  • 从宏观角度看,网络并非只有我们考虑的两台主机使用,网络上有很多的计算机,可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能使网络状况更加糟糕的
    在这里插入图片描述

  • TCP引入 慢启动 机制, 先发少量的数据探路, 了解了当前的网络拥堵状态后, 再决定按照多大的速度传输数据

    1. 发送开始的时候, 定义拥塞窗口大小为1;
    2. 每次收到一个ACK应答, 拥塞窗口加1;
    3. 每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口
      在这里插入图片描述

  1. 拥塞窗口加速机制 , 是指数级别的,“慢启动” 指初始时慢,增长速度快。
  2. 为了不增长速度有限制不过快,不能使拥塞窗口单纯的加倍,引入一个叫 慢启动 的阈值。
  3. 当拥塞窗口超过此阈值时,不再按照指数方式增长, 而按照线性方式增长。

在这里插入图片描述
(上图非自制)

  • 对上图进行解释:
  • 当TCP开始启动的时候, 慢启动阈值等于窗口最大值。
  • 在每次超时重发的时候, 慢启动阈值会变成原来的一半,同时拥塞窗口置回1。

少量的丢包, 仅仅触发超时重传,大量的丢包,我们就认为网络拥塞。
当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;
拥塞控制, 实际是TCP协议尽量快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案。


5.4. 延迟应答

我们知道:滑动窗口的大小与拥塞窗口和对方的接收能力有关如果每次接收端收到报文后就立刻发送ACK,此时发送端接收到的窗口就比较小

延迟应答的步骤

  • 假设接收端缓冲区为1M. 一次收到了1000K的数据; 如果立刻应答, 返回的窗口大小就是1000K;
  • 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
  • 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
  • 如果接收端等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;
  • 这种方法可以更好的提升效率,减少网络拥塞

延迟方式

窗口越大,传输效率就越高,我们的目的是在网络不拥堵的前提下提高效率,有两种延迟的方式

  1. 数量限制:每隔N个包就应答一次
  2. 时间限制:超过最大限制时间就应答一次
  3. 具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms;

在这里插入图片描述


5.5. 捎带应答

我们知道:TCP是全双工的

  • 双方通信的过程中,主机B接收消息的同时也会向主机A发送消息,即接收到消息的同时将ACK和待发送的消息捆绑一起发送给主机A。

这个概念是比较好理解的,根据文字理解就好。


6. 如何理解 TCP面向字节流?

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

  • 当调用write写入数据时时, 数据会先写入发送缓冲区中;
  • 如果发送的字节数太长,会被拆分成多个TCP的数据包发出;
  • 如果发送的字节数太短, 就会先在缓冲区里等待,在缓冲区长度合适 或 其他合适的时机发送出去;
  • 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
  • 然后应用程序可以调用read从接收缓冲区拿数据;

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

  • 写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;
  • 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;
  • 即当把字节数据发送给对方时,如何提取数据完全由对端的上层决定,发送端不考虑也不在乎,即面向字节流,
  • 可以整段提取,或者分段提取,应用层的角度看:TCP按照报文序号传送数据,这些数据被看作字节数据(即字节流)

6.1. 粘包问题

  • 粘包问题中的 “包” , 是指的应用层的数据包
  • 我们知道:在TCP的协议头中, 没有像 UDP协议段的 “报文长度” 字段, 但是有“序号”字段
  • 传输层的角度TCP传输数据通过一个个报文,按照序号排序后放在缓冲区中;
  • 站在应用层的角度传输的数据只是一串连续的字节数据
  • 从上面面向字节流我们知道:接收方如何提取数据完全由自己的上层决定,应用程序看到的只是一连串的字节数据, 不知道从哪里分段, 是一个完整的应用层数据包,比如:
  • 如果选择接收1000字节的数据,提取到的是350+350+300:此时就将一个数据包的内容给截断了。这种情况就叫做粘包问题
    在这里插入图片描述

那么如何避免粘包问题?—— 明确两个包的界限

  1. 如果接收端看到的只是一个完整的数据报,自然无从下手,只要有了明确的包与包的界限,就很容易提取数据了
    • 对于定长的包:保证每次都按固定大小读取; 例如上图, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
    • 对于变长的包: 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置。
    • 对于变长的包: 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序员自己定的, 只要保证分隔符不和正文冲突即可)

思考:UDP会不会出现粘包问题?

  1. 当然不!UDP是面向数据报的
  2. UDP一个个将数据交付给应用层,有很明显的界限
  3. 以应用层的角度分析:UDP传输要么接收完,要么不接收,不会出现接收一半的截断状况

6.2. TCP异常情况 状态

  1. 进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN. 和正常关闭没有什么区别。
  2. 机器重启: 与进程终止的情况相同。
  3. 机器掉电 / 网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset
    • 即使没有写入操作, TCP自身也内置了一个保活定时器,会定期询问对方是否还在,如果对方不在, 就把连接释放。
    • 应用层的某些协议, 也有一些这样的检测机制. 例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接

文章上面都介绍了哪些内容?可以看看下面TCP的总结,顺便思考自己是否理清了其内容。

7. TCP总结

可靠性

  • 校验和
  • 序列号(按序到达)
  • 确认应答
  • 超时重发
  • 连接管理
  • 流量控制
  • 拥塞控制

提高性能

  • 滑动窗口
  • 快速重传
  • 延迟应答
  • 捎带应答

其他

  • 定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)

8. 基于TCP的应用层协议

许多 基于TCP的应用层协议 被广泛用于各种网络应用中。这些协议利用TCP的可靠性和连接性来实现各种功能。

  1. HTTP(超文本传输协议):用于在Web服务器和客户端之间传输超文本文档,支持可靠的数据传输和连接性。

  2. SMTP(简单邮件传输协议):用于在邮件服务器之间或邮件客户端与服务器之间传输电子邮件消息。

  3. POP3(邮局协议版本3):用于从邮件服务器上获取电子邮件消息。

  4. IMAP(互联网消息访问协议):类似于POP3,也是用于从邮件服务器上获取电子邮件消息,但提供了更丰富的功能,如在服务器上管理邮件的状态。

  5. FTP(文件传输协议):用于在客户端和服务器之间传输文件。

  6. Telnet(远程终端协议):允许用户通过网络连接到远程主机并执行命令。

  7. SSH(安全外壳协议):用于通过加密的方式在网络上安全地连接到远程主机。

  8. DNS(域名系统):用于将域名解析为IP地址,以便在网络上定位主机和服务。


9. TCP / UDP 对比

首先提出一个问题: 我们知道 TCP是可靠连接,而TCP是不可靠连接,但可靠与否并非直接决定好坏,什么时候使用哪一种连接方式是要分情况讨论的:

  1. TCP用于可靠传输和顺序交付的情形, 应用于文件传输, 重要状态更新等场景;

    • 适用于需要确保数据完整性和可靠性的应用,因为TCP提供了重传、排序和确认等机制。
    • 对延迟要求不是特别敏感的应用,因为TCP的流量控制和拥塞控制机制可能会引入一定的延迟。
    • 适用于数据量较大的传输,因为TCP的滑动窗口机制可以有效地处理大量数据的传输。
  2. UDP用于对高速传输和实时性要求较高,可靠性要求不那么高 的通信领域, 如 音频、视频传输;

    • 适用于广播和多播场景,因为UDP支持多播和广播传输。
    • 适用于短小的数据包传输,因为UDP不提供重传和确认机制,因此对于大量数据或需要可靠传输的数据不太适用。
    • 适用于需要较少的网络开销和较小的头部开销的场景,因为UDP的头部开销较小,没有TCP的连接建立和维护开销。

10. TCP相关实验 - listen的第二个参数理解

根据以上的内容,我们进行一个思考,在我们编写TCP编程的相关代码时, 使用accept函数时,accept会不会参与到三次握手的过程?
答: 不需要参与,先建立好连接后,accept直接获取建立好的连接。

根据上面的分析,可以得到结论: 不需要调用accept就可以建立连接


如果上层来不及调用accept,且对端来了大量连接,怎么办 提前建立好连接?
答:服务器本身要维护一个连接队列,用于存储客户端的连接请求,其中与listen的第二个参数有关。


我们首先通过代码验证上面的结论: “不需要调用accept就可以建立连接”

写一个简单的套接字代码:

Sock.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>

using std::string;

class Sock
{
private:
    // 将listen的第二个参数设为1
    const static int gbacklog = 1; // 监听队列的最大数量
public:
    Sock() {}
    ~Sock() {}

    int Socket() // 创建监听socket
    {
        int listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if(listenSock < 0) // 失败
        {
            // logMessage(FATAL, "socket error");
            exit(2);
        }
        // logMessage(NORMAL, "create socket successfully");
        return listenSock;
    }

    void Bind(int sock, uint16_t port, string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        // local.sin_addr.s_addr = inet_addr(ip.c_str());
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
        if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            // logMessage(FATAL, "bind error | %d : %s", errno, strerror(errno));
            exit(3);
        }
    }

    void Listen(int sock)
    {
        if(listen(sock, gbacklog) < 0)
        {
            // logMessage(FATAL, "listen error | %d : %s", errno, strerror(errno));
            exit(4);
        }
        // logMessage(NORMAL, "init server successfully");
    }

    int Accept(int sock, string* ip, uint16_t* port)
    {
        // 接收连接请求
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int serviceSock = accept(sock, (struct sockaddr*)&src, &len);
        if(serviceSock < 0)
        {
            // logMessage(FATAL, "accept error | %d : %s", errno, strerror(errno));
            exit(5);
        }
        if(port) *port = ntohs(src.sin_port);
        if(ip) *ip = inet_ntoa(src.sin_addr);
        return serviceSock;
    }

    // 连接
    bool Connect(int sock, const string& server_ip, const uint16_t& server_port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());
        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0)
        {
            // logMessage(NORMAL, "connect to %s successfully", server_ip.c_str());
            return true;
        }
        else
            return false;
    }

    // 主动关闭连接
    void Close(int sock)
    { }
};

main.cc

#include "Sock.hpp"

// 不进行accept
int main()
{
    // 创建一个Socket对象
    Sock sock;
    // 创建一个监听Socket,并且绑定到8080端口
    int listenSock = sock.Socket();
    sock.Bind(listenSock, 8080);
        
    // 仅创建一个监听状态的套接字
    // 开始监听
    sock.Listen(listenSock);
    while(true)
    {
        sleep(1);
    }
    return 0;
}

当开启进程后,此时发现进程进入了 监听状态

[usr@folder]:# ./TcpServer
[usr@folder]# netstat -nltp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      12889/./TcpServer   

此时如果我们 用其他主机对当前主机进行telnet命令连接:

[root@iZ0jl4dw0m30ln5nvqiew5Z verifyListenParameters]# netstat -ntp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name                      
tcp        0      1 172.22.99.98:8080      8.130.140.119:23124      ESTABLISHED     
tcp        0      1 172.22.99.98:8080      8.130.140.119:56191      ESTABLISHED     

如果此时再开一个连接,会出现:

[root@iZ0jl4dw0m30ln5nvqiew5Z verifyListenParameters]# netstat -ntp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name                      
tcp        0      1 172.22.99.98:8080      8.130.140.119:23124      ESTABLISHED     
tcp        0      1 172.22.99.98:8080      8.130.140.119:56191      ESTABLISHED     
tcp        0      1 172.22.99.98:8080      8.130.140.119:44191      SYN_RECV   

此时第三个连接的状态为SYN_RECV,我们知道SYN_RECV表示已经收到连接,但暂时不处理。
因为: Linux内核协议栈为一个tcp连接管理使用两个队列:

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

而全连接队列的长度会受到 listen 第二个参数的影响。
全连接队列满了的时候, 就无法继续让当前连接的状态进入 established 状态了。

  • 此时我们就可以解释 listen的第二个参数的意义
    • 底层全连接的长度 = listen的第二个参数 + 1

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

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

相关文章

通俗易懂,Java之Collection接口带你了解集合类型

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

风吸式杀虫灯解析

TH-FD2S风吸式杀虫灯是一种创新且环保的害虫控制设备&#xff0c;它结合了太阳能和风力的双重优势&#xff0c;为农业生产、园林绿化以及居民生活等提供了高效且安全的害虫防治方案。 首先&#xff0c;风吸式杀虫灯的工作原理是利用害虫的趋光性&#xff0c;通过特定的光源吸引…

后仿真中的关于延时问题(物理特性角度)

大家都知道&#xff0c;后仿真讲究仿真时序。那么&#xff0c;在网表阶段&#xff0c;接触到后仿延时问题。今天总结一下。 一 延时概念和分类 1.1 分布式延迟&#xff08;Distributed Delays&#xff09; 一般用来指定模块内部信号通过逻辑单元或者线网耗费的时间。 1.2 模…

【嵌入式必读】一文彻底理解PID自整定及PID自整定代码设计

文章目录 1. 前言2. PID简介3. 常用的PID自整定方法3.1 临界度比例法3.2 衰减曲线法 4. 继电反馈整定法原理4.1 继电反馈自整定的基本思想4.2 继电反馈自整定原理 5. 算法设计5.1 振荡的生成5.2 提取出临界周期 T c T_c Tc​和振荡波形幅值 A A A5.3 计算出PID参数 6 原代码6.1…

SQL Server 存储过程中的字符串本身包含单引号的用法

文章目录 引言I 存储过程中的字符串本身包含单引号的用法1.1 问题1.2解决方法引言 使用场景: 字符串类型字段的值比较 I 存储过程中的字符串本身包含单引号的用法 在SQL Server中,单引号用于表示字符串常量。如果你的存储过程中的字符串本身包含单引号,你需要用两个连续的…

PMP的考试费用是多少啊?大概需要多少钱?

如何以最低的经济成本取得PMP证书呢&#xff1f;PMP的认证考试费用包括考试报名费、学习备考费用和续证费用三个部分。 考试报名费用 PMP考试费用&#xff1a;PMP普通申请者初次考试费用为固定3900元人民币&#xff0c;补考&#xff08;重考&#xff09;费用为2500元人民币。退…

springboot 获取maven打包时间

springboot 获取maven打包时间 pom <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.13.RELEASE</version><relativePath /> <!-- lookup parent…

【Linux系列】file命令

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

C语言常见的动态内存错误及几个经典笔试题以及c/c++内存开辟空间等的介绍

文章目录 前言一、常见的动态内存错误1. 对NULL指针的解引用操作2. 对动态开辟空间的越界访问3. 对非动态开辟内存使用free()4. 使用free释放一块动态开辟内存的一部分5. 对同一块动态内存多次释放6. 动态开辟内存忘记释放&#xff08;内存泄漏&#xff09; 二、几个经典笔试题…

【busybox记录】【shell指令】shuf

目录 内容来源&#xff1a; 【GUN】【shuf】指令介绍 【busybox】【shuf】指令介绍 【linux】【shuf】指令介绍 使用示例&#xff1a; 打乱内容 - 默认输出 打乱内容 - 最多输出n行 打乱内容 - 将输出写入文件 打乱内容 - 重复输出 打乱内容 - 打乱本条指令的参数 打…

Concise CoT(CCoT)提示词工程

原文地址&#xff1a;concise-chain-of-thought-ccot-prompting 2024 年 1 月 24 日 传统的 CoT 是以增加输出令牌使用为代价的&#xff0c;CCoT 提示是一种提示工程技术&#xff0c;旨在减少 LLM 响应的冗长和推理时间。 基于LLMs的生成式人工智能应用程序必须使用多管齐下的方…

静态分配IP,解决本地连接不上Linux虚拟机的问题

在Window环境下&#xff0c;使用远程终端工具连接不了VMware搭建的Linux虚拟机&#xff08;CentOS 7&#xff09;&#xff0c;并且在命令行ping不通该Linux虚拟机的IP地址。下面通过配置网关解决本地与Linux虚拟机连接问题&#xff1a; 1 查看虚拟机网关地址 在VMware虚拟机上…

文本清洁器:如何一键批量删除空格,让内容更整洁的技巧

在日常工作和学习中&#xff0c;我们经常需要处理大量的文本内容。而文本中多余的空格往往会让内容显得杂乱无章&#xff0c;影响阅读体验。为了解决这个问题&#xff0c;我们可以使用办公提效工具来一键批量删除空格&#xff0c;让内容更加整洁易读。 一、为什么需要批量删除空…

js宏任务微任务输出解析

第一种情况 setTimeout(function () {console.log(setTimeout 1) //11 宏任务new Promise(function (resolve) {console.log(promise 1) //12 同步函数resolve()}).then(function () {console.log(promise then) //13 微任务})})async function async1() {console.log(async1 s…

AI模型:windows本地运行下载安装ollama运行Google CodeGemma可离线运行数据模型【自留记录】

AI模型&#xff1a;windows本地运行下载安装ollama运行Google CodeGemma可离线运行数据模型【自留记录】 CodeGemma 没法直接运行&#xff0c;需要中间软件。下载安装ollama后&#xff0c;使用ollama运行CodeGemma。 类似 前端本地需要安装 node.js 才可能跑vue、react项目 1…

华为:三层交换机与路由器连通上网实验

三层交换机是一种网络交换机&#xff0c;可以实现基于IP地址的高效数据转发和路由功能&#xff0c;通常用于大型企业、数据中心和校园网络等场景。此外&#xff0c;三层交换机还支持多种路由协议&#xff08;如OSPF、BGP等&#xff09;&#xff0c;以实现更为复杂的网络拓扑结构…

Java数据结构---链表

目录 链表的基本概念 LinkedList ArrayList和LinkedList的区别 链表的基本概念 当在ArrayList任意位置插入或删除元素时&#xff0c;就需要将后续元素整体往前或者整体往后搬移&#xff0c;时间复杂度O&#xff08;n&#xff09;&#xff0c;效率比较低&#xff0c;因此Arra…

【计算机科学速成课】笔记三

文章目录 17.集成电路真空管时代晶体管时代集成电路时代印刷电路板时代光刻时代 17.集成电路 Over the past six episodes, we delved into software, 过去 6 集我们聊了软件 \N 从早期编程方式到现代软件工程 from early programming efforts to modern software engineerin…

每天五分钟计算机视觉:通过交并比判断对象检测算法的性能

本文重点 在对象检测领域,交并比(Intersection over Union,简称IoU)是衡量算法性能的重要指标之一。它不仅直观地反映了预测框与真实框之间的重叠程度,还是判断算法是否“运行良好”的关键依据。 那个定位是好的? 对象检测任务中,我们希望不仅检测到对象,同时我们还希…

分析错误ValueError: could not determine the shape of object type ‘Series‘

这个错误提示 ValueError: could not determine the shape of object type Series 通常发生在尝试将 pandas 的 Series 直接转换为 PyTorch 的 tensor 时&#xff0c;尤其是当 Series 的数据类型不明确或者包含非数值类型的数据时。为了修正这个问题&#xff0c;确保在转换之前…