传输层协议 TCP [ 上 ]

传输层协议 TCP [ 上 ]
上文学习了传输层的协议之一UDP接下来我们来好好学习一下传输层另外一个更加重要的协议 ——TCP协议TCP 协议TCP 协议段格式tcphdrstruct tcphdr { __be16 source; // 16位源端口号 __be16 dest; // 16位目的端口号 __be32 seq; // 32位序列号 __be32 ack_seq; // 32位确认号 #if defined(__LITTLE_ENDIAN_BITFIELD) __u16 res1:4, // 保留位 doff:4, // TCP头长度单位为4字节 fin:1, // FIN标志 syn:1, // SYN标志 rst:1, // RST标志 psh:1, // PSH标志 ack:1, // ACK标志 urg:1, // URG标志 ece:1, // ECE标志用于拥塞控制 cwr:1; // CWR标志用于拥塞控制 #elif defined(__BIG_ENDIAN_BITFIELD) __u16 doff:4, res1:4, cwr:1, ece:1, urg:1, ack:1, psh:1, rst:1, syn:1, fin:1; #else #error Adjust your asm/byteorder.h defines #endif __be16 window; // 16位滑动窗口大小 __sum16 check; // 16位校验和 __be16 urg_ptr; // 16位紧急指针 };TCP 是如何实现将报头和有效载荷进行分离的前提说明TCP 是面向字节流并提供可靠传输。面向字节流是 TCP 独立的数据模型设计并非为实现可靠性而设立可靠性由确认、重传、序号、去重等机制保障。字节流模型的作用是允许传输层自由拆分、合并字节优化网络传输效率。所以从机制上来说确实也是字节流比较好实现。具体体现有1. 可以合并小包最直观减少报文头部开销IP 首部 TCP 首部 一共至少 40 字节。 如果你大量发送很小的数据比如每次仅几个字节聊天、按键上报报文模型每个小包都带上 40 字节头部头部占比极高带宽浪费严重字节流模型内核可以短暂缓存多个小包攒成一个较大段一起发送。 一次头部承载更多有效载荷。TCP 的 Nagle 算法本质就是利用字节流特性做小包合并。 ⚠️ UDP 不能开类似机制一旦合并接收端分不清原始消息边界。2. 丢包重传时可以精细重传字节不用重传整条消息举例子 一条大数据如果是报文模型假设一条消息 10000 字节被底层分成多个 IP 分片。 只要任意一个分片丢失 → 整条消息作废必须完整重传 10000 字节。TCP 字节流模型用字节序号标记每一段数据。 如果中间某一段字节丢失只需要重传丢失的那一段字节不需要重传前后完好数据。这里关键点 TCP 的序号是按字节编号天然适配字节流 如果是消息模型序号一般按「消息」编号最小重传单元是整条消息。3. 灵活适配 MSS充分利用最大报文长度链路有 MSS 限制单个 TCP 段最大承载数据长度。报文模型应用送出一段长度不规则的数据内核无法调整如果应用消息大于 MSS 只能分片小于 MSS 只能空着。字节流模型内核可以持续填充缓冲区尽可能把每个 TCP 段填满到 MSS 上限。 每个数据包有效载荷最大化减少数据包数量降低路由器处理压力。4. 拥塞控制更好发挥拥塞控制的目标尽可能填满链路又不超限。 字节流模型下发送缓冲区是一整块连续字节。拥塞窗口允许多少字节就送出多少字节不受应用消息边界割裂。报文模型发送受消息边界切割。哪怕拥塞窗口还有空闲容量若下一条应用消息太大 / 太小没法充分利用现有窗口。TCP协议的标准的报头的长度是20 个字节也就是一般而言TCP报头也是一个定长的报头也就是将来来一个TCP报文我们只需要进行 data 指针位置的加 20 个字节就可以提取出来了。不过TCP协议当中还有一个选项字段可以为 0 字节也可以有字节这个后面我们自会理解所以一个TCP报头的完整长度是 20 个字节加上选项字段这个选项字段正常是为 0 字节的。因为有不定长的选项字段存在我们该如何保证报文的报头和有效载荷进行分离呢所以在标准报头 20 字节当中存在一个叫做数据偏移的字段也叫做4 位首部长度就是 4 个比特位当成无符号来算就是【00001111】总共是【015】个数据范围TCP协议规定【015】的数据范围的单位是 4 字节也就是说TCP报头的大小是【015*4】【060】字节但是对于TCP来说其报头的标准长度最少都是 20 个字节所以TCP的报头长度应该是x*420字节也就是 4 位首部长度的范围应该是【01011111】即【2060】字节又因为TCP的报头的基本单位是 4 个字节所以后面的TCP报头是肯定可以整除 4 个字节的【20242832......60】所以将来选项的基本单位也是 4 字节的所以报头和有效载荷是如何进行分离的呢就是我们首先无脑读取报文的前 20 个字节然后提取 4 为首部长度直接*4然后减去 20剩下的就是选项的所占字节大小最后剩下的就是有效载荷了我们在谈UDP协议的报文格式的时候UDP有其对应的报头长度 8 字节定长其中报头中还有整个UDP报文的长度的但是为什么TCP协议格式中为什么没有报文大小只有报头大小呢 没有对应的数据部分的长度其实从这里我们就可以窥探出一个认知了在操作系统内部操作系统可以知道UDP报文的边界的知道UDP报文从哪里开始从哪里结束可以知道一个完整的UDP报文因为UDP是面向数据报的不过TCP是面向字节流的在操作系统内部发送了许多TCP数据段他也区分不清楚是不是一个完整的报文所以TCP不能够来表示一个报文完整的长度所以对于TCP协议来说不需要也不能设置一个叫做报文长度的字段因为将来来的TCP报文是不是一个完整报文我操作系统吃不准只能是将报头拿走剩下的数据交给接收缓冲区当中由用户上层自行去分析这么多个按序拼接好的有效载荷可是就不会说数据和下一个报头黏在一堆的情况嘛这种情况是不可能存在的因为每收到一个报文都是一个独立的sk_buff但是TCP是面向字节流的报文的有效载荷在接收缓冲区当中会黏在一起如果UDP有接收缓冲区我们可以理解为数据报是由一个一个的队列维护的这样就不会产生和字节流的黏在一起的问题了。TCP 面向字节流 → 无消息边界 → 必然粘包 → 必须靠自定义协议解决。UDP 面向数据报 → 自带消息边界 → 不会粘包。TCP 如何将自己的有效载荷交付给上一层当TCP数据段到达时内核会根据目的端口号找到对应的套接字socket并将数据传递给该套接字关联的应用程序。这个过程是通过内核的协议栈和套接字管理机制实现的确保数据能够准确地到达正确的应用程序。可靠性的本质确认应答ACK机制给个实际的例子两名同学在相距100m的操场上对话小 B吃了吗小 S吃了其实如果小 S 没有回复小 B 就可以认为小 S 没有收到自己的消息。但是小 S 回复了也没办法确定小 B 有没有听到所以小 S好可这样又怎么保证小 B 一定听到了呢所以这个世界上在长距离传输的时候根本就不存在 100% 可靠的协议所以正确理解可靠性具有应答可以保证对历史消息的可靠性是 100% 的通信中最新的报文永远没有应答最新可靠性无法保证TCP一般的通信过程暂时这么说后面会更加正确理解最朴素的认识在TCP当中如果要保证可靠性处于核心地位的可靠性叫做确认应答机制就是在客户端和服务端客户端向服务端发送消息硬性规定说服务端要给客户端应答这就是确定了客户端向服务端的可靠性服务端向客户端发送消息客户端响应服务端给服务端做应答这就保证说服务端到客户端的可靠性的保证不过这种模式是一个一个串行的走的效率太低下了所以我们就有了TCP传递信息时更加通用的过程TCP传递信息时更加通用的过程我们可以一次性发送多个报文这就相当于是并行的由客户端向服务端发报文信息这样的效率是明显提高了但是一系列问题也就随之而来假设一次性发送过来的这么多报文数据当中有其中一两条没有发送到服务端服务端就没办法做应答造成客户端没法接收到对应的应答哪一个呢客户端怎么知道所以为了能够更加细致的确认应答TCP协议报头中就引入了32 位序号和32 位确认序号我们来好好说说假设客户端需要向服务端发送一系列数据包每个数据包包含一个报文。为了提高效率客户端选择一次性发送多个报文而不是逐个发送。客户端发送报文客户端C一次性发送多个报文黄色实线箭头到服务端S。这些报文可能包含不同的数据如请求、命令或数据更新。服务端接收报文服务端S接收到这些报文后需要确认每个报文是否成功接收。服务端通过发送确认应答绿色虚线箭头来告知客户端哪些报文已经成功接收。确认应答的作用确认应答绿色虚线箭头包含一个确认序号表示服务端期望接收的下一个字节的序号。例如如果服务端成功接收了序号为1到10的报文它会发送一个确认序号为11的应答。客户端收到确认应答后知道序号为1到10的报文已经被服务端成功接收可以继续发送后续报文。指定报文序号之前的所有信息已经全部收到--- 这一条很重要的规定处理丢失的报文如果服务端发现某个报文丢失例如序号为5的报文没有收到它不会发送确认序号为6的应答而是继续发送确认序号为5的应答告知客户端需要重传序号为5的报文。客户端收到这个重复的确认应答后知道序号为5的报文丢失需要重新发送该报文。所以确认序号就是对端下一次所要的报文那么为什么会有两个序号一个序号、一个确认序号呢说人话TCP 是全双工通信双方可以同时发送数据与应答。同一个报文既要携带本机发送数据的序号也要携带对远端数据的确认序号。如果只设一个序号字段无法区分 “我发的数据” 和 “我对你的确认”。同时为了提高效率TCP 使用捎带应答机制应答信息直接封装在带有有效载荷的报文中不需要单独发送仅含报头的纯 ACK 包。因此必须用两个独立的序号字段分别标识才能正确区分发送与确认。你客户端和朋友服务端边聊天边确认消息你发我说的话“今晚吃啥”同时确认“你上一句我收到了”朋友回他说的话“吃火锅”同时确认“你刚才问吃啥我收到了” 一句话里同时包含 “我发的内容” 和 “我对你的确认”。如果只有一个位置写字要么只能说话、要么只能确认没法同时干两件事。️ 对应到 TCP序号 我现在发给你的数据我要说的话确认序号 我已经收到你之前的数据我对你的确认捎带应答 一边说话一边确认不单独发 “我收到了” 三个字详细说明在TCP协议中数据传输的可靠性是通过序号和确认序号来保证的。这两个序号在TCP报文段中扮演着不同的角色序号是用来标识TCP报文段中的数据字节流的位置。目的是为了确保数据的顺序性允许接收方正确地重新组装从发送方接收到的数据片段。是标识本报文段的数据的第一个字节的序号。确认序号是用来指示接收方期望从发送方接收的下一个字节的序号。目的是为了告知发送方哪些数据已经被成功接收从而触发发送方发送更多的数据或重传丢失的数据。如果ACK标志位被设置此字段值加1即是下一个期望接收的字节序号。【客户端 C】 【服务端 S】 发送缓冲区起始: seq_c 1000 发送缓冲区起始: seq_s 2000 ┌───────────────────────────────────────────────────────────────┐ │ C → S 报文: │ │ • 序号(Seq) 1000 ← C 自己发送缓冲区的下标 │ │ • 确认序号(Ack) 2000 ← C 期望 S 下次从 2000 发数据过来 │ │ • 数据: Hello (5字节) │ └───────────────────────────────────────────────────────────────┘ ↓ ┌───────────────────────────────────────────────────────────────┐ │ S → C 报文 (捎带应答): │ │ • 序号(Seq) 2000 ← S 自己发送缓冲区的下标 │ │ • 确认序号(Ack) 1005 ← S 已收到 C 的 1000~1004下次要 1005 │ │ • 数据: Hi (2字节) │ └───────────────────────────────────────────────────────────────┘ ↑ ┌───────────────────────────────────────────────────────────────┐ │ C → S 下一个报文: │ │ • 序号(Seq) 1005 │ │ • 确认序号(Ack) 2002 │ └───────────────────────────────────────────────────────────────┘TCP报文由TCP报头和可能的有效载荷数据组成。TCP报头包含了序号和确认序号等重要信息捎带应答Piggybacking在TCP中捎带应答是一种优化技术它允许接收方在发送数据即有数据要发送给发送方时将对先前接收数据的应答包含在同一个报文中。这样可以减少单独发送应答报文的次数提高网络效率。报头确认应答 有效载荷数据不是单单的只有报头即只有单纯的应答客户端发送数据客户端发送一个TCP报文序号为100包含数据字节101到105。服务端接收并发送数据服务端接收到数据后发送一个TCP报文作为应答其中包含确认序号106表示已成功接收到序号为105的数据并期望接收下一个序号为106的数据。有效载荷如果服务端也有数据要发送给客户端这些数据将作为有效载荷包含在同一个报文中。为什么需要两个序号来区分序号用于标识发送的数据字节流确保接收方可以正确地重新组装数据。确认序号用于告知发送方哪些数据已经被成功接收触发发送方的后续动作如发送更多数据或重传丢失的数据。通过使用两个序号TCP协议能够确保数据的可靠传输同时通过捎带应答优化网络通信效率。这种设计允许TCP在保证数据完整性和顺序性的同时也能够有效利用网络带宽。好了对应TCP数据从发送端的发送缓冲区 拷贝 到对端的接收缓冲区中这就有一个问题上面也说过了在对端接收缓冲区如果满了的话那么就会导致发送的报文被丢弃这已经不是可不可靠性的问题了因为可以通过超时重传等等的机制来保证但是这里带来的问题不就是浪费而是一种低效如果满了的话还一次性又发来一大堆然后又丢了这不就很低效浪费吗所以发送端是需要知道对端的接收缓冲区的当前接收能力是由对端接收缓冲区中剩余空间的大小所决定的其实我们上面也说了发送端发送消息对端原则上是需要给发送端做应答的不管是捎带还是普通应答而只要对端给应答那么不就是双方互相在进行报头 / 报文的传输吗双方就需要知道对应对端的接收缓冲区的剩余空间大小所以TCP协议中有一个关键字段 ---16 位窗口大小这个16 位窗口大小的内容就是接收缓冲区剩余空间的大小是自己的不是对端的接收缓冲区的剩余空间的大小我们所构建的报文一定是给对方的当然将自己的剩余缓冲区空间大小告诉对方所以尽管可以一次性并行的给对端发送报文但是也是有上限的这个上限取决于对端接收缓冲区剩余空间的大小我们将这种按照对端接收缓冲区的接受能力来动态调整我们发送速度的机制称为 ---流量控制 多发多少发少合理控制的机制考虑了可靠性一定程度上防止了丢包但是流量控制更加考虑的是效率问题TCP协议当中还有一个校验和这个我们不用关心TCP协议中的校验和是一个重要的机制用于确保数据在传输过程中的完整性和准确性。以下是关于TCP校验和的简单介绍检测数据完整性TCP校验和的主要功能是检测数据在传输过程中是否发生了错误。由于网络传输可能会受到干扰如电磁干扰、硬件故障等数据可能会被损坏。通过校验和机制接收端可以验证数据是否与发送端发送的数据一致。提高可靠性TCP是一个面向连接的可靠协议校验和是其可靠性机制的重要组成部分。如果接收端发现校验和不匹配可以要求发送端重新发送数据从而确保数据的正确性。还有一个16 位紧急指针需要了解这个我们就需要将剩下的标志位谈清楚TCP协议报头中有一个保留位6 位也就是还没想好该怎么用知道我们想使用再用除了保留的6 位后面还有6 个标志位协议的本质就是结构体tcphdr结构体当中含有条件编译是对应位段的这也说明了内核已经可以自己进行大小端的区分了其中位段是一种数据结构通常用于将一个字节8 位或多个字节划分为多个字段每个字段可以表示不同的信息。位段的设计允许在有限的空间内存储多个标志或状态信息。这是我们C 语言所学习的现在知道了吧位段主要应用在网络通信中其实所谓的标志位本质就是报头中的比特位我们先来说说为什么需要有标志位我们先简单谈谈TCP的三次握手后面会谈到TCP的三次握手的详细过程在这个世界上我们正常通信之前有许多准备工作有的是没有准备工作的比如发送邮件这种直接发送的我们称之为面向数据报但是如果要打电话进行双方间的通信的话需要先拨通电话拨通之后对方需要将电话接起来这样的话电话就建立好通信了。TCP也是如此TCP在正常通信之前需要进行三次握手你可以做我女朋友吗建立连接的请求--- 可以呀什么时候 --- 就现在连接关系建立本质就是达成共识往后就是正常通信了这样客户端在某一时刻向接收端发送报文的类型是不一样的那么可以有多个客户端同时向服务端发送各种各样的报文数据这样就会导致接收方收到的TCP报文一定会存在不同的类型针对不同的报文类型接收方要有对应的不同的做法双方传输的至少是含有报头的所以报头当中一定存在标志报文类型的字段在TCP协议中6 个标志位也称为控制位是TCP头部的重要组成部分用于控制TCP连接的建立、维护和终止等操作。以下是这 6 个标志位的详细介绍1.SYNSynchronize Sequence Numbers作用用于建立连接。当SYN标志位被设置为1时表示这是一个连接请求或连接接受报文。应用场景在TCP的三次握手过程中第一次握手客户端向服务器发送连接请求和第二次握手服务器接受连接请求并回复都会设置SYN 标志位。客户端发送SYN报文时表示请求建立连接服务器收到SYN报文后也会发送一个带有SYN标志位的报文作为响应。【连接请求 接受连接响应】2.ACKAcknowledgment作用确认号Acknowledgment Number字段是否有效。如果ACK标志位被设置为1则表示确认号字段是有效的接收方通过确认号来确认已收到的数据。表示该报文是一个应答报文应用场景在TCP通信中除了第一次握手的SYN报文外几乎所有的TCP报文都会设置ACK 标志位。例如服务器在收到客户端的SYN报文后会发送一个带有ACK标志位的SYN-ACK报文表示确认收到了客户端的连接请求。三次握手建立之前不能正常通信前两次握手不能发送像 Hello World 的数据就是三次握手没有完成也就是只能互发报头了那么客户端给服务端发送消息之前不是要进行流量控制吗那么第一次发送报文的时候不是还不清楚对端的接收缓冲区的剩余空间大小吗注意我们第一次发数据是第一次给对端服务器发报文吗并不是报文而是说双方在通信之前就已经完成三次握手了三次握手期间已经发送过报头了绝对不会出现数据溢出丢弃的问题了3.FINFinish作用用于终止连接。当FIN标志位被设置为1时表示发送方已经完成数据发送希望关闭连接。和三次握手类似客户端给服务端说我不想玩了想要断开连接服务端好呀服务端我也要跟你断开连接客户端好呀。所以说为什么是 4 次挥手主要是TCP是全双工的一方关闭就是只关闭一个通信信道如果服务端有的还没发完客户端发完了那就客户端信道关了等服务端发完了也就双方一起实现了完全关闭了应用场景在TCP的四次挥手过程中发送方客户端或服务器在完成数据传输后会发送一个带有FIN 标志位的报文通知对方自己已经没有数据要发送了。对方收到FIN报文后会发送一个ACK报文作为确认等待条件满足了然后自己也会发送一个FIN报文最终完成连接的关闭。4.PSHPush作用表示接收方应尽快将数据推送给上层应用。当PSH标志位被设置为1时接收方会立即将数据传递给上层应用而不是等待缓冲区填满后再传递。应用场景通常用于实时性要求较高的数据传输例如在HTTP协议中当服务器向客户端发送响应数据时可能会设置PSH 标志位以便客户端尽快处理这些数据。对端上层应用层很忙接收缓冲区的所剩空间越来越少直至为0那么发送端就只能不发了但是不发总不能就傻傻的等着吧还有如果接收端读走了部分缓冲区的数据那么发送端又怎么知道我们后面会说到一旦数据读走了窗口更新了这个接收端 / 服务端就会主动向客户端发送报文还有一种方式时客户端会定期向服务端发送一个不携带数据的报文一发数据就需要ACK那么客户端也就清楚现在服务端的接收缓冲区的状态了PSH就是叫接收方应尽快将数据推送给上层应用是一种催促的机制注意我们举得例子比较极端准确来说当PSH标志位被设置为1时接收方会立即将数据传递给上层应用而不是等待缓冲区填满后再传递。什么是叫对端操作系统赶快读缓冲区没有数据的时候读取会阻塞其实有数据的时候也可能会阻塞就是我们后面会谈到的低水位线和高水位线的问题举例就是需要满50个字节才会触发read的条件我们后面提到IO事件就绪的概念的时候详谈所以PSH的作用就是让对端的read/... 条件就绪5.RSTReset作用用于重置连接。当RST标志位被设置为1时表示当前连接出现了问题需要强制断开连接。应用场景如果接收方收到一个错误的报文例如报文中的序号或确认号不正确或者接收方没有建立对应的TCP连接它会发送一个带有RST 标志位的报文通知对方终止连接。例如当客户端尝试连接一个不存在的端口时服务器会发送一个RST报文拒绝连接。我们知道TCP建立连接的三次握手的第三次握手是没有应答的也就是前两次握手尽管丢包了也不怕因为有应答能知道。其实客户端在最后一次握手的时候将第三次握手的ACK发送出去的时候就已经认为客户端三次握手成功了。但是如果在第三次握手的时候丢包了的话客户端是不知道的双方只是站在自己的立场三次握手是看自己有没有发送和收到的发 - 收 - 发收 - 发 - 收双方三次握手完成是有时间差的如果第三次的ACK丢了的话客户端认为建立成功但是服务端认为连接失败了导致连接是否成功的判定不一致。那么客户端接下来就极有可能向服务器发送数据可是服务器说不是三次握手还没有好吗于是服务器就会向客户端发送一个RST 置为 1 的报文表示当前连接出现了问题需要强制断开连接。上面这个例子也是比较极端的其实在通信的过程中连接出现任何问题都可以进行重置6.URGUrgent作用表示报文中有紧急数据。当URG标志位被设置为1时表示TCP报文中的数据是紧急数据接收方应优先处理这些数据。应用场景紧急数据通常用于控制信息例如在Telnet协议中用户可以通过发送紧急数据来中断当前的操作。当URG标志位被设置时TCP会使用紧急指针Urgent Pointer 来指示当前报文的有效载荷中紧急数据的起始位置。接收缓冲区可以看成是字节流式的接收队列还有因为序号按序到达的机制那么我们如果有数据想要优先读取并处理的话直接是行不通的在计算机网络当中什么情况是需要被优先处理的呢有一种情形就是上传资料到百度网盘大小大概是一个 G 的文件就会向服务端发送大量的报文对端缓冲区也是收到了该文件的部分报文上层会进行备份保存在指定文件当中。但是上传到一半的时候发现传错了要取消上传。尽管该文件是一个报文来完成也是需要死板地到对端接收缓冲区等待前面的报文处理了才到该报文。现在就是表示取消上传的报文想要优先被处理因为前面部分报文是属于同一文件的上层还需要将前面的报文处理了但是处理这些报文已经是没有意义的了。所以对端接收缓冲区一旦接收到了含有紧急数据的报文就直接优先处理了。就像我们的取消还有暂停等等URG是比较不常用的URG标志位通常要与TCP报文协议当中的16 位紧急指针相配合。如果URG标志位被置为0表示该 16 位紧急指针是无效的为1就是有效了。下面我们来看看 16 位紧急指针的源码__be16 urg_ptr; // 16位紧急指针紧急数据并不属于正常数据它是带外数据URG只是代表紧急指针有效还是无效本质还是要看16 位紧急指针紧急指针用来指示当前报文的有效载荷中紧急数据的起始位置本质就是一个偏移量紧急数据只有一个字节正因为只有一个字节所以它往往可以用来设置状态码暂停、取消、继续上传不就可以用0、1、2来表示嘛用不同的状态码来表示不同的控制命令标志位的组合TCP标志位可以组合使用以实现不同的功能。例如SYNACKSYN 和 ACK 标志位同时置 1表示接受连接请求并确认。FINACKFIN 和 ACK 标志位同时置 1表示本方数据发送完毕并确认对方的 FIN 报文。TCP的 6 个标志位是TCP协议的核心控制机制通过这些标志位TCP能够实现连接的建立、数据传输、连接终止以及异常处理等功能。这些标志位在TCP三次握手和四次挥手过程中起着关键作用保证了TCP协议的可靠性与高效性。至此TCP包头协议的主要字段我们就讲完了剩下的字段我们后续用到时再详细说明更多精彩请看下文

最新新闻

日新闻

周新闻

月新闻