TCP 协议TCP 全称为 传输控制协议(Transmission Control Protocol). 人如其名, 要对数据的传 输进行一个详细的控制;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 字节头部选项: 暂时忽略TCP 首部格式详解TCP 首部长度最小为20 字节没有选项时最大为60 字节选项占 40 字节。下图是每个字段的位置text0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -------------------------------- | 源端口 | 目的端口 | -------------------------------- | 序号 (Sequence Number) | -------------------------------- | 确认序号 (Acknowledgment Number) | -------------------------------- | 首部长度 | 保留 |U|A|P|R|S|F| 窗口大小 | -------------------------------- | 校验和 | 紧急指针 | -------------------------------- | 选项 (可选) | 填充 | -------------------------------- | 数据 | --------------------------------1. 源端口16 位 目的端口16 位作用标识发送方和接收方的应用进程通过端口号找到对应的 socket。细节端口号加上 IP 首部中的源 IP 和目的 IP才能唯一确定一个 TCP 连接四元组。常用范围0~1023 为系统保留知名端口1024~49151 为注册端口49152~65535 为动态/私有端口。2. 序号32 位Sequence Number作用该 TCP 报文段中第一个数据字节的编号。初始序号ISN建立连接时双方随机生成避免历史报文干扰而非固定从 1 开始。范围0 ~ 2^32 - 1到达最大值后回绕到 0TCP 通过时间戳或扩展选项处理回绕问题。为什么重要保证数据有序、去重、检测丢失。3. 确认序号32 位Acknowledgment Number作用期望收到的下一个字节的序号同时隐含确认了该序号之前的所有字节都已正确收到。有效条件仅当ACK 标志位 1时确认序号字段才有效。累计确认Ack N 表示序号 N-1 及之前的数据都已收到。示例收到 Seq1001 且长度500 的数据后回复 Ack1501表示期待第 1501 字节。4. 首部长度4 位作用表示 TCP 首部有多少个32 位字4 字节。取值范围5 ~ 15因为最小 20 字节即 5×420最大 60 字节即 15×460。为什么需要因为选项字段长度可变接收方需要知道首部在哪里结束、数据从哪里开始。5. 保留6 位作用保留为未来使用当前必须置 0。6. 标志位每个 1 位共 6 位标志名称为 1 时的含义URGUrgent紧急指针字段有效报文中有紧急数据应优先处理ACKAcknowledgment确认序号字段有效除初始 SYN 报文外几乎所有报文都置 1PSHPush提示接收端立即将数据交给应用层不要等缓冲区满RSTReset连接出现严重异常需要强制关闭并重新建立连接拒绝非法请求SYNSynchronize建立连接时使用SYN1 表示这是一个连接请求或连接接受报文FINFinish关闭连接时使用发送方不再发送数据补充细节PSH 的实际行为发送方设置 PSH 后接收方 TCP 不会等待缓冲区填满而是立即把数据递交给应用进程但现代 TCP 实现通常自动优化很少显式依赖。RST 常见场景尝试连接一个未监听的端口、连接超时、收到不属于现存连接的报文。SYNACK服务器回复连接请求时SYN1, ACK1。7. 窗口大小16 位作用告诉对方从确认序号开始自己还能接收多少字节的数据即接收窗口大小。范围0 ~ 65535 字节。若需要使用更大窗口可以通过TCP 窗口缩放选项Window Scale将窗口值左移若干位最大可达 1GB。用途实现流量控制防止发送方发送过快导致接收方缓冲区溢出。动态变化接收方根据自身可用缓冲区大小随时调整窗口值并通知发送方。8. 校验和16 位作用检测 TCP 首部和数据在传输过程中是否出现比特错误。计算范围TCP 伪首部12 字节包含源 IP、目的 IP、协议号、TCP 长度 TCP 首部 TCP 数据。伪首部并不真正传输只在计算校验和时临时构造。计算方式将上述所有内容按 16 位字累加进位回卷最后取反码。接收方同样计算若结果不为全 1即 0xFFFF则丢弃报文。为什么包含伪首部为了验证报文是否确实发送给了正确的 IP 和协议防止 IP 欺骗或路由错误。9. 紧急指针16 位作用仅在URG1时有效指向紧急数据的最后一个字节的序号偏移量相对于当前序号字段的值。用途发送紧急数据如中断命令时接收方可以立即读取而不被流控阻塞。实际使用现代应用很少依赖因为带外数据out-of-band data在其他机制下可能更复杂。10. 选项长度可变最多 40 字节常见选项MSS (Maximum Segment Size)告诉对方自己能接收的最大报文段长度不包括 TCP 首部。窗口缩放因子用于扩展窗口大小Windows Scale。时间戳用于计算 RTT 和防止序号回绕PAWS。SACK (Selective Acknowledgment)允许接收方告知哪些数据块丢失提高重传效率。填充确保首部长度是 4 字节的整数倍用 0 填充。总结各字段的功能分组功能相关字段标识进程源端口、目的端口可靠传输有序/确认序号、确认序号、ACK、SYN、FIN流量控制窗口大小错误检测校验和紧急数据URG、紧急指针连接控制SYN、FIN、RST提示接收方PSH扩展功能选项、保留、首部长度确认应答(ACK)机制主机A发送方主机B接收方数据1~1000表示该TCP报文段携带的数据字节序号是从 1 到 1000。确认应答下一个是1001主机B收到数据后回复的确认报文含义是 “我已收到 1~1000 的所有数据下一个期望收到的字节序号是 1001”。数据1001~2000主机A收到确认后继续发送下一个数据段字节序号从 1001 到 2000。确认应答下一个是2001主机B再次回复表示成功收到 1001~2000期待序号 2001。1序列号与累计确认TCP 把数据流看作一个字节流每个字节都有一个唯一的序列号Seq。主机A发送的第一个数据段“1~1000”实际上是指该数据段起始字节序号为 1长度为 1000 字节。主机B回复的确认号Ack是1001这是 TCP 的累计确认机制告诉主机A “我已经收到了序号 1000 及之前的所有字节请从 1001 开始发送”。2确认应答保证可靠主机A每发一个数据段都需要收到来自主机B的确认。如果一段时间内没有收到确认超时主机A会重传未确认的数据。图中每个数据段都得到了确认因此主机A可以正常发送下一段。3发送与接收的同步主机A不会一次性把所有数据全部发出去而是等待对方确认后再发下一段这是最简单的停-等协议的体现。在实际 TCP 中为了提高效率会使用滑动窗口允许连续发送多个数据段再等待确认但图里展示的是基础逻辑发送 → 确认 → 再发送。TCP 将每个字节的数据都进行了编号. 即为序列号.每一个 ACK 都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下 一次你从哪里开始发超时重传机制• 主机 A 发送数据给 B 之后, 可能因为网络拥堵等原因, 数据无法到达主机 B;• 如果主机 A 在一个特定时间间隔内没有收到 B 发来的确认应答, 就会进行重发但是, 主机 A 未收到 B 发来的确认应答, 也可能是因为 ACK 丢失了;因此主机 B 会收到很多重复数据. 那么 TCP 协议需要能够识别出那些包是重复的包, 并 且把重复的丢弃掉. 这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果.那么, 如何超时的时间如何确定• 最理想的情况下, 找到一个最小的时间, 保证 确认应答一定能在这个时间内返 回.• 但是这个时间的长短, 随着网络环境的不同, 是有差异的.• 如果超时时间设的太长, 会影响整体的重传效率;• 如果超时时间设的太短, 有可能会频繁发送重复的包TCP 为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超 时时间• Linux 中(BSD Unix 和 Windows 也是如此), 超时以 500ms 为一个单位进行控 制, 每次判定超时重发的超时时间都是 500ms 的整数倍.• 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.• 如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.• 累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接连接管理机制在正常情况下, TCP 要经过三次握手建立连接, 四次挥手断开连接三次握手建立连接当前状态事件 / 动作下一状态说明CLOSED服务器执行listen()LISTEN服务器被动监听CLOSED客户端执行connect()SYN_SENT客户端发送 SYNLISTEN收到客户端的 SYNSYN_RCVD服务器回复 SYNACKSYN_SENT收到服务器的 SYNACKESTABLISHED客户端回复 ACKSYN_RCVD收到客户端的 ACKESTABLISHED连接建立完成四次挥手关闭连接关键原则主动调用close()的一方为主动关闭方另一方为被动关闭方。下面分别列出两种场景。场景 A客户端主动关闭服务器被动关闭角色当前状态事件 / 动作下一状态说明客户端主动ESTABLISHED调用close()发送 FINFIN_WAIT_1主动发起关闭服务器被动ESTABLISHED收到 FIN回复 ACKCLOSE_WAIT应用层会收到 EOF客户端FIN_WAIT_1收到 ACK对 FIN 的确认FIN_WAIT_2等待服务器发送 FIN服务器CLOSE_WAIT应用层处理完数据后调用close()发送 FINLAST_ACK主动发送 FIN客户端FIN_WAIT_2收到服务器的 FIN回复 ACKTIME_WAIT进入 2MSL 等待服务器LAST_ACK收到客户端对 FIN 的 ACKCLOSED彻底关闭客户端TIME_WAIT等待 2MSL 后CLOSED防止残留报文干扰场景 B服务器主动关闭客户端被动关闭角色当前状态事件 / 动作下一状态说明服务器主动ESTABLISHED调用close()发送 FINFIN_WAIT_1主动发起关闭客户端被动ESTABLISHED收到 FIN回复 ACKCLOSE_WAIT应用层会收到 EOF服务器FIN_WAIT_1收到 ACK对 FIN 的确认FIN_WAIT_2等待客户端发送 FIN客户端CLOSE_WAIT应用层处理完数据后调用close()发送 FINLAST_ACK主动发送 FIN服务器FIN_WAIT_2收到客户端的 FIN回复 ACKTIME_WAIT进入 2MSL 等待客户端LAST_ACK收到服务器对 FIN 的 ACKCLOSED彻底关闭服务器TIME_WAIT等待 2MSL 后CLOSED防止残留报文干扰注意TIME_WAIT只会出现在主动关闭方。CLOSE_WAIT和LAST_ACK出现在被动关闭方。常见状态解释状态含义出现位置LISTEN服务器监听等待客户端连接服务器SYN_SENT客户端已发送 SYN等待 SYNACK客户端SYN_RCVD服务器收到 SYN已回复 SYNACK等待 ACK服务器ESTABLISHED连接已建立可以传输数据双方FIN_WAIT_1主动关闭方已发送 FIN等待 ACK主动方FIN_WAIT_2主动关闭方已收到 FIN 的 ACK等待对方 FIN主动方CLOSE_WAIT被动关闭方收到 FIN等待应用层关闭被动方LAST_ACK被动关闭方已发送 FIN等待最终 ACK被动方TIME_WAIT主动关闭方收到 FIN 并回复 ACK等待 2MSL主动方CLOSED连接完全关闭双方补充TIME_WAIT为什么要等 2MSL保证主动关闭方发送的最后一个 ACK 能到达对方若丢失对方重发 FIN自己还能响应。让本次连接的所有残留报文在网络中消失避免干扰新连接。CLOSE_WAIT容易出问题如果被动关闭方的应用层没有及时调用close()连接会一直卡在CLOSE_WAIT导致文件描述符泄漏。详细解释一、前期准备服务器端先启动1. 服务器端应用层listenfd socket()创建一个监听 socket返回文件描述符listenfd。bind(listenfd, 服务器地址端口)将listenfd绑定到服务器的 IP 和端口例如0.0.0.0:8080。listen(listenfd, 连接队列长度)将listenfd变为被动监听状态内核为该 socket 维护一个已完成连接队列backlog。2. 服务器端 TCP 层状态执行listen()后服务器 TCP 状态从CLOSED→LISTEN等待客户端连接。此时服务器阻塞在accept()调用上等待客户端连接到来。二、TCP 三次握手建立连接1. 客户端应用层fd socket()创建主动 socket。connect(fd, 服务器地址端口)发起连接请求阻塞等待服务器应答。2. 客户端 TCP 层状态调用connect()后客户端状态CLOSED→SYN_SENT发送 SYN 报文。3. 服务器端 TCP 层收到 SYN 后状态LISTEN→SYN_RCVD并回复 SYNACK。客户端收到 SYNACK 后状态SYN_SENT→ESTABLISHED并回复 ACK。服务器收到 ACK 后状态SYN_RCVD→ESTABLISHED。4. 应用层返回客户端connect()返回表示连接建立成功。服务器端阻塞的accept()返回生成一个新的已连接 socketconnfd用于与该客户端通信。注accept()返回后服务器 TCP 层已经处于ESTABLISHED状态。三、数据传输可循环多次服务器端应用层read(connfd, buf, size)阻塞等待客户端请求数据。收到数据后read返回处理请求write(connfd, buf, size)向客户端发送应答数据。继续循环read→ 处理 →write形成多次请求-应答图中所示“循环多次”。客户端应用层图中未画客户端的数据发送但通常客户端也会write发送请求read接收应答。TCP 层状态整个数据传输期间双方 TCP 状态保持在ESTABLISHED。每次write产生数据报文收到ACK确认每次read获取对方发来的数据。图中客户端 TCP 层状态一栏写着DATA和ACK这并非正式状态而是表示数据发送和确认阶段。四、TCP 四次挥手关闭连接以服务器端主动关闭为例服务器调用close(connfd)。1. 服务器端应用层调用close(connfd)TCP 层发送 FIN 报文主动关闭。2. 服务器端 TCP 状态转移ESTABLISHED→FIN_WAIT_1发送 FIN 后。收到客户端对 FIN 的 ACK 后FIN_WAIT_1→FIN_WAIT_2。收到客户端的 FIN 后发送 ACKFIN_WAIT_2→TIME_WAIT等待 2MSL 后关闭。TIME_WAIT→CLOSED。3. 客户端 TCP 状态转移被动关闭收到 FIN 后发送 ACKESTABLISHED→CLOSE_WAIT图中未显示但标准是CLOSE_WAIT。客户端应用层调用close()后发送 FIN状态CLOSE_WAIT→LAST_ACK。收到服务器对 FIN 的 ACK 后LAST_ACK→CLOSED。图中客户端 TCP 层状态只画到了FIN_WAIT_2实际上对客户端来说被动关闭时没有FIN_WAIT_1/2只有CLOSE_WAIT和LAST_ACK。图中可能简化了或者画的是客户端主动关闭的情况。建议按标准理解。五、“循环多次”的含义服务器端在accept()得到connfd后在一个循环中反复read/write处理同一条连接上的多次请求例如 HTTP 持久连接。外层循环accept()本身可以循环接受多个客户端的连接每个连接分配一个connfd分别处理。在服务器端标注了两个“循环多次”内层对同一个connfd反复读请求 → 发应答。外层不断accept()新连接。六、关键总结系统调用作用对应 TCP 状态变化socket()创建套接字不改变状态bind()绑定地址端口无状态变化listen()变为监听套接字CLOSED→LISTENaccept()接受连接返回已连接套接字状态不变connect()主动连接CLOSED→SYN_SENT→ESTABLISHEDread()/write()读写数据保持ESTABLISHEDclose()关闭连接触发四次挥手状态变化如上TCP 状态转换的汇总• 较粗的虚线表示服务端的状态变化情况;• 较粗的实线表示客户端的状态变化情况;• CLOSED 是一个假想的起始点, 不是真实状态;