TCP协议

2023-05-16

文章目录

    • 1.保证可靠性机制
      • 1.1 确认应答机制
        • 1.1.1确认应答机制概念
        • 1.1.2常规确认应答的工作方式
        • 1.1.3报文按序到达
        • 1.1.4 如何确认历史数据被收到
        • 1.1.5 16位序号和16确认序号(字段讲解)
      • tcp的缓冲区(背景知识)
      • 1.2流量控制(16位窗口大小)
      • 6位标志位(字段讲解)
      • 1.3 建立连接(三次握手)
      • PUSH标志位(6位标志位知识)
      • URG标志位(6位标志位知识)
      • 1.4 断开连接(四次挥手)
      • 1.5 超时重传机制(重要知识)
      • 为什么是三次握手?(重要知识)
      • 1.6 连接管理机制
    • 2. 提升效率的机制
      • 2.1 滑动窗口
      • 2.2 快重传VS超时重传
      • 2.3 流量控制
      • 2.4 拥塞控制
      • 2.5 延迟应答
      • 2.6 捎带应答
      • 2.7 面向字节流
      • 2.8 粘包问题
    • 3. TCP异常情况
    • 4. TCP小结
    • 5. 理解 listen 的第二个参数

tcp可以保证数据可靠的被对方接收,当数据丢失时进行重传,通过确认应答机制判断数据丢失。

1.保证可靠性机制

  • 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;
  • 32位序号/32位确认号: 后面详细讲;
  • 4位TCP报头长度: 表示该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最大长度是15 * 4 = 60
  • 6位标志位:
    – URG: 紧急指针是否有效
    – ACK: 确认号是否有效
    – PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
    – RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段
    – SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
    – FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
  • 16位窗口大小: 后面再说
  • 16位校验和: 发送端填充, CRC校验。 接收端校验不通过, 则认为数据有问题。 此处的检验和不光包含TCP首部, 也包含TCP数据部分。
  • 16位紧急指针: 标识哪部分数据是紧急数据;
  • 40字节头部选项: 暂时忽略;

1.1 确认应答机制

1.1.1确认应答机制概念

发送数据,要使数据确认收到,必须要收到数据的应答,双方一来一回如此操作,
如下图如此下去将会造成应答死循环。

需要注意的是无论是数据,还是应答,本质都是一条tcp报文

因此总会有一条最新发送的确认应答不能确认是否被收到了。所以tcp并不是完全可靠的。但是我们能够确认,只要一条数据有确认应答那么这条数据肯定被对方收到。没有应答时,可能是因为数据丢失了或者应答丢失了。

1.1.2常规确认应答的工作方式

我方发送数据的目的就是为了让对方能够可靠的收到数据,因此如果数据丢失了,我方应该重新发送数据。当我方收到对方的确认应答说明我方已经可靠将数据发送给对方了。我方目的达成因此对于对方的确认应答,我方没必要给对方发送确认应答了。即只能保证数据单方向的可靠。

1.1.3报文按序到达

我们把发送出去的数据,看作是报文。一个一个的报文按顺序发送出去,但是接收时,可能因为网络状况报文接收出现延迟,导致接收所有的报文后是乱序的。因此我们给每个报文带上序号,接收报文时按照序号进行排序,那么应用层收到的数据就是有序的。

【 可靠性不仅要保证被对方收到,还要按序到达。】

在这里插入图片描述

1.1.4 如何确认历史数据被收到

tcp报头中涵盖一个叫做确认序号,对历史确认报文的序号+1就是确认序号。

确认序号的含义是确认序号之前的所有的报文我已经全部收到了,下次发送请从确认序号报文开始发送。

1.1.5 16位序号和16确认序号(字段讲解)

一个报文既有序号,又有确认序号。

为何这两个字段独立存在?

如果一个字段,我们可以使用标志符ACK进行区分(往下阅读会讲解),但是在tcp是支持全双工的,在我们发送数据或应答的同时,对方也有可能给我们发送数据或应答,因此我们在发送tcp报文时可能发送数据和确认应答要共有一个tcp报文,因此设计了两个字段。

如何理解序号和确认序号呢?

  • 我们可以想象TCP将缓冲区的每个字节的数据都进行了编号。 即为序列号.
  • 每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你从哪里开始发。
  • 每次发送数据时,都将要发送数据的最大的序号作为发送序号。
  • 接收方根据报头长度,进行报文分离,将有效载荷根据序号有序的放到缓冲区里。

tcp的缓冲区(背景知识)

tcp有接收和发送缓冲区,用户调用发送系统接口时,其实是向tcp缓冲区读写数据。

tcp缓冲区的作用?

  • 提高应用层效率。
  • 数据的发送与接收由tcp协议控制。
  • 只有OS TCP协议可以知道网络,乃至对方的转态明显,所以,只有TCP协议,能处理如何发,什么时候发,发多少,出错了怎么办?等细节问题。
  • 我们把TCP协议称作为传输控制协议,怎么办就是由tcp控制。
  • 又因为缓冲区的存在,所以可以做到应用层与TCP进行解耦。

1.2流量控制(16位窗口大小)

  • 对方接收缓冲区满,如果我方还继续发送,那么对方会丢弃该报文。对方缓冲区满了可能是因为对方应用层来不及接收。
  • 此时丢弃的报文没有得到应答那么我方会使用超时重传的机制重新发送报文。
  • 如果对方接收缓冲区一直存满状态,我方不断的发送,如此下去,虽然不是问题,但是一个报文千里送过去被丢弃,有点浪费网络资源。
  • 因此tcp引入了流量控制机制。
  • tcp提供了16位窗口大小的字段,该字段表示我方接收缓冲区剩余空间的大小。
  • 我方收到对方的窗口大小,得知对方的接收情况,根据该字段动态的调整发送数据的大小。
  • 这也体现了TCP的控制能力。

6位标志位(字段讲解)

  • TCP是面向连接的,TCP socket 通信前首先要connect()函数建立连接,本质就是三次握手,即交互三次报文。
  • 首先server端可能会面临一个问题,server可能在任何一个时刻,都有可能有成百上千个报文再向server发送数据。
  • server首先面临的是,面对大量的TCP报文,如何区分各个报文的类别
  • 通过TCP的标志位来进行区分
  • 6位标志位:(往下阅读将会详细介绍完6位标志位的特性)
    – URG: 紧急指针是否有效
    – ACK: 确认号是否有效
    – PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
    – RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段
    – SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
    – FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段

注意:

  • 三次握手会用到SYN,进行建立连接。
  • 4次挥手会用到FIN,断开链接。
  • 在双方通信时,保证双方是连接状态的,说明双方通信没问题。
  • 这也是为了保证可靠性,如果双方不是连接状态,既双方没建立连接。
  • 和UDP一样、无法确认对方的状态、因此数据也无法保证可靠传送。

1.3 建立连接(三次握手)

  • 虽然TCP有重传机制和确认应答机制,但是不能知道对方的状态。
  • 对方有可能处于离线状态,如果我们一味的进行重传,那么只能等到对方在线为止。
  • 这必然是不行的,因此双方在通信前应该先建立连接,获知对方能正常通信。
  • 当有一方因某种原因导致需要断开连接,应该告诉对方断开连接,即四次挥手。
  • 我们发现建立连接也是发送报文,因此也会有对方收不到的可能性,在最坏的情况下(往下阅读会有具体描述),如果对方还是没有响应那么可以认为对方是离线的。
  • 下面我们详细讲解三次握手,是如何建立起连接的。

理解一下建立连接的本质是什么?

  • server存在大量的连接,server应该对其进行管理,先描述,后组织,建立连接的结构体,再使用数据结构将其联系起来。
  • 双方都需要维护数据结构,因此是有时间和空间成本的。

三次握手的过程

  • client发送SYN标志的报文请求对方连接(第一次握手)。
  • server收到SYN标志的报文,检测一下本机是否允许建立连接。
  • 如果不允许,那么server只发送ACK的报文。
  • 如果允许,那么server发送ACK+SYN的报文,即server确认应答并请求对方连接(第二次握手)。
  • 如果client收到ACK的报文,那么会进行重新建立连接(三次握手)。
  • 如果client收到ACK+SYN的报文,client发送ACK报文。
  • 此时当client发送第三次握手后。(client认为三次握手成功了,即建立连接成功)。
  • 如果第三次握手server收到,那么server认为三次握手成功了。
  • 需要注意,第三次握手,client发送第三次握手和server接收第三次握手是有时间间隔的。
  • 因此,第三次握手有可能丢失或者延迟较大。
  • 当server还没收到第三次握手时,server认为还没建立好连接,server收到client的报文时,server发送RST报文,即serve而要求client重新建立连接。
  • RST用来重置异常连接的,是连接异常的一种情况,只要双方出现连接异常,都可以使用RST,来进行连接重置。

正常三次握手(图片)

第三次握手报文丢失(图片)

TCP三次握手和四次挥手的异常连接情况

此时还不是解答以下问题的时候,因为还差一些背景知识没讲。

  • 第一次握手,如果客户端发送的SYN一直都传不到被服务器,那么客户端是一直重发SYN到永久吗?客户端停止重发SYN的时机是什么?
  • 第三次握手,如果服务器永远不会收到ACK,服务器就永远都留在 Syn-Recv 状态了吗?退出此状态的时机是什么?
  • 第三次挥手,如果客户端永远收不到 FIN,ACK,客户端永远停留在 Fin-Wait-2状态了吗?退出此状态时机是什么时候呢?
  • 第四次挥手,如果服务器永远收不到 ACK,服务器永远停留在 Last-Ack 状态了吗?退出此状态的时机是什么呢?
  • 如果客户端 在 2SML内依旧没收到 FIN,ACK,会关闭链接吗?服务器那边怎么办呢,是怎么关闭链接的呢?

PUSH标志位(6位标志位知识)

  • PUSH:告知对方尽快将缓冲区的数据进行向上交付。
  • 接收缓冲区是有水位线的,当到达水位线时,TCP才会通知应用层接口。
  • 原因在于:TCP希望应用层可以一次性拷贝一批数据。内核态到用户态是需要耗费很长时间的。因此频繁的切换状态,会使性能降低。

URG标志位(6位标志位知识)

  • 目前TCP有按序到达,每一个报文,什么时候被上层取到基本是确定的。
  • 如果想让一个数据尽快的被上层读到,可以设置URG。
  • URG:表明该报文携带了紧急数据,需要被优先处理。
  • 紧急数据在哪里?紧急数据如何获取?
  • 紧急数据在有效载荷里,TCP报头有一个16位紧急指针、其用来标识紧急数据在有效载荷的具体位置。
  • 应用层ssize_t rec**加粗样式**v(int sockfd, void *buf, size_t len, int flags);函数。
  • 如果我们想获取紧急数据,我们可以调用recv函数,需要设置flags参数为 MSG_OOB
  • 需要注意、我们只能获取1个字节的紧急数据。

紧急数据的用途

应用层工程师一般是接触不到的,常用来当server出现问题时,server不能给予对方响应,server会有一个专门的线程来获取紧急数据,所以也叫带外数据,该线程用于通知给对方server的状况。

1.4 断开连接(四次挥手)

  • 一般而言,建立连接一般是client,而断开连接是双方的事,即双方都有随时断开连接。
  • 下面来讲解一下四次挥手的过程(详细分析还需要到讲解TCP状态)
  • 如下图、
  • client发送FIN报文(第一次挥手)、告知对方我要断开连接。
  • server端收到FIN报文后,发送ACK(第二次挥手)。告知对方我同意。
  • server端发送FIN报文(第三次挥手),告知对方我要断开连接。
  • client发送ACK报文(第四次挥手),告知对方我同意。
  • 详细细节还要往下分析。

1.5 超时重传机制(重要知识)

  • TCP保证可靠性机制有些体现在报头里,但有一些不在报头里。
  • 超时重传机制就是其中一种。
  • 主要原因就是,没必要体现,只要获取现成的报头,及OS本身的机制或自己本身代码来完成。
  • 超时重传就是当发送数据出去以后,给自己设定一个定时器,在特定间隔时间里,如果没有收到应答报文,那么执行重传报文机制。

在这里插入图片描述

重传的时间间隔应该是多少?

  • 当我们发送完对应的报文,没有收到对方的ACK,有可能是对方的ACK丢失,并不是数据丢失了。因此我们会执行超时重传。如果对方继续收取相同报文,那么就报文重复了,因此接收方应该通过序号去重,这也是保证可靠性,该接收的接收、不该接收的不接收。
  • 最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”。
  • 但是这个时间的长短, 随着网络环境的不同, 是有差异的。
  • 如果超时时间设的太长, 会影响整体的重传效率;
  • 如果超时时间设的太短, 有可能会频繁发送重复的包;
  • TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间。
  • Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍。
  • 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传。
  • 如果仍然得不到应答, 等待 4*500ms 进行重传。 依次类推, 以指数形式递增。
  • 累计到一定的重传次数(一般是5次大概是60s), TCP认为网络或者对端主机出现异常, 强制关闭连接。

为什么是三次握手?(重要知识)

  • 建立连接的目的就是为了保证可靠性,验证双方是否有通信能力。
  • TCP是要进行全双工的,为了验证全双工,即全方是否具备收发能力。
  • 而三次握手是验证全双工的最少次数,下面来看看他们是如何验证的。
  • client发送SYN,如果收到对方的SYN+ACK报文,此时client具备收发能力了,server也局部收的能力了。
  • client发送ACK,此时client认为自己建立好连接了。
  • server如果收到ACK,那么就能验证自己是有发送能力了。
  • 我们模拟过程就知道,1、2次握手肯定不行的。
  • 4、5、6、7次已经是多余了,我们不是为了建立连接而建立连接,我们是为了验证网络状态,验证双方主机的就绪状态、验证全双工状态,来发起三次握手,过多的次数会增加建立连接的成本。
  • 例如:如果为保证双方是为了确认自己和对方是否建立好连接,即三次握手只能确认自己,不能确认对方。那么可能为了确保建立好连接,而造成重传次数不确定性,因此是不会被采用的。TCP采用三次握手就是为了以最低次数较大概率连接成功。
  • 其余解析:
    – b.如果是一次两次握手(就建立好连接),那么将会遭到“SYN洪水攻击”,使服务器要维护大量的“链接资源”。
    – c.如果是三次握手那么也会受到“SYN洪水攻击”,只是双方都要付出等价的代价,因此这种手段只能限制小数量服务器的攻击,如果有大量的肉机被安装木马病毒,大量的攻击,那么服务端也会垮掉。
  • d.其实“洪水攻击”是有防御手段的,通过IP地址……

为什么是四次握手?

  • 与三次握手不一样,四次握手是让双方达成共识,就是一个通知对方的机制。
  • 让对方知道自己已经断开连接了,并且知道对方也断开连接的一个过程。
  • 根离婚一样,双方都要签字那么才算真的离婚了。

1.6 连接管理机制

在这里插入图片描述

如下、什么状态收到什么报文,状态就有可能发生变化。

服务端状态转化:

  • [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状态。

状态分析

  • 在正常情况下, TCP要经过三次握手建立连接, 四次挥手断开连接。
  • 双方建立好的连接是由五元组进行维护的,也可以认为五元组标识一个唯一的通信信道。

理解TIME_WAIT状态

现在做一个测试,首先启动server,然后启动client,然后Ctr-c使server终止,这使马上再运行server,这些网络服务都是bind同一个端口号结果是:

在这里插入图片描述
这是因为,虽然server的应用程序终止了,但是TCP协议层的连接并没有完全断开,因此该连接相当于一个进程,一个端口号不能绑定多个进程,因此不能再次监听同样的server端口。(往下阅读会有办法解决bind失败的问题)我们用netstat命令查看一下:

在这里插入图片描述

  • TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态。
  • 注意上图说明的是不管连接是什么状态,再次bind都会失败。
  • 我们使用Ctrl-C终止了server, 所以server是主动关闭连接的一方, 在TIME_WAIT期间仍然不能再次监听同样的server端口;
  • MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7上默认配置的值是60s;
  • 可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看msl的值;
  • 规定TIME_WAIT的时间请读者参考UNP 2.7节

为什么是TIME_WAIT的状态时间2MSL?

  • MSL是TCP报文的最大生存时间, 因此TIME_WAIT持续存在2MSL的话
  • 就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到
  • 来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);
  • 网络中可能存在发送方的数据包,当这些数据包被接收方处理后又会向对方发送响应,一来一回最多需要等待2MSL的时间。
  • 这个时间是从客户端接收到服务器的FIN报文后内核发送ACK报文开始计时的,如果在这个时间内,客户端又收到了服务器重发
    的FIN报文,则重新计时
  • 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN。 这
  • 时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK)
  • 期间也是一来一回的,因此2MSL也是为了这。
  • 最后一次ACK一直没有被收到,那么服务端会一直进行超时重传,直到重传次数超过tcp_orphan_retries参数控制,次数达到进入CLOSE状态。

解决TIME_WAIT状态引起的bind失败的方法

因此有些场景下是不合理的,例如:

  • 服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很大数量的客户端来请求)。
  • 这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务器端主动清理掉), 就会产生大量TIME_WAIT连接。
  • 由于我们的请求量很大, 就可能导致TIME_WAIT的连接数很多, 每个连接都会占用一个通信五元组(源ip,源端口, 目的ip, 目的端口, 协议)。
  • 其中服务器的ip和端口和协议是固定的。 如果新来的客户端连接的ip和端口号和TIME_WAIT占用的链接重复了, 就会出现bind失败的问题。

解决方法:
使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符
在这里插入图片描述setsockopt()设置以后解决了不能绑定相同端口的新套接字,旧连接发送的数据肯定早于新连接被创建的时间,区分的目的是因为旧连接与新连接的五元组相同,当报文传输到传输层后,根据端口号和序号的判断下交付给特定的连接。因此,新连接只会接收到新的数据,老旧连接的数据不会发送到新的连接上。

理解CLOSE_WAIT状态

  • CLOSE_WAIT状态表示等待应用层调用close()函数,所以CLOSE_WAIT的等待时间由。
  • server不关闭客户端连接(close(linkfd)),这会导致一个无用的连接长时间停留在系统里,如果存在大量的CLOSET_WAIT状态的连接那么系统资源会越来越少,导致fd泄漏。
  • 我们编译运行服务器。 启动客户端链接, 查看 TCP 状态, 客户端服务器都ESTABLELISHED 状态, 没有问题。然后我们关闭客户端程序, 观察 TCP 状态
tcp 0 0 0.0.0.0:9090 0.0.0.0:* LISTEN 5038/./dict_server

tcp 0 0 127.0.0.1:49958 127.0.0.1:9090 FIN_WAIT2 -

tcp 0 0 127.0.0.1:9090 127.0.0.1:49958 CLOSE_WAIT 5038/./dict_server

此时服务器进入了 CLOSE_WAIT 状态, 结合我们四次挥手的流程图, 可以认为四次挥手没有正确完成.
小结: 对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭 socket, 导致四次挥手没有正确完成。 这是一个 BUG。 只需要加上对应的 close 即可解决问题。

三次握手的异常连接处理

第一次握手丢失了,会发生什么?

当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到 SYN_SENT 状态。

在这之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发超时重传机制。

不同版本的操作系统可能超时时间不同,有的 1 秒的,也有 3 秒的,这个超时时间是写死在内核里的,如果想要更改则需要重新编译内核,比较麻烦。

当客户端在 1 秒后没收到服务端的 SYN-ACK 报文后,客户端就会重发 SYN 报文,那到底重发几次呢?

在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries 内核参数控制,这个参数是可以自定义的,默认值一般是 5。

通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后。没错,每次超时的时间是上一次的 2 倍。

当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。

所以,总耗时是 1+2+4+8+16+32=63 秒,大约 1 分钟左右。

第二次握手丢失了,会发生什么?

当服务端收到客户端的第一次握手后,就会回 SYN-ACK 报文给客户端,这个就是第二次握手,此时服务端会进入 SYN_RCVD 状态。

第二次握手的 SYN-ACK 报文其实有两个目的

  • 第二次握手里的 ACK, 是对第一次握手的确认报文;
  • 第二次握手里的 SYN,是服务端发起建立 TCP 连接的报文;

所以,如果第二次握手丢了,就会发送比较有意思的事情,具体会怎么样呢?

因为第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文,所以,如果客户端迟迟没有收到第二次握手,那么客户端就觉得可能自己的 SYN 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文。

然后,因为第二次握手中包含服务端的 SYN 报文,所以当客户端收到后,需要给服务端发送 ACK 确认报文(第三次握手),服务端才会认为该 SYN 报文被客户端收到了。

那么,如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文。

在 Linux 下,SYN-ACK 报文的最大重传次数由 tcp_synack_retries 内核参数决定,默认值是 5。

因此,当第二次握手丢失了,客户端和服务端都会重传:

  • 客户端会重传 SYN 报文,也就是第一次握手,最大重传次数由 tcp_syn_retries 内核参数决定。;
  • 服务端会重传 SYN-AKC 报文,也就是第二次握手,最大重传次数由 tcp_synack_retries 内核参数决定。
  • 如果第一次握手被服务端接收到,那么客户端重传的报文会被去重。

第三次握手丢失了,会发生什么?

客户端收到服务端的 SYN-ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISH 状态。

因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。

因为client已经认为自己建立好连接了,因此有可能会发送数据给服务端,但是服务端没建立连接时收到,那么服务端会发送RST报文,要求客户端重新建立连接。

注意:ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。

TCP 四次挥手期间的异常

我们再来看看 TCP 四次挥手的过程。

第一次挥手丢失了,会发生什么?

当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。

正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为FIN_WAIT2状态。

如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。

当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,直接进入到 close 状态。

第二次挥手丢失了,会发生什么?

当服务端收到客户端的第一次挥手后,就会先回一个ACK确认报文,此时服务端的连接进入到 CLOSE_WAIT 状态。

在前面我们也提了,ACK 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。

这里提一下,当客户端收到第二次挥手,也就是收到服务端发送的 ACK 报文后,客户端就会处于 FIN_WAIT2 状态,在这个状态需要等服务端发送第三次挥手,也就是服务端的 FIN 报文。

对于 close 函数关闭的连接,由于无法再发送和接收数据,所以FIN_WAIT2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。

这意味着对于调用 close 关闭的连接,如果在 60 秒后还没有收到 FIN 报文,客户端(主动关闭方)的连接就会直接关闭。

第三次挥手丢失了,会发生什么?

当服务端(被动关闭方)收到客户端(主动关闭方)的 FIN 报文后,内核会自动回复 ACK,同时连接处于CLOSE_WAIT状态,顾名思义,它表示等待应用进程调用 close 函数关闭连接。

此时,内核是没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。

服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。

如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。

第四次挥手丢失了,会发生什么?

当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态。

在 Linux 系统,TIME_WAIT 状态会持续 60 秒后才会进入关闭状态。

然后,服务端(被动关闭方)没有收到 ACK 报文前,还是处于LAST_ACK状态。

如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。

小结:

在极端情况下,除了FIN_WAIT2状态等待60s后关闭连接,其余都是超时重传次数够了进行关闭连接。

状态具体细节
CLOSE连接断开
SYN_SENT请求建立连接,等待ACK+SYN 等待最长时长超时重传次数
SYN_RECV请求建立连接,等待ACK ,等待最长时长超时重传次数
FIN_WAIT1等待ACK ,等待最长时长超时重传次数(没收到ACK进入CLOSE状态)
CLOSE_WAIT等待应用层调用close(), ,等待最长时长随进程
FIN_WAIT2等待对方FIN,等待最长时间随OS配置,一般为60s(没收到FIN进入CLOSE状态)
LAST_ACK等待ACK,等待最长时长超时重传次数(没收到ACK进入CLOSE状态)
TIME_WAIT等待2MSL时间

2. 提升效率的机制

2.1 滑动窗口

刚才我们讨论了确认应答策略, 对每一个发送的数据段, 都要给一个ACK确认应答。 收到ACK后再发送下一个数据段。这样做有一个比较大的缺点, 就是性能较差。 尤其是数据往返的时间较长的时候。

在这里插入图片描述
既然这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)。

在这里插入图片描述

  • 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。 上图的窗口大小就是4000个字节(四个段)。
  • 发送前四个段的时候, 不需要等待任何ACK, 直接发送;
  • 收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;
  • 操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉;
  • 窗口越大, 则网络的吞吐率就越高;

理解滑动窗口

  • 如上图发送端调用write()实际就是把数据拷贝到传输层的发送缓冲区里,数据什么时候发送由传输层决定。
  • 发送缓冲区里1号区为已经发送,已经确认的数据;2号区为可以/已经发送,但是还没收到确认;3号区为没有发送的数据。

2号区就是滑动窗口,我们来分析滑动窗口的一些细节问题。

在这里插入图片描述如上图,收到2001序号的确认应答,我们的滑动窗口往右移动一个分段。

窗口的大小与接收方的接收能力强相关

窗口的大小不会一直不变,根据接收方的接收能力去调整。

丢包重传

情况一:数据包已经抵达, ACK被丢了
在这里插入图片描述

  • 这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认;
  • 接收方是根据序号顺序发送确认应答的,比如1,3,4序号的数据都被接收,但是2号序号的数据还没被接收,接收方只会发送1号序号的确认应答,因为双方都达成了协议,确认序号代表确认序号前的数据都被接收了,这时候如果发送确认序号4,那么接收方就认为2号序号被接收了,后续2号序号数据丢失将不会被重传。

情况二: 数据包就直接丢了

在这里插入图片描述

  • 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是 1001"一样;
  • 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
  • 这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中

2.2 快重传VS超时重传

超时重传前提条件是等待2MSl时间后没有收到应答才会执行重传。
快重传是收到3次相同的应答后才会重传。

区别:

  • 快重传效率比超时重传效率高,原因是超时需要等待时间较长。

为什么不全使用快重传?

原因是快重传要有前提条件的,如果满足不了收到3次相同的应答,那么快重传不能使用。所以说在网络层快重传与超时重传都会使用到。

2.3 流量控制

  • 接收端处理数据的速度是有限的。 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应。

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

  • 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;

  • 窗口大小字段越大, 说明网络的吞吐量越高;

  • 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;

  • 发送端接受到这个窗口之后, 就会减慢自己的发送速度;

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

在这里插入图片描述

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

2.4 拥塞控制

  • 虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据。 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题。
  • 之前我们考虑的是端对端的情况,现在我们来考虑网络。
  • 这里引入了拥塞窗口来反应网络的情况。
  • 因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵。 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜的。
  • TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据

在这里插入图片描述

  • 此处引入一个概念程为拥塞窗口发送开始的时候, 定义拥塞窗口大小为1;
  • 每次收到一个ACK应答, 拥塞窗口加1;
  • 每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;

像上面这样的拥塞窗口增长速度, 是指数级别的。 “慢启动” 只是指初使时慢, 但是增长速度非常快.

  • 为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍。
  • 此处引入一个叫做慢启动的阈值。
  • 当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长。

在这里插入图片描述

  • 当TCP开始启动的时候, 慢启动阈值等于窗口最大值;
  • 在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1;
  • 少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
  • 当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;
  • 拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.

2.5 延迟应答

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

  • 假设接收端缓冲区为1M。 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;

  • 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;

  • 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;

  • 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;

  • 一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高。 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;

  • 那么所有的包都可以延迟应答么? 肯定也不是;
    数量限制: 每隔N个包就应答一次;
    时间限制: 超过最大延迟时间就应答一次;
    – 具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms;

在这里插入图片描述

2.6 捎带应答

  • 在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 “一发一收” 的。 意味着客户端给服务器说了 “How are you”, 服务器也会给客户端回一个 “Fine, thank you”;
  • 那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起回给客户端
  • 最典型的例子就是第二次握手报文。

在这里插入图片描述

2.7 面向字节流

创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;调用write时, 数据会先写入发送缓冲区中;

  • 如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
  • 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
  • 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
  • 然后应用程序可以调用read从接收缓冲区拿数据;
  • 另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据。 这个概念叫做 全双工

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

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

2.8 粘包问题

  • 首先要明确, 粘包问题中的 “包” , 是指的应用层的数据包。
  • 在TCP的协议头中, 没有如同UDP一样的 “报文长度” 这样的字段, 但是有一个序号这样的字段。
  • 站在传输层的角度, TCP是一个一个报文过来的。 按照序号排好序放在缓冲区中。
  • 站在应用层的角度, 看到的只是一串连续的字节数据。
  • 那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包。
  • 因此这不是TCP要解决的问题。
  • 那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界。
  • 由应用层协议通过特殊字符,字描述字段,定长的方式解决。
  • 对于定长的包, 保证每次都按固定大小读取即可; 例如上面的Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
  • 对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;
  • 对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可);

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

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

3. TCP异常情况

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

4. TCP小结

基于TCP应用层协议

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

TCP/UDP对比

  • 我们说了TCP是可靠连接, 那么是不是TCP一定就优于UDP呢? TCP和UDP之间的优点和缺点, 不能简单, 绝对的进行比较
  • TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景;
  • UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等. 另外UDP可以用于广播;
  • 归根结底, TCP和UDP都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定

5. 理解 listen 的第二个参数

  • 基于刚才封装的 TcpSocket 实现以下测试代码
  • 对于服务器, listen 的第二个参数设置为 2, 并且不调用 accept

test_server.cc

#include "tcp_socket.hpp"
int main(int argc, char* argv[]) {
	if (argc != 3) {
		printf("Usage ./test_server [ip] [port]\n");
		return 1;
	}
	TcpSocket sock;
	bool ret = sock.Bind(argv[1], atoi(argv[2]));
	if (!ret) {
		return 1;
	}
	ret = sock.Listen(2);
	if (!ret) {
		return 1;
	}
// 客户端不进行 accept
	while (1) {
		sleep(1);
	}
	return 0;
}

test_client.cc

#include "tcp_socket.hpp"
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./test_client [ip] [port]\n");
return 1;
}
TcpSocket sock;
bool ret = sock.Connect(argv[1], atoi(argv[2]));
if (ret) {
printf("connect ok\n");
} else {
printf("connect failed\n");
}
while (1) {
sleep(1);
}
return 0;
}

此时启动 3 个客户端同时连接服务器, 用 netstat 查看服务器状态, 一切正常.

但是启动第四个客户端时, 发现服务器对于第四个连接的状态存在问题了

tcp 3 0 0.0.0.0:9090 0.0.0.0:* LISTEN
9084/./test_server
tcp 0 0 127.0.0.1:9090 127.0.0.1:48178 SYN_RECV -
tcp 0 0 127.0.0.1:9090 127.0.0.1:48176 ESTABLISHED -
tcp 0 0 127.0.0.1:48178 127.0.0.1:9090 ESTABLISHED
9140/./test_client
tcp 0 0 127.0.0.1:48174 127.0.0.1:9090 ESTABLISHED
9087/./test_client
tcp 0 0 127.0.0.1:48176 127.0.0.1:9090 ESTABLISHED
9088/./test_client
tcp 0 0 127.0.0.1:48172 127.0.0.1:9090 ESTABLISHED
9086/./test_client
tcp 0 0 127.0.0.1:9090 127.0.0.1:48174 ESTABLISHED -
tcp 0 0 127.0.0.1:9090 127.0.0.1:48172 ESTABLISHED -

客户端状态正常, 但是服务器端出现了 SYN_RECV 状态, 而不是 ESTABLISHED 状态
这是因为, Linux内核协议栈为一个tcp连接管理使用两个队列:

  1. 半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求)
  2. 全连接队列(accpetd队列)(用来保存处于established状态,但是应用层没有调用accept取走的请求)
    而全连接队列的长度会受到 listen 第二个参数的影响.

全连接队列满了的时候, 就无法继续让当前连接的状态进入 established 状态了.

这个队列的长度通过上述实验可知, 是 listen 的第二个参数 + 1.

为什么需要该队列?

  • 充分利用服务器资源。服务的资源能百分百被利用。
  • 例如:服务器占时只能服务5个连接。
  • 当前队列有2个连接准备被服务。
  • 当服务器有服务空位时,可以立刻在队列里拿取连接进行服务。
  • 并不是队列越大越好,队列也是要系统资源维护的,队列小了,那么服务器的资源也就多了。
  • 服务器的资源能百分百被利用的情况下,队列的长度根据情况来进行控制
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TCP协议 的相关文章

  • Python套接字模块:Recv()数据响应被切断

    解释 我目前正在尝试使用 python 脚本控制智能电源板 为了实现这一点 我使用了带有套接字模块的 TCP 连接 大约 75 的情况下 我会得到我正在寻找的响应 数据 并且一切都运行良好 然而 大约 25 的情况下 响应会以完全相同的长度
  • 如何在Linux中打开端口[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我已经安装了 Web 应用程序 该应用程序在 RHEL centOS 上的端口 8080 上运行 我只能通过命令行访问该机器 我尝试从我的
  • 为什么 UDP 服务器中只有一个套接字?

    我正在准备考试 发现了这个问题 典型的 UDP 服务器可以使用单个套接字来实现 解释一下为什么 对于 TCP 驱动的服务器 我发现创建了两个套接字 一个用于所有客户端访问服务器 另一个用于每个客户端的特定 套接字 用于服务器和客户端之间的进
  • iOS 上的多个 HTTP 请求与单个 TCP 连接

    我正在开发一个 iPhone 应用程序 它使用我控制的基于 Web 的 API 连接到持续打开的 TCP 端口并通过 TCP API 发出请求 或者为我想要获取的所有数据发出新的 HTTP 请求 会更快或更高效吗 我认为差异可以忽略不计 但
  • 如何查找连接到 AF_INET 套接字的客户端的 UID?

    有什么方法或类似的东西ucred for AF UNIX如果是AF INET插座 TCP在我的例子中 找出连接到我的套接字的客户端的UID 还有 proc net tcp但它显示了UID of the creator插座的而不是连接的cli
  • 如何强制关闭 TcpListener

    我有一个通过 tcpListener 进行通信的服务 问题是当用户重新启动服务时 抛出 地址已在使用 异常 并且服务在几分钟左右无法启动 有没有办法告诉系统终止旧连接 以便我可以打开一个新连接 我不能只使用随机端口 因为服务无法通知客户端端
  • ADO.NET SQLServer:如何防止关闭的连接持有S-DB锁?

    i Dispose http msdn microsoft com en us library system data sqlclient sqlconnection close aspx一个 SqlConnection 对象 但是当然它并
  • 当 TCP 序列号到达而不是预期时会发生什么情况?

    我正在编写一个程序 使用 libpcap 捕获数据包并重新组装 TCP 流 我的程序只是监视流量 因此我无法控制数据包的接收和发送 我的程序忽略所有非 TCP IP 流量 我根据 ISN 计算下一个预期序列号 然后计算连续的 SEQ 号 我
  • 谁在 Mac OS X 上监听给定的 TCP 端口?

    在Linux上 我可以使用netstat pntl grep PORT or fuser n tcp PORT找出哪个进程 PID 正在侦听指定的 TCP 端口 如何在 Mac OS X 上获得相同的信息 在 macOS 上Big Sur然
  • 将 C++ TCP/IP 应用程序从 IPv4 转换为 IPv6。难的?值得这么麻烦吗?

    多年来 我使用 WinSock 为 Windows 开发了少量 C 服务器 客户端应用程序 路由器 Web 邮件 FTP 服务器等 等等 我开始越来越多地考虑创建这些应用程序的 IPv6 版本 当然 同时也保留原始的 IPv4 版本 问题
  • 使用 TCP 时是否需要使用校验和来保护我的消息?

    使用 TCP 作为网络协议 在通过线路发送消息之前 我会为每条消息的大小 以及可能的校验和 添加前缀 我想知道 计算和传输消息的校验和是否有意义 以确保消息将被不变地传递 如果以及何时传递 例如因为一些网络错误 目前 我在发送消息本身之前发
  • TCP 连接寿命

    客户端 服务器 TCP 连接在野外可以持续多长时间 我希望它保持永久连接 但事情发生了 所以客户端将不得不重新连接 我什么时候可以说代码有问题而不是某些外部设备有问题 我同意赞 林克斯的观点 虽然无法保证 但假设不存在连接或带宽问题 您可以
  • 使用 boost::asio 是否有一种可移植的方法来查找空闲端口号

    我目前正在尝试找出一种方法来查找空闲端口号以建立连接 最好使用 boost asio 然后 该端口号将用于侦听 只有这样我才能打开套接字 大致来说 有没有办法做到 tcp resolver query query localhost por
  • StreamWriter的正确使用

    经过几次尝试后 我无法让 StreamWriter 正确构建 工作 所以我做了一些根本错误的事情 C Visual Studio 我有一个现有的 TCP 客户端 它连接并充当读取器 它工作正常 private System Net Sock
  • 用 C 语言进行非阻塞 udp 套接字编程:我能得到什么?

    我在理解从非阻塞 UDP 套接字返回什么recv recvfrom 时遇到问题 与 TCP 相比更具体一点 如果我错了 请纠正我 阻塞套接字 TCP 或 UDP 在缓冲区中有一些数据之前不会从 recv 返回 这可以是一定数量的字节 TCP
  • 创建 ip 网络数据包 [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我必须使用任何协议手动创建自己的网络
  • PHP 通过 TCP/IP 发送消息

    我尝试通过 TCP IP 从 PHP 网站向 Arduino 发送消息 使用以下代码我可以从 php 网站发送消息 问题是 当第一次调用该网站时 消息不会立即发送 网站刷新几次后 消息就会到达 但逻辑上很多次 就像网站刷新量一样 已经尝试将
  • 使用 TcpClient 通过 C# 通过 TCP 发送多个文件

    我正在尝试使用 C TcpClient 通过 TCP 发送多个文件 对于单个文件来说它效果很好 但是当我有多个文件时 它只发送第一个文件 这是我的代码 发送文件 try TcpClient tcpClient new TcpClient N
  • Jmeter TCP Sampler - 如何重用线程之间的连接?

    我在 JMeter 的 ThreadGroup 下设置了一个 TCP 采样器 数据是从 CSV 文件中选取的 第一行数据用于认证 后续行为实际参数数据 像下面这样的东西 AAAAAAA21 BBBBBBBCCCCCCCDDDDDDD BBB
  • Java 客户端到服务器未知来源

    我有一个简单的乒乓球游戏 需要通过网络工作 服务器将创建一个带有球和 2 个球棒位置的游戏 当客户端连接到服务器时 服务器将创建一个名为 PongPlayerThread 的新类 它将处理客户端到服务器的输入和输出流 我的服务器工作100

随机推荐

  • 《国产操作系统之银河麒麟》银河麒麟服务器操作系统引导过程

    目录 系统引导过程 01 系统启动流程概述 系统启动总流程 第一阶段 xff1a BIOS初始化 编辑 第二阶段 GRUB2启动引导 编辑 第三阶段 内核引导 编辑 第四阶段 systemd进程 02 固件与BIOS BIOS启动流程 BI
  • Spring MVC基础配置

    Spring MVC 使用步骤 xff1a 1 在web xml中的配置DispatcherServlet span class token tag span class token tag span class token punctua
  • 对Redis布隆过滤器的实现

    目录 实现思路 首先最重要的自定义hash 然后就是将key放入bitSet 然后就是判断布隆过滤器bitSet数组中是否含有对应的key 代码 实现思路 39条消息 Redis布隆过滤器 Fairy要carry的博客 CSDN博客 首先最
  • Keil uVision5修改工程名字

    目录 1 打开文件中mdk的文件夹 xff1a 2 把Listings和Objects里边东西全部删除 xff1a 3 删除文件夹之外的东西 xff0c 并且绿色这个文件改名 xff1a 4 打开Keil uVision5 xff0c 点开
  • Redis的Java客户端

    1 快速入门 Jedis使用的基本步骤 xff1a 1 引入依赖 2 创建Jedis对象 xff0c 建立连接 3 使用Jedis xff0c 方法名与Redis命令一致 4 释放资源 lt jedis依赖 gt lt dependency
  • linux下使用rpm安装mysql

    1 get mysql rpm package mysql rpm install https www aliyundrive com s 6xUyXcdqYJF 点击链接保存 xff0c 或者复制本段内容 xff0c 打开 阿里云盘 AP
  • python爬取豆瓣T250电影及保存excel(易上手)

    网址 xff1a 豆瓣电影 Top 250 目录 一 bs4和re正则爬取 二 xpath爬取 一 bs4和re正则爬取 源代码 xff1a import urllib request urllib error import re from
  • qt发布的程序时如何将依赖的dll分开放在不同目录

    SetDllDirectory设定DLL加载路径 include 34 Windows h 34 切换工作目录 xff0c 到指定目录查找依赖的dll文件 QString s 61 34 debug 34 LPCWSTR path 61 s
  • Android项目目录结构和资源管理

    项目目录结构和资源管理 项目目录结构默认结构形式真正的结构形式app包里结构src包里的目录 资源的管理和使用图片资源布局资源字符串资源样式颜色资源 项目目录结构 默认结构形式 新建的项目会默认使用Android模式的项目结构 xff0c
  • jupyter notebook无法打开(或无法用终端打开)

    报错如下 xff1a 解决方法 xff1a 添加这三个环境变量 注 xff1a 这三个路径虽然短 xff0c 但是一定要复制粘贴进去 xff0c 手写很容易报错 xff0c 即使你路径手写是对的 其他问题解决方法 xff1a xff08 1
  • Spring Aop通知注解的执行顺序

    spring4和spring5有所不同 spring4没异常有异常执行顺序从上往下 64 Around通知前 64 Aroud通知前 64 Before通知 64 Before通知业务代码 64 After通知 64 Around通知后 6
  • vcruntime140_1.dll无法继续执行代码如何修复?

    vcruntime140 1 dll是电脑系统动态链接中非常重要的文件 xff0c 主要用于处理各种程序 每台计算机上都有相当多的DLL文件 xff0c 不同的程序会使用不同的DLL文件 电脑系统如果丢失dll文件 xff0c 会导致很多软
  • Linux基础指令的基本操作(一)

    文章目录 Linux用户管理 xff1a 1 adduser添加用户2 passwd修改用户密码3 userdel删除用户 其他指令alias指令 取别名 whoami指令man指令 重要 bc指令unamefreedf h Linux 访
  • Linux 权限(二)权限掩码 粘滞位 详细

    文章目录 Linux权限的概念Linux权限管理01 文件访问者的分类 xff08 人 xff09 02 文件类型和访问权限 xff08 事物属性 xff09 拥有者 xff0c 所属组 xff0c other vs root 和普通用户a
  • Linux——基础IO

    文章目录 先来段代码回顾C文件接口写文件读文件输出信息到显示器 xff0c 你有哪些方法 默认打开的三个流 stdin amp stdout amp stderr系统接口openclosewriteread文件描述符fd文件描述符的分配规则
  • boost字符串库简单使用

    boost字符串库简单使用 说明用法大小写转换字符串分割去掉字符串两边空格替换字符串 replace first replace first copy 说明 写c 43 43 程序的时候 xff0c 虽然std string有数百余函数 x
  • 线程安全下单例模式

    文章目录 什么是单例模式单例模式的特点定义对象的本质什么时候创建对象饿汉实现方式和懒汉实现方式饿汉方式实现单例模式懒汉方式实现单例模式懒汉方式实现单例模式 线程安全版本 什么是单例模式 单例模式是一种 经典的 常用的 常考的 设计模式 单例
  • Linux 线程池

    文章目录 线程池的定义使用线程池的原因基于POSIX实现的线程池基于block队列的线程池实现基于ring队列的线程池实现 设计单例模式线程池 线程池的定义 线程池就一堆已经创建好的任务线程 xff0c 初始它们都处于空闲等待状态 xff0
  • 魔都,3年,程序员到CTO

    过一个平凡无趣的人生实在太容易了 xff0c 你可以不读书 xff0c 不冒险 xff0c 不运动 xff0c 不写作 xff0c 不外出 xff0c 不折腾 但是 xff0c 人生最后悔的事情就是 xff1a 我本可以 陈素封 我可以 在
  • TCP协议

    文章目录 1 保证可靠性机制1 1 确认应答机制1 1 1确认应答机制概念1 1 2常规确认应答的工作方式1 1 3报文按序到达1 1 4 如何确认历史数据被收到1 1 5 16位序号和16确认序号 xff08 字段讲解 xff09 tcp