TCP 协议详解 在笔者以前的工程中,用过 socket 套接字实现过多进程通信的程序,也用过 Node.js + socket.io + express 构建过 B/S 软件架构,但对最基础的 TCP 协议其实并没有透彻了解。适逢笔者最近找工作,为防面试时被面试官问到相关问题惨遭打脸,笔者决定总结一下 TCP 协议的相关知识点。
参考网址:
《TCP 协议详解》
《简析TCP的三次握手与四次分手》
《TCP协议中的三次握手和四次挥手(图解)》
《TCP通信的三次握手和四次撒手的详细流程(顿悟) 》
《TCP建立连接的三次握手(例题)》
一、TCP 协议功能简述 在世界上各地,各种各样的电脑运行着各自不同的操作系统为大家服务,这些电脑在表达同一种信息的时候所使用的方法是千差万别。就好像圣经中上帝打乱了各地人的口音,让他们无法合作一样。计算机使用者意识到,计算机只是单兵作战并不会发挥太大的作用。只有把它们联合起来,电脑才会发挥出它最大的潜力。于是人们就想方设法的用电线把电脑连接到了一起。但是简单的连到一起是远远不够的,就好像语言不同的两个人互相见了面,完全不能交流信息。因而他们需要定义一些共通的东西来进行交流,TCP / IP 就是为此而生。
TCP / IP 不是一个协议,而是一个协议族的统称。 里面包括了 IP 协议,IMCP 协议,TCP 协议,以及我们更加熟悉的 http、ftp、pop3 协议等等。电脑有了这些,就好像学会了外语一样,就可以和其他的计算机终端做自由的交流了。
# 二、TCP / IP 协议分层 网络结构有两种分层模式:OSI 参考模型与 TCP / IP 参考模型,两者虽然不同,但却有很多共通之处,如下所示:
TCP / IP 协议按照层次由上到下,层层包装。TCP / IP 模型各层之间的基本作用如下:
应用层 :向用户提供一组常用的应用程序,如电子邮件(简单邮件传输协议,SMTP),文件传输访问(文件传输协议,FTP),远程登录(TELNET)等。 远程登录 TELNET:使用 TELNET 协议,提供在网络其它主机上注册的接口,TELNET 会话提供了基于字符的虚拟终端; 文件传输访问 FTP:使用 FTP 协议来提供网络内机器间的文件拷贝功能; 传输层 :即图中的运输层,负责提供应用程序间的通信。其功能包括: 格式化信息流; 提供可靠传输。为实现后者,传输层协议规定接收端必须发回确认,并且假如分组丢失,必须重新发送。 网络层 :负责相邻计算机之间的通信。功能主要包括三方面: 处理来自传输层的分组发送请求 :收到请求之后,将分组装入 IP 数据报,填充报头,选择去往信宿机的路径,然后将数据报发往适当的网络接口;处理输入数据报 :首先检查其合法性,然后进行寻址:如果该数据包已经到达信宿机,则去掉报头,将剩下一部分交给适当的传输协议;如果该数据包尚未到达信宿机,则转发该数据报;处理路径、流控、拥塞等问题; 网络接口层 :这是 TCP / IP 的最底层,负责接收 IP 数据报并通过网络发送数据报,或者从网络上接收物理帧,抽出 IP 数据报,交给 IP 层;三、TCP 的可靠连接 TCP 用于应用程序之间的可靠通信。
当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求,这个请求必须被送到一个一个确切的地址。 在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (Full-Duplex) 的通信,这个全双工通信将占用两个计算机之间的通信线路,直到它被一方(或双方)关闭为止。
注 :UDP 和 TCP 很相似,当时更简单,同时可靠性低于 TCP。
# 四、TCP 报文格式 TCP 是一个协议,那这个协议是如何定义的,它的数据格式是什么样子的呢?要进行更深层次的剖析,就需要了解,甚至是熟记 TCP 协议中每个字段的含义。
Source Port, Destination Port(源端口号,目的端口号) :分别占用 16 位,用于区别主机中的不同进程;由于 IP 地址用来区分不同主机,所以源端口号、目的端口号与 IP 首部中的源 IP 地址和目的 IP 地址,技能确定唯一的一个 TCP 连接;Sequence Number(发送序号) :32 位数据,用来标识从 TCP 发送端向 TCP 接收端发送的数据字节流,它表示在这个报文段中的第一个数据字节在数据流中的序号 ,主要用来解决网络报乱序问题;Acknowledgment Number(确认序号) :占用 32 位,由接收端的计算机使用,将分段的报文重组成最初形式;如果设置了控制位 ACK = 1,则这个值表示下一个准备接受的包的序列码;Offset(数据偏移量) :占用 4 位,给出首部中 32bit 字的数目,需要这个值是因为任选字段的长度是可变的(如果没有任选字段,正常的长度是 20 字节);Reserved(保留位) :占用 6 位,且必须是 0,为了将来定义新的用途而保留;TCP Flags(TCP 标志位) :用于标志 TCP 的某些状态,它们中的多个可同时被设置为 1,主要用于操控 TCP 的状态机,6 个标志位依次为 URG, ACK, PSH, RST, SYN, FIN。每个标志位的意义如下: URG :紧急标志 (Urgent),该标志表示 TCP 包的紧急指针域有效(后面将会说到紧急指针域的内容),用来保证 TCP 连接不被中断,并督促中间层设备要尽快处理这些数据;ACK :确认标志 (Acknowledge),该标志表示应答域有效,就是说前面提到的 TCP 应答信号会包含在 TCP 数据包中;ACK 可以由两个取值( 0/1 ):应答域有效为1,反之为0;PSH :推标志 (Push),表示 Push 操作,即在数据报到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队;RST :复位标志 (Reset),用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据报;SYN :同步标志 (Synchronize),用来建立连接。该标志经常与 ACK 标志搭配使用: 连接请求时,SYN = 1, ACK = 0; 连接被响应时,SYN = 1, ACK = 1; SYN 的数据报经常被用来进行端口扫描,扫描这发送一个只有 SYN 的数据包,此时若对方主机相应了一个数据包回来,就表明这台主机存在该端口; 这种扫描方式只是进行 TCP 三次握手的第一次握手,因此这种扫描的成功表示被扫描的机器并不安全,一台安全的主机将会强制要求一个连接严格的进行 TCP 三次握手; FIN :结束标志 (Finish),表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的TCP数据包后,连接将被断开。这个标志的数据包也经常被用于进行端口扫描;Window(窗口大小) :用来进行流量控制(问题比较复杂,本博文中并不总结);五、TCP 的三次握手
## 1. 三次握手详解 TCP 是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。 在 TCP / IP 协议中,TCP 协议提供可靠的连接服务,连接是通过三次握手进行初始化的。 三次握手的目的是同步连接双方的序列号和确认号并交换 TCP 窗口大小信息。这就是面试中经常会被问到的 TCP 三次握手。
第一次握手:建立连接。首先客户端发送连接请求报文段,将同步位 SYN 置为 1,发送序号 (Sequence Number) 置为 x;然后客户端进入 SYN_SEND 状态,等待服务器确认; 第二次握手:服务器收到 SYN 报文段。服务器收到了客户端发送的 SYN 报文段,对该 SYN 报文段进行确认,设置确认标志 Acknowlegde Number 为 x + 1(即发送序号 Sequence Number + 1);同时服务器自己还要发送 SYN 请求信息,将 SYN 置为 1,发送序号 Sequence Number 为 y;服务器端将上述所有信息放到一个报文段(即 SYN + ACK 报文段)中,一并发给客户端;此时服务器进入 SYN_RECV 状态; 第三次握手:客户端收到服务器的 SYN + ACK 报文段,然后将确认序号 Acknowledgment Number 设置为 y+1,向服务器发送 ACK 报文段,这个报文段发送完毕后,客户端与服务器端都进入了 ESTABLISHED 状态,此时便完成了 TCP 三次握手; 完成了三次握手,客户端和服务器端就可以开始传送数据。以上就是TCP三次握手的总体介绍。
2. 为什么要进行三次握手? 既然总结了TCP的三次握手,那为什么非要三次呢?怎么觉得两次就可以完成了。那TCP为什么非要进行三次连接呢?在谢希仁的《计算机网络》中是这样说的:
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
同时,书中举了一个例子如下:
“已失效的连接请求报文段”的产生在这样一种情况下:客户端发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达服务器。本来这是一个早已失效的报文段,但服务器收到此失效的连接请求报文段后,就误认为是客户端再次发出的一个新的连接请求。于是就向客户端发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要服务器发出确认,新的连接就建立了。由于现在客户端并没有发出建立连接的请求,因此不会理睬服务器的确认,也不会向服务器发送数据。但服务器却以为新的运输连接已经建立,并一直等待客户端发来数据。这样,服务器的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,客户端不会向服务器的确认发出确认。服务器由于收不到确认,就知道客户端并没有要求建立连接。”
这样讲就很明白了,防止了服务器端的一直等待而浪费资源。
# 六、TCP 的三次握手实例讲解 例1:实例如下: > IP 192.168.1.116.3337 –> 192.168.1.123.7788: S 3626544836:3626544836 > IP 192.168.1.123.7788 –> 192.168.1.116.3337: S 1739326486:1739326486 ack 3626544837 > IP 192.168.1.116.3337 –> 192.168.1.123.7788: ack 1739326487,ack 1 - 第一次握手:192.168.1.116 发送位码 SYN = 1,随机产生发送序列号Sequence Number = 3626544836 的数据包,并发送到 IP 为 192.168.1.123 的地址,192.168.1.123 由 SYN = 1 知道 192.168.1.116 要求建立联机; - 第二次握手:192.168.1.123 收到请求后要确认联机信息,向 192.168.1.116 确认序列号 (Acknowledge Number) ACK = 3626544837, SYN = 1, ACK = 1,随机产生发送序列号seq = 1739326486 的包; - 第三次握手:192.168.1.116 收到后检查第二次握手中收到的确认序列号 (ACK = 3626544837) 是否正确,即是否等于第一次发送的发送序列号 Sequence Number + 1 (3626544836 + 1),以及位码 ACK 是否为 1,若正确,192.168.1.116 会再发送确认序列号 Acknowledge Number = 1739326487, ACK = 1,192.168.1.123 收到后确认 seq = seq + 1, ack = 1,则连接建立成功。 用网络抓包分析工具 WireShark 可以分析上述过程如下面两图所示:
第一次握手的标志位如下图所示:
我们看到标志位中只有一个同步位为 1,也就是说此时在做请求 (SYN);
第二次握手的标志位如下图所示:
我们可以看到,标志位里只有确认位和同步位,也就是正在做应答(SYN + ACK);
第三次握手的标志位如下图所示:
我们可以看到,标志位里只有一个确认位,也就是正在做再次确认 (ACK);
故可以得知:一次完整的三次握手,就是请求 -> 应答 -> 再次确认;
例2,有题如下:
TCP建立连接的过程采用三次握手,已知第三次握手报文的发送序列号为 1000,确认序列号为 2000,请问第二次握手报文的发送序列号和确认序列号分别为
A. 1999,999
B. 1999,1000
C. 999,2000
D. 999,1999
答案应该选 B:发送序列 (Sequence Number)是自己发送报文的序列号,当前发送序列号是上一次发送序列号 +1,确认序列号 (Acknowledge Number) 是从对方接收到的发送序列号 +1 。第三次握手发送的序列号(即 seq number = x+1)是 1000,那说明第一次握手发送的序列号(即seq number = x)是 999。
注意:这里是握手,因此,第二次握手的确认序列号是 1000,即第二次握手的确认序列号是第一次握手时从对方接收到的发送序列号(即上文中推出来的999) +1。第三次握手发送的确认号是 2000,说明第二次握手的发送序列号是 1999。所以选 B。
七、TCP 的四次分手 1. 四次分手详解 在客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定总要断开 TCP 连接。那对于 TCP 的断开连接,这里就有了对应的“四次分手”。
第一次分手:主机 1(可以是客户端,也可以是服务器端),设置发送序列号 (Sequence Number) 和确认序列号 (Acknowledgment Number),向主机 2 发送一个 FIN 报文段;这时候,主机 1 进入 FIN_WAIT_1 状态;这表示主机 1 没有数据要发送给主机 2 了; 第二次分手:主机 2(收到了主机 1 发送的 FIN 报文段,向主机 1 回一个 ACK 报文段,此时确认序列号 (Acknowledge Number) 设置为第一次分手阶段中的发送序列号 (Sequence Number) 的值 + 1;主机 1 进入 FIN_WAIT_2 状态;主机 2 告诉主机 1:“我同意你的关闭请求”; 第三次分手:主机 2 向主机 1 发送 FIN 报文段,请求关闭连接,同时主机 2 进入 LAST_ACK 状态; 第四次分手:主机 1 收到主机 2 发送的 FIN 报文段,然后主机 1 进入 TIME_WAIT 状态;主机 2 收到主机 1 的 ACK 报文段之后,就关闭连接;此时主机 1 等待 2MSL(最大报文段生存时间)后依然没有收到回复,则证明服务器端已经正常关闭,这时候主机 1 也可以关闭连接了。 这次 TCP 的四次分手就这么完成了。
2. 为什么要四次分手? 四次分手是为何呢?如果要正确的理解四次分手的原理,就需要了解四次分手过程中的状态变化。
FIN_WAIT_1 (主动方):该状态需要好好解释一下。其实 FIN_WAIT_1 和 FIN_WAIT_2 状态的真正含义,都是表示等待对方的 FIN 报文,而这两种状态的区别是:FIN_WAIT_1 状态实际上是 socket 在 ESTABLISHED 状态时,它想主动关闭连接,向对方发送了 FIN 报文,此时该 socket 进入到了 FIN_WAIT_1 状态;而当对方回应 ACK 报文后,则进入到 FIN_WAIT_2 状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应 ACK 报文,所以 FIN_WAIT_1 状态一般都比较难见到,而 FIN_WAIT_2 状态还可以时常用 netstat 看到;FIN_WAIT_2 (主动方):上面已经详细解释了这种状态,实际上 FIN_WAIT_2 状态下的 socket 表示半连接,即由一方要求关闭连接,但同时还告诉对方,我暂时还有一点数据需要传送给你 (ACK),稍后再关闭连接;CLOSE_WAIT (被动方):这种状态含义其实是表示等待关闭。当对方关闭一个 socket 后,对方会发送一个 socket 给自己,此时系统必然会回应一个 ACK 报文给对方,此时进入到 CLOSE_WAIT 状态。接下来,实际上你真正需要考虑的事情是查看你是否还有数据发送给对方。如果没有的话,就可以关闭这个 socket,发送 FIN 报文给对方,即关闭了连接。所以在 CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接;LAST_ACK (被动方):被动关闭一方发送 FIN 报文后,最后等待对方的 ACK 报文;当收到 ACK 报文后,也就可以进入到 CLOSED 可用状态了;TIME_WAIT (主动方):表示收到了对方的 FIN 报文,并发送出了 ACK 报文,就等 2MSL 后就可回到 CLOSED 可用状态了。如果 FIN_WAIT_1 状态下,收到了对方同时带 FIN 标志和 ACK 标志的报文时,就可以直接进入到 TIME_WAIT 状态,而无须经过 FIN_WAIT_2 状态;CLOSED :表示连接中断;八、后记 基本上介绍到这里就结束了,但 TCP 协议还是比较复杂的,需要好好理解。
希望这次总结可以让笔者对 TCP 协议更加深入了解,而且能够在以后的某不知何时何地的面试中过关斩将吧~