12.【网络】传输层协议TCP:TCP协议四层模型、协议端格式、TCP 三次握手与四次挥手详解
目录1. TCP协议四层模型2. TCP协议段格式2.1 TCP 首部整体结构2.2 各字段详细解析2.3 关键补充说明3.确认应答(ACK)机制32位序列号确认号可靠性保证重点4. 理解”丢包“丢包Retransmission 数据已发送 未收到 ACK 时间耗尽RTO4.1 丢包4.2 超时重传机制5. 连接管理机制六个标志位的作用5.0 标志位简介5.1 三次握手建立连接SYNACK5.2 四次挥手断开连接ACKFIN5.3 连接失败重新连接RST标志位5.4 连接中的状态变化5.5 连接管理机制5.5.1 理解TIME_WAIT状态服务端主动关闭之后不能立即重启5.5.2 解决TIME_WAIT状态引起的bind失败的方法5.5.3 理解 CLOSE_WAIT 状态6. TCP 紧急模式Urgent Mode详解URG标志位1. TCP协议四层模型TCP 全称为 传输控制协议( Transmission Control Protocol ). 人如其名, 要对数据的传输进行一个详细的控制;2. TCP协议段格式2.1 TCP 首部整体结构TCP 首部默认长度为20 字节如果包含选项字段最大可扩展至60 字节。它的核心作用是为 TCP 提供可靠传输、流量控制、连接管理等功能的控制信息。2.2 各字段详细解析16 位源端口号标识发送端的应用进程告诉接收端数据来自哪个进程。16 位目的端口号标识接收端的应用进程告诉接收端数据要交付给哪个进程。32 位序列号Sequence Number表示本报文段中第一个字节在整个字节流中的编号用于保证数据的有序性和去重。32 位确认号Acknowledgment Number期望接收的下一个字节的序号仅当 ACK 标志位为 1 时有效。它是对对方发送数据的确认。4 位首部长度Data Offset表示 TCP 首部的长度单位是32 位4 字节。取值范围为 5~15对应首部长度 20~60 字节。6 位保留Reserved预留字段目前固定为 0为未来扩展使用。6 位标志位Flags控制 TCP 连接的建立、终止和数据传输行为标志位全称 (Full Name)中文含义核心功能简述URGUrgent紧急指示本报文段包含紧急数据此时紧急指针字段有效。ACKAcknowledgment确认指示确认号字段有效。大多数数据传输报文都设置此位。PSHPush推送指示接收方应立即将数据提交给应用层而不必等待缓冲区填满。RSTReset复位用于强制中断连接重置通常在连接出现错误或拒绝连接时使用。需要重新建立连接。SYNSynchronize同步用于建立连接同步双方的初始序列号ISN。携带该标志的报文称为 “同步报文段”。FINFinish结束用于关闭连接发送方表示自己没有数据要传输了请求关闭连接携带该标志的报文称为 “结束报文段”。助记小贴士连接管理三兄弟SYN(Synchronize)开始握手同步。FIN(Finish)结束对话完成。RST(Reset)强制中断重置。数据传输三助手ACK(Acknowledgment)告诉对方 “收到了”确认。PSH(Push)告诉对方 “别存了快给应用层”推送。URG(Urgent)告诉对方 “这是加急件”紧急。16 位窗口大小Window Size表示接收端当前可用的接收缓冲区大小用于流量控制告诉发送端最多还能发送多少字节的数据。16 位校验和Checksum用于校验 TCP 首部和数据部分的完整性采用伪首部 首部 数据的校验方式接收端校验失败则丢弃报文。16 位紧急指针Urgent Pointer仅当 URG 标志位为 1 时有效指向紧急数据的最后一个字节在报文段中的位置用于标识紧急数据的边界。选项Options可选字段长度可变0~40 字节用于实现额外功能如最大报文段长度MSS、窗口扩大因子、时间戳等。选项字段必须填充为 32 位的整数倍以保证首部长度为 4 字节的整数倍。数据Data应用层交付的有效载荷长度可变若没有数据则为纯首部报文段如 SYN、ACK 报文。2.3 关键补充说明首部长度计算4 位首部长度的最大值为 15因此 TCP 首部最大长度为15 × 4 60 字节其中默认的 20 字节是固定首部剩下的 40 字节留给选项字段。标志位组合使用TCP 的连接建立三次握手使用SYN和ACK组合连接终止四次挥手使用FIN和ACK组合异常断开使用RST。校验和的伪首部TCP 校验和计算时会包含一个伪首部其中包含源 IP、目的 IP、协议号和 TCP 长度用于防止报文被错误交付到其他主机或协议。3.确认应答(ACK)机制32位序列号确认号可靠性保证重点TCP将每个字节的数据都进行了编号即为序列号。可靠性TCP协议具有应答可以保证对历史消息的可靠性通信中最新的报文永远没有应答最新报文的可靠性无法保证。每一个有ACK确认应答信号的报文都带有对应的确认序列号意思是告诉发送者我已经收到了哪些数据下一次你从哪里开始发。不需要对应答做应答。4. 理解”丢包“丢包Retransmission 数据已发送 未收到 ACK 时间耗尽RTO4.1 丢包丢包有以下两种情况丢数据丢应答注意以主机A为客户端来看无法确认是数据丢失还是应答丢失。无法100%保证对方是否收到消息。无法保证可靠性。丢包定义只有收不到应答超时同时发生才会认为是丢包了并且丢包概念是主观概念。并不是100%确定的。主机A发送数据给B之后可能因为网络拥堵等原因数据无法到达主机B如果主机A在一个特定时间间隔内没有收到B发来的确认应答就会进行重发但是主机A未收到B发来的确认应答也可能是因为ACK丢失了因此主机B会收到很多重复数据。那么TCP协议需要能够识别出那些包是重复的包并且把重复的丢弃掉。这时候我们可以利用前面提到的序列号就可以很容易做到去重的效果。序列号的作用确认应答、按序到达、去重4.2 超时重传机制那么如果超时的时间如何确定?最理想的情况下找到一个最小的时间保证 “确认应答一定能在这个时间内返回”。但是这个时间的长短随着网络环境的不同是有差异的。如果超时时间设的太长会影响整体的重传效率如果超时时间设的太短有可能会频繁发送重复的包TCP为了保证无论在任何环境下都能比较高性能的通信因此会动态计算这个最大超时时间。Linux中(BSD Unix和Windows也是如此)超时以500ms为一个单位进行控制每次判定超时重发的超时时间都是500ms的整数倍。如果重发一次之后仍然得不到应答等待 2*500ms 后再进行重传。如果仍然得不到应答等待 4*500ms 进行重传。依次类推以指数形式递增。累计到一定的重传次数TCP认为网络或者对端主机出现异常强制关闭连接。5. 连接管理机制六个标志位的作用5.0 标志位简介1. TCP 中 SYN、ACK、FIN 的英文全称及含义标志位英文全称中文含义主要作用SYNSynchronize同步用于建立 TCP 连接同步双方的初始序列号ACKAcknowledgment确认表示 TCP 报文中的确认号有效用于确认收到的数据FINFinish结束表示发送方的数据已经发送完毕请求关闭发送方向 大白话理解SYN同步相当于说“你好我想和你建立连接并告诉你我的初始序列号”。ACK确认相当于说“收到你的消息了我确认一下”。FIN结束相当于说“我的数据已经发送完了不再发送数据了”。2. 这三个标志在 TCP 中的应用① TCP 三次握手建立连接客户端 服务端 | | | -------- SYN --------- | 请求建立连接 | | | ----- SYN ACK ------ | 同意连接并确认 | | | -------- ACK --------- | 确认连接 | | TCP 连接建立成功② TCP 四次挥手关闭连接主动关闭方 被动关闭方 | | | -------- FIN --------- | 我不发数据了 | | | ------- ACK ---------- | 收到 | | | ------- FIN ---------- | 我也不发数据了 | | | -------- ACK --------- | 收到 | |注以上是典型流程实际 TCP 报文可能同时设置多个标志位例如FIN ACK。面试记忆SYN建立连接、同步初始序列号。ACK确认收到的数据准确说是表示确认号有效。FIN结束本方向的数据发送。注意FIN 只表示一方不再发送数据并不代表双方的 TCP 连接立即完全关闭。5.1 三次握手建立连接SYNACK三次握手期间不能携带数据因为还没有完成连接的建立。此时只能传TCP报头一、TCP 三次握手的完整流程TCP 三次握手是为了在客户端和服务器之间建立一个可靠的全双工连接确保双方都具备收发数据的能力。第一次握手客户端 → 服务器客户端向服务器发送一个SYN报文请求建立连接。此时客户端进入SYN_SENT状态等待服务器的确认。第二次握手服务器 → 客户端服务器收到SYN报文后回复一个SYNACK报文既确认客户端的连接请求也向客户端发起自己的连接请求。此时服务器进入SYN_RCVD状态。第三次握手客户端 → 服务器客户端收到SYNACK报文后发送一个ACK报文确认服务器的连接请求。此时客户端进入ESTABLISHED状态服务器收到ACK报文后也进入ESTABLISHED状态连接正式建立。二、SYN 和 ACK 是三次握手中的核心标志位是的SYN和ACK是三次握手中最重要的两个标志位没有它们就无法完成连接的建立SYNSynchronize用于发起连接请求同步双方的初始序号。ACKAcknowledgment用于确认对方的报文表明已收到并认可。三、三次握手中 SYN 和 ACK 的具体设置握手阶段发送方标志位设置说明第一次握手客户端SYN1ACK0客户端主动发起连接仅需发送同步请求此时还没有需要确认的报文所以 ACK 为 0。第二次握手服务器SYN1ACK1服务器需要同时做两件事用SYN1向客户端发起自己的连接请求用ACK1确认收到了客户端的 SYN 报文。第三次握手客户端SYN0ACK1客户端仅需确认收到了服务器的 SYN 报文所以 ACK1此时连接请求已完成无需再发送 SYN所以 SYN0。四、为什么需要三次握手技术层面的 “必要性”三次握手的核心目的是避免历史连接的干扰确保双方的初始序号都能被对方正确接收和确认从而建立一个可靠的连接。如果只进行两次握手服务器可能会接收并处理已失效的客户端连接请求造成资源浪费。为什么要三次握手补充理解以最短的方式进行验证全双工本质是验证我们两个所处的网络是通常的能够支持全双工以最小成本100%确认双方通信意愿。补面对客户端的连接请求服务器都需要无脑接受三次握手是四次握手使用了捎带应答压缩后的产物。五、注意客户端在接收到服务端发来的第二次握手时就认为三次握手已经完成了服务端要在接收到客户端发送来的第三次握手时才会认为三次握手已经完成5.2 四次挥手断开连接ACKFIN一、TCP 四次挥手的完整流程TCP 四次挥手是为了终止一个全双工连接确保双方都没有数据要发送了且所有数据都已传输完毕。第一次挥手客户端 → 服务器客户端发送一个FIN报文表示自己没有数据要发送了请求关闭连接。此时客户端进入FIN_WAIT_1状态。第二次挥手服务器 → 客户端服务器收到FIN报文后回复一个ACK报文表示 “我知道你要关闭了但我可能还有数据没发完请稍等”。此时服务器进入CLOSE_WAIT状态客户端收到 ACK 后进入FIN_WAIT_2状态。第三次挥手服务器 → 客户端服务器数据发送完毕后向客户端发送一个FIN报文表示 “我的数据也发完了现在可以关闭连接了”。此时服务器进入LAST_ACK状态。第四次挥手客户端 → 服务器客户端收到FIN报文后回复一个ACK报文表示 “收到同意关闭”。此时客户端进入TIME_WAIT状态等待 2MSL 后彻底关闭服务器收到 ACK 后立即关闭连接。二、四次挥手中的核心标志位FINFinish用于释放连接表示发送端没有数据要传输了请求关闭连接。ACKAcknowledgment用于确认对方的报文表明已收到并认可。三、四次挥手中标志位的具体设置挥手阶段发送方标志位设置说明第一次挥手客户端FIN1ACK0客户端主动发起关闭请求没有数据发送也没有需要确认的报文此时 ACK 标志位通常为 0除非是捎带应答。第二次挥手服务器FIN0ACK1服务器仅确认收到了客户端的关闭请求但自己可能还有数据要发所以不发送 FIN只发送 ACK。第三次挥手服务器FIN1ACK1服务器数据发送完毕发起自己的关闭请求同时确认之前的通信状态ACK 标志位为 1 是为了确认序号。第四次挥手客户端FIN0ACK1客户端仅确认收到了服务器的关闭请求连接即将彻底关闭所以不发送 FIN只发送 ACK。四、为什么需要四次挥手因为 TCP 是全双工协议数据传输是双向的。第一次和第二次挥手关闭的是客户端 → 服务器方向的连接第三次和第四次挥手关闭的是服务器 → 客户端方向的连接。服务器收到客户端的 FIN 后可能还需要处理剩余数据不能立即关闭连接所以需要先回复 ACK等数据处理完再回复 FIN这就导致了第二次和第三次挥手的分离。5.3 连接失败重新连接RST标志位只要 TCP 协议栈收到一个报文且该报文不符合当前的状态机逻辑State Machine为了自我保护和清理无效资源就会触发RST。场景关键特征典型报错端口未开SYN 发到了黑洞Connection refused状态冲突一方认为断了一方认为连着Connection reset by peer半打开连接对方挂了又重启我发数据过去Connection reset by peer暴力关闭应用层强制 Kill 或配置了 LINGER(连接直接断开)TCP可靠性是因为有32位序号保证数据按照顺序到达接收缓冲区然后字节流式的接收队列如果我们想要数据被优先处理就可以通过设置URG标志位并设置16位紧急指针偏移地址。紧急数据的大小一般限制只有一个字节。5.4 连接中的状态变化在正常情况下TCP要经过三次握手建立连接四次挥手断开连接三次握手建立连接在代码中connect发起三次握手三次握手过程由双方操作系统自动完成。客户端不accept三次握手也能成功。即accept不参与三次握手。三次握手过程由双方操作系统自动完成。服务端状态转化:[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状态转换的一个汇总较粗的虚线表示服务端的状态变化情况较粗的实线表示客户端的状态变化情况CLOSED是一个假想的起始点不是真实状态5.5 连接管理机制5.5.1 理解TIME_WAIT状态服务端主动关闭之后不能立即重启现在做一个测试,首先启动server,然后启动client,然后用Ctrl-C使server终止,这时马上再运行server, 结果是:这是因为虽然server的应用程序终止了但TCP协议层的连接并没有完全断开因此不能再次监听同样的server端口。我们用netstat命令查看一下:TCP协议规定主动关闭连接的一方要处于TIME_ WAIT状态等待两个MSL(maximum segmentlifetime)的时间后才能回到CLOSED状态。我们使用Ctrl-C终止了server所以server是主动关闭连接的一方在TIME_WAIT期间仍然不能再次监听同样的server端口MSL在RFC1122中规定为两分钟但是各操作系统的实现不同在Centos7/Ubuntu上默认配置的值是60s可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看msl的值规定TIME_WAIT的时间请读者参考UNP 2.7节想一想为什么是TIME_WAIT 的时间是2MSL ?MSL 是TCP 报文的最大生存时间因此TIME_WAIT 持续存在2MSLMax Segment Life, 报文最大生存时间的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启可能会收到来自上一个进程的迟到的数据但是这种数据很可能是错误的)同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失那么服务器会再重发一个FIN。这时虽然客户端的进程不在了但是TCP连接还在仍然可以重发LAST_ACK)查看MSL指令cat /proc/sys/net/ipv4/tcp_fin_timeout5.5.2 解决TIME_WAIT状态引起的bind失败的方法在server 的TCP 连接没有完全断开之前不允许重新监听, 某些情况下可能是不合理的服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很大数量的客户端来请求).这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务器端主动清理掉)就会产生大量TIME_WAIT连接.由于我们的请求量很大, 就可能导致TIME_WAIT的连接数很多, 每个连接都会占用一个通信五元组(源ip, 源端口, 目的ip, 目的端口, 协议)。其中服务器的ip和端口和协议是固定的。如果新来的客户端连接的ip和端口号和TIME_WAIT占用的链接重复了就会出现问题.使用 setsockopt()设置socket 描述符的选项SO_REUSEADDR 为1 , 表示允许创建端口号相同但IP地址不同的多个socket 描述符5.5.3 理解 CLOSE_WAIT 状态#pragmaonce#includefunctional#includetcp_socket.hpptypedefstd::functionvoid(conststd::stringreq,std::string*resp)Handler;classTcpServer{public:TcpServer(conststd::stringip,uint16_tport):ip_(ip),port_(port){}boolStart(Handler handler){// 1. 创建 socket;CHECK_RET(listen_sock_.Socket());// 2. 绑定端口号CHECK_RET(listen_sock_.Bind(ip_,port_));// 3. 进行监听CHECK_RET(listen_sock_.Listen(5));// 4. 进入事件循环for(;;){// 5. 进行 acceptTcpSocket new_sock;std::string ip;uint16_tport0;if(!listen_sock_.Accept(new_sock,ip,port)){continue;}printf([client %s:%d] connect!\n,ip.c_str(),port);// 6. 进行循环读写for(;;){std::string req;// 7. 读取请求. 读取失败则结束循环boolretnew_sock.Recv(req);if(!ret){printf([client %s:%d] disconnect!\n,ip.c_str(),port);// [注意!] 将此处的关闭 socket 去掉// new_sock.Close();break;}// 8. 计算响应std::string resp;handler(req,resp);// 9. 写回响应new_sock.Send(resp);printf([%s:%d] req: %s, resp: %s\n,ip.c_str(),port,req.c_str(),resp.c_str());}}returntrue;}private:TcpSocket listen_sock_;std::string ip_;uint64_tport_;};我们编译运行服务器。启动客户端链接查看 TCP 状态客户端服务器都为 ESTABLELISHED 状态没有问题。然后我们关闭客户端程序, 观察 TCP 状态。tcp000.0.0.0:90900.0.0.0:* LISTEN5038/./dict_server tcp00127.0.0.1:49958127.0.0.1:9090 FIN_WAIT2 - tcp00127.0.0.1:9090127.0.0.1:49958 CLOSE_WAIT5038/./dict_server此时服务器进入了 CLOSE_WAIT 状态结合我们四次挥手的流程图可以认为四次挥手没有正确完成。 小结:对于服务器上出现大量的 CLOSE_WAIT 状态原因就是服务器没有正确的关闭socket导致四次挥手没有正确完成。这是一个 BUG。只需要加上对应的 close 即可解决问题。连接也要被管理先描述再组织struct Link{}6. TCP 紧急模式Urgent Mode详解URG标志位TCP 提供可靠性的基础是32 位序号和字节流机制保证数据按序、无差错地交付给应用层。但在某些场景下如终端 CtrlC 中断我们需要发送比普通数据更紧急的信息这就需要用到URG 标志位和16 位紧急指针。1.核心字段定义URG 标志位当URG1时表示本报文段中包含紧急数据。此时TCP 首部中的16 位紧急指针字段才有效。16 位紧急指针Urgent Pointer这是一个偏移量需要与首部中的32 位序号相加。计算结果序号 紧急指针 - 1紧急数据的最后一个字节的位置。含义它指向紧急数据块的尾部而不是头部。2.紧急数据的格式与大小修正点紧急数据的大小可以是任意长度只要不超过 MSS。格式TCP 报文段中从第一个字节开始到 “紧急指针指向的位置” 之前的所有数据都被视为紧急数据。结构| 紧急数据部分 | 普通数据部分 | | (Urgent) | (Normal) | |--- 指针指向这里 (末尾) ---|注意虽然协议允许发送长紧急数据但在实际应用中如 Telnet/Rlogin通常只发送1 个字节的控制字符如 0x03 代表 CtrlC因为目的只是为了通知对方 “有紧急事件发生”而不是传输大量数据。3.处理机制带外数据 OOB当接收端收到URG1的报文时内核通知内核会立即通知应用程序例如通过信号SIGURG或 I/O 复用事件。读取方式应用程序通常使用MSG_OOB标志读取这部分数据称为带外数据。如果不读取紧急数据它会被留在缓冲区中后续读取普通数据时会按顺序读到它变成普通数据。4.与 PSH 标志位的区别URG紧急告诉对方 “这个数据很重要请优先处理不要等缓冲区满了再通知应用层”。它是逻辑上的优先级。PSH推送告诉对方 “数据发送完了请立即递交给应用层不要缓存”。它是传输上的立即性。总结TCP 的可靠性由序号保证而紧急模式URG则是在可靠字节流之上提供的一种带外通信Out-of-Band能力。URG1标记有紧急数据。紧急指针指向紧急数据的最后一个字节。大小理论上可变实际常用 1 字节控制符。目的用于传输中断指令如 CtrlC或异常通知。…过云雨-CSDN博客主页