TCP/IP 协议 是一组用于互联网通信的协议。它由两个主要协议组成:传输控制协议(TCP)和互联网协议(IP)。TCP/IP协议是互联网上最常用的协议之一,它使得不同类型的计算机和网络设备能够相互通信。
在计算机网络(谢希仁编著)这本书中,详细介绍了TCP/IP网络传输涉及的分层,及各个比特的含义。在这篇文章中,我们将使用Wireshark 抓包分析 TCP/IP 协议,用实践来验证理论。
OSI 是 OSI/RM(Open Systems Interconnecation Reference Model)开放互联基本参考模型的简称,由 ISO 于 1983 年制定,形成正式文件。
OSI 分层是一种网络架构模型,它将网络通信分为七个不同的层次。每个层次都有自己的功能和任务。下面是由上到下每个层次的简要介绍:
序号 | 层级 | 名称 |
---|---|---|
1 | 应用层 | 负责提供各种应用程序,例如电子邮件、文件传输和远程登录等。 |
2 | 表示层 | 负责将数据转换为应用程序可以理解的格式,它定义了如何在不同的系统之间交换数据。 |
3 | 会话层 | 负责建立、管理和终止会话,它定义了如何在两个节点之间建立连接。 |
4 | 传输层 | 负责将数据分割成数据包,并确保它们在网络上的传输。它还提供了可靠的端到端传输服务。 |
5 | 网络层 | 负责将数据包从源地址路由到目标地址,它定义了如何在不同的网络之间传输数据包。 |
6 | 数据链路层 | 负责将数据包从一个节点传输到另一个节点,它定义了如何在物理介质上传输数据包。 |
7 | 物理层 | 负责将数据从一个节点传输到另一个节点,它定义了电气、光学和机械接口的规范。 |
虽然标准已经制定了,但是由于一些缺陷,现今规模最大的、覆盖全世界的因特网并未使用 OSI 标准,而是使用 TCP/IP 标准。
相比OSI 7层模型,TCP/IP协议只有4层,它将部分层进行了合并。
通常讲的TCP/IP协议是指TCP/IP协议簇,按照分层可以分为以下部分
层级 | 协议名称 |
---|---|
应用层 | HTTP、FTP、SMTP、POP3、IMAP、Telnet、SSH、DNS |
传输层 | TCP、UDP |
网络层 | IP、ICMP、ARP、RARP |
网络接口层 | Ethernet、Token Ring、FDDI、PPP |
下面抓包使用的 WireShark 用的就是TCP/IP标准。
使用 Java 编写服务端和客户端代码, 通过调用 Socket API 实现两者通讯。
Socket 即套接字,是应用层 与 TCP/IP
协议族通信的中间软件抽象层,表现为一个封装了 TCP / IP协议簇 的编程接口(API),ServerSocket 是使用 TCP 协议,如果想使用 UDP 协议,可以使用 DatagramSocket。
WireShark 需要管理员权限启动,默认不能查看不走网卡的 loopback,需要下个 npcap。
Server.java
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(10007);
System.out.println("服务器已启动,等待客户端连接...");
Socket socket = server.accept();
System.out.println("客户端已连接!");
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str = br.readLine();
System.out.println("客户端说:" + str);
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.println("欢迎您!");
pw.flush();
socket.close();
}
}
Client.java
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
Socket socket = new Socket("localhost", 10007);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.println(str);
pw.flush();
BufferedReader br2 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String str2 = br2.readLine();
System.out.println("服务器说:" + str2);
socket.close();
}
}
TCP 首部如下所示:
源端口和目的端口,各占2个字节
序号,占4个字节
确认号,占4个字节
数据偏移,占4位,半个字节
保留,占6位
flag 控制位: URG/ACK/PSH/RST/SYN/FIN,占6位
窗口,占2个字节
检验和,占2个字节
紧急指针,占2个字节
选项,字节数不固定,下面是12字节
tcp 建立连接的最先三个包就是握手的三个包,下面会从理论结合实际抓包分析三次握手具体流程
客户端发送给服务端
客户端在建立TCP连接时,会启用一个随机的序列号,并将其值存入相互交换的第一个报文段的TCP头部的序列号字段,后续TCP报文段的序列号字段值将依次递増
在以下抓包过程中,可以发现 0x4cf2bfaa 就是第一个包的随机序列号,而且 wireshark 已经标注相对序号Seq为0,就是第一个包.
而且TCP 客户端在发送时需要将 SYN 设置为1,此时客户端进入SYN-SENT 状态
服务端发送给客户端
服务端接到报文后,同意建立连接后向客户端发送确认报文,此报文需要将 SYN 和 ACK 都设置为1,ack 设为发送过来的 seq+1,并且为自己启用一个随机序号,在报文中,可以发现随机序号为 0x890b1b78 ,此时服务器进入到 SYN-RCVD 状态
客户端发送给服务端
客户端收到后还需要进行确认,再次发送报文,这次报文将 ACK 设为1,序号设为之前自身序号+1=0x4cf2bfab, 将 ack 设为服务器发送过来的序号+1=0x890b1b79,当发送过去后,进入ESTABLISHED状态,服务器接收到后也会进入ESTABLISHED状态
以上过程可以总结如下,在上述例子中 x 实际为 0x4cf2bfaa,y 实际为 0x890b1b78
从书上226页可以得到的图表如下:
建立连接的服务端和客户端都可以释放连接,双方需要进行四次挥手,上面 java 代码经抓包发现是服务端先释放连接
服务端发送FIN报文
发送FIN=1, ACK=1, seq=u(此次抓包u实际为0x91dd5af1)。
书上记载 u是前面已发送过的数据的最后一个字节的序号加1, 这个不太明白,还有实际ACK也被置为1,是否必须?后续再研究
客户端发送ACK=1,seq=v(此次抓包实际seq为0x6a067550),ack为u+1(此次抓包实际为0x91dd5af2)
v是前面已发送过的数据的最后一个字节的序号加1。
客户端接着发送FIN=1,ACK=1,seq=w(此次抓包实际为0x6a067550),ack=u+1(此次抓包实际为0x91dd5af2)
此处的w实际抓包发现w和v一样
服务端发送ACK=1,seq=u+1(此次抓包实际为0x91dd5af2),ack=w+1(此次抓包实际为0x6a067551 )
上述过程可以总结如下,由于客户端和服务器都可以发送FIN包释放连接,下面图表由客户端释放连接