前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TCP协议三次握手和四次挥手抓包分析

TCP协议三次握手和四次挥手抓包分析

作者头像
用户4415180
发布2022-06-23 14:28:55
4750
发布2022-06-23 14:28:55
举报
文章被收录于专栏:高并发高并发

 TCP协议在双方建立连接的时候需要三次握手,首先客户端发送SYN标志为1的TCP数据包,然后服务器端收到之后,也会发送一个SYN标志置位,并且带有ack应答的数据包,最后客户端再发送给服务端一个应答,这样就建立起了通信。

 首先看TCP数据包头部各个字段:

在三次握手和四次挥手过程中,主要看UAPRSF6个标志和seq ack的变化。首先使用telnet程序测试,比如telnet 8.8.8.8 443,使用tcpdump -i enp0s3  -X -vv tcp -s0 命令看通信过程

 192.168.0.10.53548 > 14.215.177.39.https 表示从192.168.0.10的53548端口发送到了14.215.177.39的https(443)端口,Flags [S]表示发送的SYN,也就是连接标志,seq 1436271743表示序号, win 29200表示窗口大小,options [mss 1460,sackOK,TS val 16451924 ecr 0,nop,wscale 7]是在正式建立连接前告诉对方自己的一些选项字段,比如mss表示自己的最大报文长度是1460,length:0表示数据字段部分为0,就是没有数据。

以太网帧完整数据:

0x0000:  4510 003c cd2b 4000 4006 eccf c0a8 000a  E..<.+@.@....... 0x0010:  0ed7 b127 d12c 01bb 559b c47f 0000 0000  ...'.,..U....... 0x0020:  a002 7210 80df 0000 0204 05b4 0402 080a  ..r............. 0x0030:  00fb 0954 0000 0000 0103 0307            ...T........ 主要看TCP头部相关的内容和TCP头部图对比下,第二行的 0x559b c47f表示seq,0x0000 0000表示ack,第三行的0xa002表示TCP头部长度是10 * 4 = 40字节,并且SYN为1表示发起连接请求。

下面看服务器端的回复

 14.215.177.39.https > 192.168.0.10.53548: Flags [S.], cksum 0x418e (correct), seq 2784679373, ack 1436271744, win 8192, options [mss 1440,sackOK,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,wscale 5], length 0  表示14.215.177.39的443端口发送给192.168.0.10的53548端口的SYN连接,seq是2784679373, ack就是客户端发去的seq +1,其中服务端的mss是1440

以太网帧如下:

0x0000:  4500 003c cd2b 4000 3806 f4df 0ed7 b127  E..<.+@.8......' 0x0010:  c0a8 000a 01bb d12c a5fa d5cd 559b c480  .......,....U... 0x0020:  a012 2000 418e 0000 0204 05a0 0402 0101  ....A........... 0x0030:  0101 0101 0101 0101 0103 0305            ............ 其中标志为变为了0x12,ACK和SYN都为1,表示发送的是带有ack回应的SYN连接。

看最后一次握手,客户端发送给服务器端的确认

192.168.0.10.53548 > 14.215.177.39.https: Flags [.], cksum 0x80cb (incorrect -> 0xc571), seq 1, ack 1, win 229, length 0,发现没了选项字段,说明在默认情况下,选项字段是在三次握手中前两次握手时确定了双方的各种属性。

以太网帧如下:

0x0000:  4510 0028 cd2c 4000 4006 ece2 c0a8 000a  E..(.,@.@....... 0x0010:  0ed7 b127 d12c 01bb 559b c480 a5fa d5ce  ...'.,..U....... 0x0020:  5010 00e5 80cb 0000                      P.......

seq0x559b c480也就是服务端发来的ack字段,ack为服务端的seq + 1,标志变成了0x10只置位了ACK字段,说明这就是一个简单的确认报文。

三次握手过程如下图:

图中的UAPRSF表示TCP头部的标志位字段, 客户端会有两种状态:SYS_SENT, ESTABLISHED,我们使用netstat -npt命令可以看到这种状态的转换,比如telnet一个不存在的IP端口号,会看telnet一直处于SYS_SENT,直到进程退出,如果连接成功会变成ESTABLISHED, 服务端有三种状态LISTEN, SYS_RCVD, ESTABLISHED, LISTNE表示正在监听某一个端口,在接受到客户端发送的SYN时会变为SYS_RCVD,然后再发送一个SYN报文给客户端,客户端回复确认后,会从SYS_RCVD变为ESTABLISHED.也就是服务端和客户端已经连接成功。

再看四次挥手,四次挥手如果使用telnet 百度网站的话如果输入quit,是由服务端发起关闭连接的,如果直接关掉telnet程序的话,客户端会一直发送FIN报文,但是服务端不会响应了,所以这次自己写个socket服务端程序和客户端程序,先看具体报文

发现只有三段报文,书上说四次挥手,为啥不一样呢,怎么变成了三次,TCP/TP协议详解四次挥手过程是1.客户端发送FIN报文2.服务端响应发送ack 3.服务端发送FIN报文 4.客户端发送响应ack报文。而抓包结果是第二和第三也就是服务端发送的ack和FIN合并成了一个报文。先具体分下下各个报文

192.168.0.10.55728 > 192.168.0.10.8887: Flags [F.], cksum 0x818b (incorrect -> 0x07a3), seq 1, ack 1, win 342, options [nop,nop,TS val 17498958 ecr 17497745], length 0   客户端的55728发送给服务端的8887端口,FIN标志为1

以太网帧如下:

    0x0000:  4500 0034 7214 4000 4006 474b c0a8 000a  E..4r.@.@.GK....     0x0010:  c0a8 000a d9b0 22b7 eb37 8c84 f7c4 7c80  ......"..7....|.     0x0020:  8011 0156 818b 0000 0101 080a 010b 034e  ...V...........N     0x0030:  010a fe91                                .... 标志位为 0x11,ACK 和FIN为1,所以客户端发送的是关闭连接请求。

 192.168.0.10.8887 > 192.168.0.10.55728: Flags [F.], cksum 0x818b (incorrect -> 0x02e5), seq 1, ack 2, win 342, options [nop,nop,TS val 17498958 ecr 17498958], length 0   服务端回复的也是FIN报文

以太网帧如下:

    0x0000:  4500 0034 8211 4000 4006 374e c0a8 000a  E..4..@.@.7N....     0x0010:  c0a8 000a 22b7 d9b0 f7c4 7c80 eb37 8c85  ....".....|..7..     0x0020:  8011 0156 818b 0000 0101 080a 010b 034e  ...V...........N     0x0030:  010b 034e                                ...N 标志位为0X11, ACK和FIN为1,如果按照书本上说的应该是0X10的,只发送ACK,不发送FIN,但是这里两个合并了。

192.168.0.10.55728 > 192.168.0.10.8887: Flags [.], cksum 0x818b (incorrect -> 0x02e5), seq 2, ack 2, win 342, options [nop,nop,TS val 17498958 ecr 17498958], length 0   服务端发送ack应答,断开连接完毕。

所以这里有个疑问点,为啥断开连接只交互了三次,而不是四次,因为我写的程序里客户端关闭连接后,服务端立马关闭连接,没有了任何数据传输,而且都是调用的close,所以服务端将第二次第三次合并成了一个报文,这样做就是捎带ACK,减少一次通信。服务端代码如下

代码语言:javascript
复制
package per.pzt.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	
	private static final Integer PORT = 8888;
	private Socket socket;
	private ServerSocket serverSocket;
	public Server(int port) {
		try {
			serverSocket = new ServerSocket(port);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) throws IOException {
		Server server = new Server(PORT);
		BufferedReader br = null;
		PrintWriter pw=null;
		while((server.socket = server.serverSocket.accept()) != null) {
			br = new BufferedReader(new InputStreamReader(server.socket.getInputStream()));
			pw = new PrintWriter(new OutputStreamWriter(server.socket.getOutputStream()));
			String msg = null;
			while((msg = br.readLine()) != null){
                System.out.println("client:" + msg);
                if(msg.equals("exit")){
                    break;
                }
                pw.println(msg);
                pw.flush();
            }
			//server.closeSocket();
			server.socket.shutdownInput();
			pw.println("goodbye");
            pw.flush();
			server.socket.shutdownOutput();;
		}
		server.serverSocket.close();
	}

}

如果客户端和服务端采用半连接方式通信,也就是客户端关闭时先调用socket.shutdownOutput,对应的系统调用api是shutdown(fd,1)关闭输出通道,服务端关闭时先调用shutdownInput对应的系统调用api是shutdown(fd,0),然后服务端发送最后的数据goodbye给客户端,然后服务端调用shutdownOutput彻底关闭双向通道,客户端接收后调用shutdownInput也关闭输入通道。此时的报文为

五段报文,1.客户端发送FIN。2.服务端回应FIN,并且携带数据goodbye。3.服务端发送FIN。4.客户端回应goodbye。5.客户端回应服务端FIN。看回应哪个报文就看seq和ack的对应值就可以了。所以除了回应goodbye的报文,确实是四次挥手。1,2,3,5构成了四次挥手。

接着改造把服务端和客户端都改为socket.close调用,也就是不用半关闭,直接全部关闭双向通道。但是在服务端close之前Thread.sleep(1)。服务端休眠1ms,得到的报文如下

这就是标准的4次挥手,可以得处一个结论,如果在客户端关闭后,服务端无任何操作也是直接关闭,则服务端会将四次挥手第二三次挥手合并为1个报文,也就是FIN+ACK,捎带ACK机制,如果是半连接状态,则会使用四次挥手,或者如果服务端没有立即关闭通道,而是做了其它的操作,再关闭,则也是四次挥手。看了LWIP的TCP/IP协议栈实现源码,确实是这样操作的,客户端有一种情况直接从FIN_WAIT_1到TIME_WAIT状态,服务端也有一种是从CLOSE_WAIT到LAST_ACK状态是在一个通信报文段实现的,也就是正常情况下CLOSE_WAIT到LAST_ACK需要发送两段报文,但是在三次挥手的情况下,只发送一段报文就可以了。所以在客户端关闭后,服务端无任何操作立即关闭的情况下,只需要三次挥手即可,减少一次通信。

看TCP断开连接图:

正常情况四次挥手。

双方都立即断开的情况下只需要三次挥手(捎带ACK),其中服务端CLOSE_WAIT 到LAST_ACK 我们使用netstat -npt看不到(除非debug调试),我看LWIP协议栈对这段处理,CLOSE_WAIT只作为了一个中间状态,在发送完FIN+ACK的报文后,直接变为了LAST_ACK。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-09-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档