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

Linux -- 传输层协议TCP

TCP协议

TCP 全称为 "传输控制协议( Transmission Control Protocol "). ⼈如其名, 要对数据的传输进⾏⼀个详细的控制。它提供了一种可靠的、面向连接的、基于字节流的数据传输服务。TCP 的主要特点是确保数据在传输过程中不丢失、不重复,并且按顺序到达。

TCP协议格式

TCP 报文段由两部分组成:TCP 报头TCP 数据区(即实际要传输的应用层数据),TCP的报头长度,包括固定的部分20字节必须包含的字段,除了选项部分的字段,如源端口、目的端口、序号、确认号等。选项部分:0~40 字节(可选字段,如 MSS 协商、窗口缩放、时间戳等,常见为 4 字节的 MSS 选项)。

TCP 首部长度通过首部中的 “数据偏移”(Data Offset)字段标识,单位是 “32 位字”(4 字节)。

例如:

如果数据偏移为5,表示首部长度=4x5=20字节,说明此时只有固定部分。

如果数据偏移为6,表示首部长度=6 x4=24字节,说明此时选项部分也有数据。

数据偏移的最大值为15,即报头长度最大为60字节。

与UDP协议不同的是,TCP协议的数据报大小并不固定,需要在通信双方建立连接时相互通过MSS数据区最大长度确认。

MSS 并非随意设定,而是由底层网络的最大传输单元(MTU) 决定。
MTU 是 “数据链路层帧能携带的最大 IP 数据包长度”(含 IP 报头),不同网络的 MTU 默认值不同:

MSS 的计算逻辑:MSS = MTU - IP 报头长度 - TCP 报头长度

以太网默认场景:总长度 = 20(TCP 报头) + 1460(MSS) = 1480 字节(再加上 20 字节 IP 报头,总 IP 数据包长度为 1500 字节,恰好等于 MTU)

32位序号/32位确认号:将接受缓冲区的数组下标作为每一个字节对应的确认号,发送给对方时需要报头中需要带上,接收方回自动返回序号加1的确认号,发送方可以知道自己发送的消息成功到达了,这就是TCP的确认应答机制,待会我们会详细说。

6位标志位:

URG: 紧急指针是否有效
ACK: 确认号是否有效
PSH: 提⽰接收端应⽤程序⽴刻从TCP缓冲区把数据读⾛
RST: 对⽅要求重新建⽴连接; 我们把携带RST标识的称为复位报⽂段
SYN: 请求建⽴连接; 我们把携带SYN标识的称为同步报⽂段
FIN: 通知对⽅, 本端要关闭了, 我们称携带FIN标识的为结束报⽂段
16位窗⼝⼤⼩:告诉对方自身的接受缓冲区还有多少空间,用于流量控制。
16位校验和:发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP⾸部, 也包含TCP数据部分。
16位紧急指针: 标识哪部分数据是紧急数据。

确认应答机制

系统在发送数据的时候不会将一个个字节单独发送,这样效率太慢了,而是将接受缓冲区中的一批数据一起发送,然后应答最后一个确认序号即可。当接收到1001的确认应答时,说明前面1-1000的报文都收到了。

超时重传机制

一个主机在给另一个主机发送数据时可能因为某些原因另外一个主机没有收到,也就没有返回确认应答,一段时间以后主机会再次重复上一次的数据直到另一个主机返回确认应答确认收到了数据。需要注意的是,如果接收方主机的确认应答在返回的时候丢包了,发送主机在一定时间接受不到也会进行重发,这就是为什么TCP协议是可靠的。因此接受方主机有可能会收到很多重复数据。那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉. 这时候我们可以利⽤前⾯提到的序列号, 可以很容易做到去重的效果,并且使报文可以按序到达。

需要注意的是,我们一般通信双方返回的确认应答可能是一个只有ACK标志位的报头,也可能在发送数据的时候将确认序号带上,这叫做捎带应答机制。

连接管理机制

正常情况下TCP需要通过三次握手建立连接,四次挥手断开连接。

三次握手连接:当客户端发起connet请求时会进入阻塞等待服务器应答,接着系统就会自动进行三次挥手。客户端发送标志位SYN的同步报⽂段,服务器收到以后会返回一个SYN和ACK标志位的报头,表示自己收到了你的连接请求,同时也要和你发起连接请求,客户端在收到以后会给服务端发出确认应答,此时客户端就认为自己已经建立好了连接,服务器端需要收ACK以后才能建立成功连接。需要注意的时,最后一个ACK有可能丢包,所以建立连接的过程是存在失败的可能性的。

四次挥手端开连接:双方都可以发起断开连接的请求,断开连接需要对方的同意,所以双方都需要发送FIN并且接受来自对方的ACK,才可以确定自己断开了连接。

服务端状态转化:

[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状态
为什么握手是三次连接而挥手是四次连接?因为有捎带应答机制,在服务器发送SYN的时候就可以将ACK一起携带,由于TCP是全双工的,三次可以做到最小次数确认双方的接受与发送信道都是完好可以正常通信的。断开连接时,服务端收到 FIN 后,只能先回一个 ACK,因为服务端可能还有数据没发完服务端需要等应用层处理完数据、关闭连接后,才能发送自己的 FIN。所以ACK 和 FIN 之间有时间差,不能合并成一个包。
总结:三次握手可以捎带应答,是因为 SYN 和 ACK 可以同时发送;四次挥手不能捎带应答,是因为 ACK 和 FIN 之间可能有延迟,服务端还没准备好关闭连接。

理解TIME_WAIT状态

我们有些时候在服务器关闭再马上重启以后会出现bind erro,这是因为虽然上一次服务器终止了,但是TCP协议层的连接没有完全断开,端口还被占用着,所以无法重新bind同一个端口。

我们可以做一个测试来说明这种情况,然后启动client,然后⽤Ctrl-C使server终⽌,这时⻢上再运⾏server, 结果是:

服务器在上一次关闭以后会与客户端断开连接然后进入FIN_WAIT2,然后经过一段时间的等待没有接受到来自客户端的断开请求,就会进入TIME_WAIT。此时服务器需要等待两个MSL(maximum segmentlifetime)的时间后才能回到CLOSED状态,在TIME_WAIT期间仍然不能再次监听同样的server端⼝。

想⼀想, 为什么是 TIME_WAIT 的时间是 2MSL ?

MSL TCP 报⽂的最⼤⽣存时间, 因此 TIME_WAIT 持续存在 2MSL 的话就能保证在两个传输⽅向上的尚未被接收或迟到的报⽂段都已经消失(否则服务器⽴刻重启, 可能会收到来⾃⼀个进程的迟到的数据, 但是这种数据很可能是错误的);
同时也是在理论上保证最后⼀个报⽂可靠到达(假设最后⼀个ACK丢失, 那么服务器会再重发⼀个FIN. 这时虽然客⼾端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK);

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

server TCP 连接没有完全断开之前不允许重新监听, 某些情况下可能是不合理的

服务器需要处理⾮常⼤量的客⼾端的连接(每个连接的⽣存时间可能很短, 但是每秒都有很⼤数量的客⼾端来请求).这个时候如果由服务器端主动关闭连接(⽐如某些客⼾端不活跃, 就需要被服务器端主动清理掉), 就会产⽣⼤量TIME_WAIT连接.
由于我们的请求量很⼤, 就可能导致TIME_WAIT的连接数很多, 每个连接都会占⽤⼀个通信五元组(源ip, 源端⼝, ⽬的ip, ⽬的端⼝, 协议). 其中服务器的ip和端⼝和协议是固定的. 如果新来的客⼾端连接的ip和端⼝号和TIME_WAIT占⽤的链接重复了, 就会出现问题.
使⽤ setsockopt ()设置 socket 描述符的 选项 SO_REUSEADDR 1 , 表⽰允许创建端⼝号相同
但IP地址不同的多个 socket 描述符。

理解 CLOSE_WAIT 状态

如果客户端在退出以后而服务器不退出,那么服务器就会进入CLOSE_WAIT。一个多线程的服务器出现大量的CLOSE_WAIT意味着出现了Bug,会导致系统的可用资源变少,系统也会越来越卡。原因就是服务器没有正确的关闭socket , 导致四次挥⼿没有正确完成. 这是⼀个 BUG . 只需要加上对应的 close 即可解决问题。

滑动窗口

在系统的收缓冲区中有一个以数组下标为起始的区域称为滑动窗口。一个是start,一个是end,end=start+滑动窗口大小,start是上一次的确认序号。

在系统中收发报文并不是一收一发的,而是⼀次发送多条数据, 就可以⼤的提⾼性能(其实是将多个段的等待时间重叠在⼀起了)。

窗⼝⼤⼩指的是⽆需等待确认应答⽽可以继续发送数据的最⼤值. 上图的窗⼝⼤⼩就是4000个字 节(四个段),即一次性可以发4000条报文。
发送前四个段的时候, 不需要等待任何ACK, 直接发送;
收到第⼀个ACK后, 滑动窗⼝向后移动, 继续发送第五个段的数据; 依次类推;
操作系统内核为了维护这个滑动窗⼝, 需要开辟发送缓冲区来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉,这里的删除可以是后来的数据直接覆盖。
那么如果出现了丢包, 如何进⾏重传? 这⾥分两种情况讨论.
情况⼀: 数据包已经抵达, ACK被丢了,可以通过后续的ACK确认,假设1001的ACK丢失了,但是2001的ACK正确到达,那么发送方也可以确认消息是到达的了。
情况⼆: 数据包就直接丢了.
当某⼀段报⽂段丢失之后, 发送端会⼀直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是 1001" ⼀样。
如果发送端主机连续三次收到了同样⼀个 "1001" 这样的应答, 就会将对应的数据 1001 - 2000 重新发送。
这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前
就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中。操作系统通过序列号精确缓存乱序到达的数据,虽未立即确认,但内部已标记为“已收到”,一旦缺失段补齐,即可一次性确认大量数据。
这种机制被称为 "⾼速重发控制"(也叫 "快重传")。
需要注意的是,如果一次缺失几段报文,那么操作系统只会一段一段的补发,补发一段滑动窗口向后移动一段,直到所有报文都确认收到了。

流量控制

接收端处理数据的速度是有限的, 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继⽽引起丢包重传等等⼀系列连锁反应。因此TCP⽀持根据接收端的处理能⼒, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control)。
接收端将⾃⼰可以接收的缓冲区剩余空间⼤⼩放⼊ TCP ⾸部中的 "窗⼝⼤⼩" 字段, 通过ACK端通知发送端。
窗⼝⼤⼩字段越⼤, 说明⽹络的吞吐量越⾼。
接收端⼀旦发现⾃⼰的缓冲区快满了, 就会将窗⼝⼤⼩设置成⼀个更⼩的值通知给发送端。
发送端接受到这个窗⼝之后, 就会减慢⾃⼰的发送速度。
如果接收端缓冲区满了, 就会将窗⼝置为0; 这时发送⽅不再发送数据, 但是需要定期发送⼀个窗⼝探测数据段, 使接收端把窗⼝⼤⼩告诉发送端。发送放也会发送一个窗口探测来获取接收方的缓冲区是否刷新。

拥塞控制

TCP拥有了滑动窗口和流量控制已经能够高效可靠的传输数据了,可是在复杂的网络情况中,网络上可能有很多计算机同时发送了很多数据,此时就会导致网络比较拥堵,在不知道当前网络状态的情况下贸然发送大量数据可能会使网络更加的拥堵。

所以TCP引入了慢启动机制,先发少量的数据探路,摸清了网络拥堵情况,再决定按照多大的速度传输数据。

此处引⼊⼀个概念称为拥塞窗⼝
发送开始的时候, 定义拥塞窗⼝⼤⼩为1;
每次收到⼀个ACK应答, 拥塞窗⼝加1;
每次发送数据包的时候, 将拥塞窗⼝和接收端主机反馈的窗⼝⼤⼩做⽐较, 取较⼩的值作为实际发送的窗⼝;
像上⾯这样的拥塞窗⼝增⻓速度, 是指数级别的. "慢启动" 只是指初使时慢, 但是增⻓速度⾮常快
为了不增⻓的那么快, 因此不能使拥塞窗⼝单纯的加倍.
此处引⼊⼀个叫做慢启动的阈值
当拥塞窗⼝超过这个阈值的时候, 不再按照指数⽅式增⻓, ⽽是按照线性⽅式增⻓

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

延迟应答

如果接收数据的主机⽴刻返回ACK应答, 这时候返回的窗⼝可能⽐较⼩.
假设接收端缓冲区为1M. ⼀次收到了500K的数据; 如果⽴刻应答, 返回的窗⼝就是500K;
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
在这种情况下, 接收端处理还远没有达到⾃⼰的极限, 即使窗⼝再放⼤⼀些, 也能处理过来;
如果接收端稍微等⼀会再应答, ⽐如等待200ms再应答, 那么这个时候返回的窗⼝⼤⼩就是1M;
y⼀定要记得, 窗⼝越⼤, ⽹络吞吐量就越⼤, 传输效率就越⾼. 我们的⽬标是在保证⽹络不拥塞的情况下尽量提⾼传输效率;
那么所有的包都可以延迟应答么? 肯定也不是
数量限制: 每隔N个包就应答⼀次;
时间限制: 超过最⼤延迟时间就应答⼀次;

面向字节流

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

1.调用write时,数据先会写入发送缓冲区

2.如果发送的字节数太长,会被拆分成多个TCP的数据包发出,这里利用的是滑动窗口和流量控制

3.如果发送的数据太短,就会在缓冲区中等待,等待缓冲区中长度差不多了,或者其他合适的时机就会发送出去

4.接受数据的时候,数据是从网卡的驱动程序到达内核的接受缓冲区

5.然后应用程序可以调用read从缓冲区读取数据

6.TCP的连接既有发送缓冲区也有接受缓冲区,那么对于这样的一个连接可以读数据也可以写数据,这样的概念我们称为全双工

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

写一百个字节数据时可以一次性写入,也可以多次调用write进行写入

读一百个字节的时候也是如此,可以一次性读取一百个字节,也可以多次读取

粘包问题

  ⾸先要明确, 粘包问题中的 "包" , 是指的应⽤层的数据包. 在TCP的协议头中, 没有如同UDP⼀样的 "报⽂⻓度" 这样的字段, 但是有⼀个序号这样的字段. 站在传输层的⻆度, TCP是⼀个⼀个报⽂过来的. 按照序号排好序放在缓冲区中. 站在应⽤层的⻆度, 看到的只是⼀串连续的字节数据. 那么应⽤程序看到了这么⼀连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是⼀个完整的
应⽤层数据包.
那么如何避免粘包问题呢? 归根结底就是⼀句话, 明确两个包之间的边界.
  对于定⻓的包, 保证每次都按固定⼤⼩读取即可; 例如上⾯的Request结构, 是固定⼤⼩的, 那么就
从缓冲区从头开始按sizeof(Request)依次读取即可; 对于变⻓的包, 可以在包头的位置, 约定⼀个包总⻓度的字段, 从⽽就知道了包的结束位置; 对于变⻓的包, 还可以在包和包之间使⽤明确的分隔符(应⽤层协议, 是程序猿⾃⼰来定的, 只要保证分隔符不和正⽂冲突即可);

TCP异常情况

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

机器重启:与进程终止情况一致

机器掉电或者网线断开:接收端认为连接还在,一旦接收端有写入的操作,接收端发送链接已经不在了,就会进入reset。即使没有写入的操作,TCP内部也保活的定时器,会定时询问对方是否还在,如果对方没有回复即会把连接释放。

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

相关文章:

  • 浅谈 Protobuf——高效、安全的跨语言通信基石
  • SpringBoot安全进阶:利用门限算法加固密钥与敏感配置
  • [工作流节点17] 数据校验与错误处理机制:让自动化更安全、更可靠
  • 佛山高端网站制作wordpress免费用户
  • 《SaaS双优实战:数据驱动下的体验迭代与性能攻坚全指南》
  • 人力资源管理的思维方式学习笔记6
  • Git--
  • 怎么做车载mp3下载网站企业案例网站
  • [论文阅读]PromptArmor: Simple yet Effective Prompt Injection Defenses
  • xx网站建设策划方案网站开发必须要要掌握的语言
  • SpringBoot13-小细节
  • K8S探针-Pod创建流程-kubeadm证书续期-VPA实战
  • SQLite 别名
  • wstunnel 实现ssh跳板连接
  • QML之四转圈等待指示器
  • TOGAF®标准与应对时代冲击的韧性架构
  • 【深入理解计算机网络06】数据链路层:详解信道划分与介质访问控制
  • ACL限制研发部允许总裁办
  • 个人网站建站指南东莞营销推广
  • 服务器架构模型
  • 【C++】stack与queue的使用与模拟实现
  • JSDoc注释
  • 第4章:函数调用(Function Calling / Tool Calling)—让 AI 调用你的 API
  • LLaVA-Video论文阅读
  • 精品课程网站建设意义北京小程序网站制作
  • Mean Normalization|均值归一化
  • 可以做网站素材的服装手机安装wordpress
  • StarRocks 是如何进行并行计算
  • 私域整体结构的顶层设计:基于“开源AI智能名片链动2+1模式S2B2C商城小程序”的体系重构
  • 基于SpringBoot和Vue的超市管理系统