传输控制协议(TCP)是一种面向连接的协议,网络程序使用这个协议的时候,网络可以保证客户端和服务端的连接是可靠的,安全的。
如果 A机向 B机发送“hello”,在物理网线上传输的数据不仅仅是“hello”这5个字符,其实除了hello(被称为有效载荷)之外,还必须有用于TCP/IP协议进行处理所需要的其他很多内容。这其中与socket 编程相关的最主要有4 个东西:A机 IP地址(源IP)、B的 IP地址(目标IP)、A机端口号(源端口)、B机端口号(目标端口)。目标IP用来使数据包能到达指定的目标机,源IP使得目标机能获知源机的IP地址,从而能回送数据包。目标端口用于表示目标机上应当接收和处理数据包内容的程序是谁(目标机上不同的网络应用程序占用不同的端口号,例如:http服务器占用端口80,ftp服务器占21,POP3服务器占110,SMTP服务器占25,TELNET服务器占23)。源端口用于标识源机上发送数据的应用程序是谁,这使得目标机能够将回送数据送给该应用程序,而不是别的应用程序。
源机会在将数据包发送到网线上之前,使用上述4个内容对有效载荷进行封包操作,目标机会在收到数据包之后,进行解包操作,从而得到有效载荷。封包的过程简要描述如下:在有效载荷之前加上TCP包头(最重要的就是目标端口和源端口),然后再在前面加上IP包头(最重要的就是目标IP和源IP)。解包过程就是封包过程的逆操作。
TCP数据被封装在一个IP数据报中。
如第一个图所示:每个TCP数据段都包含源端口和目标端口的端口号(在TCP头部),用于寻找发端和收端应用进程。这两个值加上IP头部中的源IP地址和目标IP地址唯一确定一个TCP连接。有时,一个IP地址和一个端口号也称为一个插口(socket),插口对(包含源IP地址,源端口号、目标机IP地址和目标端口号的四元组)可唯一确定互联网络中每个TCP连接的双方。
第二个图:序列号用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的第一个数据字节。确认号指定的是下一个期待的字节,而不是已经正确接收到的最后一个字节。
TCP连接建立过程——三次握手
- 第一次握手:客户端发送位码为 SYN = 1(SYN 标志位置位),随机产生初始序列号 Seq = J 的数据包到服务器。服务器由 SYN = 1(置位)知道,客户端要求建立联机。
- 第二次握手:服务器收到请求后要确认联机信息,向客户端发送确认号Ack = (客户端的Seq +1,J+1),SYN = 1,ACK = 1(SYN,ACK 标志位置位),随机产生的序列号 Seq = K 的数据包。
- 第三次握手:客户端收到后检查 Ack 是否正确,即第一次发送的 Seq +1(J+1),以及位码ACK是否为1。若正确,客户端会再发送 Ack = (服务器端的Seq+1,K+1),ACK = 1,以及序号Seq为服务器确认号J 的确认包。服务器收到后确认之前发送的 Seq(K+1) 值与 ACK= 1 (ACK置位)则连接建立成功。
(上面Seq表示序列号,Ack表示确认号,SYN和ACK以及FIN等都是标志位。ACK 被设置为 1表示确认号字段是有效的,如果 ACK为 0,则该段不包含确认信息。SYN 被用于建立连接过程,在连接请求中,SYN = 1 和 ACK = 0 表示该段没有捎带确认字段。连接应答会捎带一个确认,所以应答时会有 SYN= 1 和 ACK= 1。另外发送ACK无需任何代价,所以我们会看到一旦一个连接建立起来,ACK标志总是被置为1)
文章图片
从上图可以看出,当客户端调用connect 时,触发了连接请求,向服务器发送了 SYN J包,这时 connect 进入阻塞状态;服务器监听到连接请求,即收到 SYN J包,调用 accept函数接收请求向客户端发送 SYN K,ACK J+1,这时 accept 进入阻塞状态,客户端收到服务器的 SYN K,ACK J+1之后,这时 connect 返回,并对 SYN K 进行确认,服务器收到 ACK K+1时,accept返回,至此三次握手完毕,连接建立。可以得知:客户端的 connect在三次握手的第二次返回,而服务器端的 accept在三次握手的第三次返回。
【TCP/IP|【TCP/IP】TCP的三次握手和四次挥手】为什么是三次握手:
有讲到“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。
这样说明“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”
下面用抓包工具 Wireshark 实际分析下三次握手的过程,这里是登陆一个网址,然后Wireshark 过滤http 找到与浏览器打开该网站相关的数据包。
连接的是 HTTP 服务器,可以看到其端口号正是80,客户端的端口号则是随机的。红色框框标记的则是三次握手过程,下面具体来看
文章图片
第一次握手数据包:
客户端发送一个TCP,标志位SYN置位,序列号为4225926656(默认是相对序列号,可以在Wireshark中protocol preference 设置Absolute Number,显示真正的序列号)。表示客户端请求建立连接。
第二次握手数据包:
看源端口和目的端口。服务器发回确认包,标志位SYN和ACK置位,将确认序号Ack设置为客户的ISN(初始序号)+1,为4225926657,同时发送序列号为3022381253。
第三次握手数据包
客户端再次发送确认包,其中ACK置位,确认序号Ack设置为服务器发过来的序号Seq+1,为3022381254,并发送序号=服务器发过来的确认号。
对于建连接的三次握手,主要是初始化Seq的值。通信的双方要互相通知对方自己的初始化的Seq(ISN),这个号(上面的K,J)作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输问题而乱序,TCP会用这个序号来拼接数据。
TCP连接终止过程——四次挥手
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭,也就是发送方和接收方都需要Fin和Ack。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来种植这个方向的连接,收到一个FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
这里我们假定客户端主动关闭(实际上谁先执行主动关闭没本质区别,通话结束了,谁先挂断没啥区别)
- 客户端发送一个FIN Seq = M(FIN置位,序号为M)包,用来关闭客户端到服务器端的数据传送。
- 服务器端收到这个FIN,它发回一个ACK,确认序号Ack 为收到的序号M+1。
- 服务器端关闭与客户端的连接,发送一个FIN Seq = N 给客户端。
- 客户端发回ACK 报文确认,确认序号Ack 为收到的序号N+1。
文章图片
对于四次挥手,其实仔细看是两次,因为TCP是全双工的,必须双方都关闭才可以,单方会有两次,共有四次。终止的时候,有一方是被动的,所以看上去就成了四次挥手。
前面有说道,一旦连接建立起来,ACK标志位总是被置为1。所以我们在下图可以看到TCP建立连接之后,ACK总是被置为1的。
关闭连接过程主要看FIN标志位是否置位,ACK在连接建立成功之后都是置为1的。
上面Wireshark抓包是服务器端先执行主动关闭。
第一次挥手:服务器端发起主动关闭,FIN置位,Seq = 3022381791;
第二次挥手:客户端收到FIN后,发回ACK,Ack = Seq + 1 = 3022381792;至此服务器端的连接关闭了,接下来还需要关闭客户端的。
第三次挥手:客户端发送FIN,Seq = 4225929031;
第四次挥手:服务器端收到FIN后,发回ACK,Ack = Seq + 1 = 4225929032.这样客户端的连接也关闭了。至此全双工的TCP连接关闭。
另外通过下面的截图可以发现:数据传输中的 Sequence Number 的增加是和传输字节数相关的。
文章图片
参考资料《UNP》《TCP/IP Vol.1》
推荐阅读
- netty实战|Netty进阶 -- 非阻塞网络编程 实现群聊+私聊+心跳检测系统
- netty实战|Netty入门 -- 什么是Netty()
- Java|记一次TCP TIME_WAIT引发的血案
- 计网|计算机网络——OSI模型以及TCP/IP模型
- 计算机相关知识|HTTP HTTPS
- 考研408|计算机网络——IP
- 计算机网络|计算机网络——TCP和UDP及TCP的三次握手与四次挥手
- Linux|Linux--网络基础
- 工具|Wireshark抓包对其字TCP段理解