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

Linux网络传输层TCP协议

TCP协议

TCP 全称为 "传输控制协议(Transmission Control Protocol"). 人如其名, 要对数据的传 输进行一个详细的控制。

基于TCP应用层协议:HTTP,HTTPS,SSH,Telnet,FTP,SMTP。

TCP协议段格式

• 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;

• 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 字节头部选项: 暂时忽略;

报文来了,第一步:读取20字节;第二步:查看4为首部长度,与20比较;第三步:如果大于20字节,就根据大小读取选项。

对于紧急指针,很少见到,但是比如上传一个数据,突然想要停止就要用到了,通常tcp中紧急数据只有一个字节(0正常,1暂停,2取消),recv和send有一个标志位可以填这个:

表示带外数据,一般会新起一个线程,读取带外数据,如果要终止则通知上传数据操作停止。

但是一般不会用这个,实在传输优先,可以另开一个端口号负责接收像这些控制信息。

确认应答(ACK)机制

只要有确认应答,就代表发送数据可靠,对方收到了,保证单向可靠。图上保证了A->B信息传输可靠。

TCP通信模式

如果向上边那样,发一次回一次,会浪费很多时间等待应答,因此可以一次发多条:

这样可以提高发送效率,当然上面那种发一次回一次也有在用,但是第二种更常规一些。

为了保证确定哪些报文丢失,需要对发送和回复进行标识,这个就是序列号和确认序列号(协议段格式),比如发送1000,回复就1001,发送2000,回复2001。这里2001表示2001之前的都收到了。如果收到3001,却没收到2001,也表示3001之前的都收到了。

为什么发送和回复不能用一个序列

号呢,因为不知道对面发的到底是应答还是新的数据,也可能是建立连接请求等等。

当然为了确认发的到底是什么,报头中会设置六个标志位:URG,ACK,PSH,RST,SYN,FIN。这些标志位就是一个个比特位。

超时重传机制

超过一段时间没收到应答就会就会重传,如果没收到ACK,发送方就认为对方没收到。

像这种ACK丢了的,对方就会收到很多重复的包,根据序列号就可以把重复的包丢弃。

所以这个序列号有三个作用:确认应答,去重和按序到达。

网络状态会发生变化,那么这个超时时间间隔就要弹性变化,TCP 为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间:

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

• 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.

• 如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.

• 累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接.

连接管理机制

建立连接:三次握手

SYN标志位置1表示要建立连接,ACK置1表示回复,第三次ACK表示对回复应答。相当于双方都有肯定应答,连接建立稳定。

accept函数不参与三次握手过程。

三次握手不保证一定连接成功,但是我们认为已经成功了。如果客户端的ACK丢了,然后发数据给服务器,服务器收到这个数据会发给客户端,并把RST置1,表示需要释放异常连接,重新建立连接。

断开连接:四次挥手

FIN置1表示断开连接。要取得双方同意才能断开连接,客户端发送断开连接请求,服务器第一次ACK是同意客户端断开连接,第二次是请求断开连接,客户端ACK表示同意服务器断开连接。

如果服务器没发FIN,就还可以发给客户端数据,客户端可以收到。

关闭一个连接除了close还可以下面这个方式,这样可以保证客户端能接收到:

how标志位可以设置为关闭写端。

四次挥手以最小的通信成本,建立了断开连接的共识,双方都不和对方通信了,也知道对方知道不和我通信了。

总共关闭了四次(客户端俩次,服务器俩次)。

不管是服务器还是客户端,维护连接都是要成本的(时间+空间)。

三次握手问题:

为什么不用两次/一次连接:SYN洪水,大量无用连接,但是三次握手稍微好一点,因为也要应答,双方消耗同样的资源。

三次握手的理由:

1.验证全双工--验证网络连通性(双方都收到一次ACK应答)。

2.确认双方都同意建立连接,并且都知道对方同意并建立连接。

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

简单来讲就是服务器挂掉了,再次重启会导致bind失败,因为关联的端口还在TIME_WAIT,需要等30-60秒左右,但是如果不想等呢?

这里有个函数:

一般这么用:

TIME_WAIT的时间是2MSL原因:

• MSL 是 TCP 报文的最大生存时间, 因此 TIME_WAIT 持续存在 2MSL 的话

• 就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服 务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错 误的);

• 同时也是在理论上保证最后一个报文可靠到达(假设最后一个 ACK 丢失, 那么 服务器会再重发一个 FIN. 这时虽然客户端的进程不在了, 但是 TCP 连接还在, 仍然可以重发 LAST_ACK);

如果我们断开连接又重新建立连接,没有这个TIME_WAIT的话可能会导致历史数据发了过去,影响现在使用,所有要有这个状态,等待历史报文消散。

流量控制

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

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

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

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

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

窗口大小实际上在三次握手时候已经交换过了。根据窗口大小调整发送速度。如果发过去窗口大小为0,对面就会等,一段时间后会发探测,等发送方有空间了就会发报文通知对面有空间可以继续发了。

PSH置1就是催促对方快点处理,留出空间。

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

滑动窗口

我们知道,像那种发一次一个应答,然后再发那种效率太低,所以基本这样的:

发送方规定一个概念:滑动窗口,在滑动窗口内的数据可以直接发送,暂时不用收到应答。

滑动窗口是发送缓冲区的一部分,所以缓冲区被分割成三部分:

接收到ACK这个窗口就向右移动。为了保证对方能接收这么多数据,滑动窗口大小应该和对方给的窗口大小同步(接受能力同步)。

如果对面的接受能力变强了,那这个滑动窗口可以变大,当然,接收能力变小了,这个窗口可以变小(右侧不动,左侧向右移动就变小了)。

这个窗口就是由两个数组下标表示的(win_start和win_end下标),收到应答直接win_start=ack_seq,win_end=win_start+win。

丢包分为三种情况:左边丢包,中间丢包,右边丢包:

 情况一:

那后面的就只会ACK1001,发送方要把1001-2000的重发(快重传或超时重传);这时候发送方滑动窗口不会移动,收到重传成功的ACK正确序号才会向右移动。

情况二:

如果2001-3000丢了,那么ACK序号一定是2001,然后窗口滑动到2001,就又变成了情况一,按照情况一的方式处理。

情况三:

和情况二一样,滑动窗口最左侧到原先最右侧,那么原先的最右侧变成了最左侧,处理方法和一同。

综上,滑动窗口最左侧其实取决于ACK的序号。流量控制就是通过滑动窗口实现的。当然这个缓冲区是环状的,滑动窗口不会越界,已发送确认的数据会被新的覆盖掉,不用清空。

拥塞控制

如果发了很多报文,都丢了,那么就有理由怀疑网络有问题,这时候不能快速重传,因为网络已经很拥堵了,就像桥上车已经很堵了,再来车会更堵。

解决网络堵塞的问题最大价值在于多个使用同一网络的主机,拥有拥塞避免的意识。

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

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

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

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

• 每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;

滑动窗口=min(应答窗口,拥塞窗口)。

因为网络是浮动的,所以拥塞窗口大小也必然是浮动的;需要不断尝试,感知网络拥堵情况,多次计算拥塞窗口大小,更新。

慢启动:

• 为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.

• 此处引入一个叫做慢启动的阈值

• 当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长

指数增长特点是启动慢,增幅大。慢启动意味着前期慢,尝试发现网络已经恢复,那么通信过程也要赶紧恢复,因此指数增长非常适应要求,当然也不能一直增长这么快,跨度太大,到阈值就该正常增长了,当然新的网络拥塞情况也会决定新的阈值。

这个阈值是前一个达到网络拥塞的拥塞窗口最大值的一半。当 TCP 开始启动的时候, 慢启动阈值等于窗口最大值。

当然这个拥塞窗口不可能一直增长,操作系统不同,上限值定义不同。

延迟应答

如果接收数据的主机立刻返回 ACK 应答, 这时候返回的窗口可能比较小,但是积压一会,等上层数据处理一些了,这时候应答会使得应答窗口更大,这个延迟时间不会超过超时时间。

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

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

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

但是不是所有包都可以延迟应答:

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

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

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

捎带应答

就说发ACK后又要发数据,可以把这两条报文合成一个,只不过ACK置1,发送数据就行。但是三次握手中前两次握手不允许捎带数据。

面向字节流及粘包问题

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

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

• 如果发送的字节数太长, 会被拆分成多个 TCP 的数据包发出;

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

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

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

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

由于这个缓冲区的存在,因此读的时候可以一次多更多的数据,这就会导致粘包问题:

• 首先要明确, 粘包问题中的 "包" , 是指的应用层的数据包.

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

• 站在传输层的角度, TCP 是一个一个报文过来的. 按照序号排好序放在缓冲区 中.

• 站在应用层的角度, 看到的只是一串连续的字节数据.

• 那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个 部分, 是一个完整的应用层数据包.

如何避免粘包问题,实际上就是明确两个包之间的边界:

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

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

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

TCP异常情况

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

机器重启: 和进程终止的情况相同(关机的时候操作系统会提醒还有任务在运行,确定要关机吗).

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

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

总结

TCP是一个可靠的链接,保证可靠性有:校验和、序列号、确认应答、超时重发、连接管理、流量控制、拥塞控制。

不仅如此,还提高了性能:滑动窗口、快速重传、延迟应答、捎带应答、定时器。

而这些都是UDP不能保证的,但是不能简单的评判UDP和TCP快慢。

http://www.dtcms.com/a/568635.html

相关文章:

  • 做企业网站备案收费吗怎么修改网站标题
  • 机器视觉---Intel RealSense SDK 2.0 开发流程
  • 【AI基础篇】Transformer架构深度解析与前沿应用
  • QuickNacos
  • 用Python来学微积分30-微分方程初步
  • Opencv(七) : 图像颜色替换
  • Skywalking运维之路(Skywalking服务搭建)
  • 网站开发及建设赔偿条款中国最牛的十大企业
  • 广州全运会即将开幕,获得文远知行自动驾驶技术支持
  • 在智能制造语境下理解ISA-95、IIoT和UNS
  • 网站建设 服务器 预算报价清单企业展厅设计公司虎
  • 算法学习入门---前缀和(C++)
  • 一键生成系统架构图
  • 2025国产MOM系统全景透视:谁在领跑智能制造新赛道?
  • 系统架构设计师备考第64天——网络构建关键技术
  • 使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第三十五讲)
  • 网站备案个人可以做吗thinkphp cms开源系统
  • 一般电脑网站建设及运营多少钱中国最新军事新闻报道
  • Elasticsearch+Logstash+Filebeat+Kibana部署[7.17.3版本]
  • Elasticsearch单机部署全指南
  • 前端实战开发(三):Vue+Pinia中三大核心问题解决方案!!!
  • 从零开始:开发一个仓颉三方库的完整实战
  • 本机 MongoDB 注册系统服务、启用security认证
  • Nginx代理配置的“双斜杠陷阱“:从IP到域名的完整排查与解决指南
  • 三水容桂网站制作天眼查企业信息查询平台
  • HarmonyOS鸿蒙开发:Swiper组件实现精美轮播图
  • 互联网大厂前端面试实录:HTML5、ES6、Vue/React、工程化与性能优化全覆盖
  • 宣威网站建设公司做钓鱼网站要什么工具
  • VBA中类的解读及应用第二十九讲: 最简单的类属性建立
  • 金蝶用友数据分析:奥威BI解锁ERP智能决策新纪元