本文基于Java代码构建了一个网络通信的模型.模拟客户端和服务器通信的全流程,从连接到数据传输再到断开连接。一个基于TCP协议网络连接的全流程。然后运行这个模型,进行网络数据交换,同时使用WireShark抓取数据报文进行分析,以此论证TCP协议
1.网络通信模型
这里服务端创建绑定的端口为8090,在SocketServer的bind绑定ip+port方法中指定。形成了一个二元组。执行了accept()方法阻塞等待,直到接收到客户端的消息后才会继续执行,将客户端发送的消息从内存缓冲区中读取出来。
客户端只需要创建一个Socket套接字,传入目标主机ip和port端口号,客户端的port由操作系统进行分配,这里调用wirte方传输了一个”hello world”字符串数据到内存缓冲区,然后需要执行flush方法将内存缓冲区的数据刷新到网卡中,即经过了OS的协议栈进行封包后再传输。
package com.lwl.socket;
import java.io.*;
import java.net.*;
import java.util.concurrent.CountDownLatch;
/**
* @author lwl
* @create 2023/5/8 14:20
*/
public class socket_comm {
static CountDownLatch countDownLatch = new CountDownLatch(1); //计时锁,1个长度单位,控制两个线程的时序
public static void main(String[] args) {
//客户端
new Thread(()->{
Socket socket = null;
OutputStream ops = null;
BufferedWriter bufferedWriter = null;
try {
countDownLatch.await(); //需要等服务器启动!
socket = new Socket("172.220.52.238",8090); //往主机服务器连
ops = socket.getOutputStream();
bufferedWriter = new BufferedWriter(new OutputStreamWriter(ops));
bufferedWriter.write("hello world");
bufferedWriter.flush();
}catch (IOException e){
e.printStackTrace();
}catch (InterruptedException e1)
{
e1.printStackTrace();
}
finally {
try {
bufferedWriter.close();
ops.close();
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
//服务端
ServerSocket serverSocket = null;
Socket accept = null;
InputStream inputStream = null;
BufferedReader bufferedReader = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(8090));
countDownLatch.countDown();
accept = serverSocket.accept();
inputStream = accept.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String temp = null;
while((temp = bufferedReader.readLine())!=null){
System.out.println(temp);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
bufferedReader.close();
inputStream.close();
if(accept!=null){ accept.close();}
serverSocket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
2.WireShark抓包
Java使用的Socket套接字对应的协议为TCP协议,所以我们在使用WireShark进行抓包时,只需要知道src源ip和 目的ip 以及协议TCP,即可通过过滤筛选获取到指定的数据报文。如下图,使用WireShark抓取到网络通信的数据报文有9个,代表了一个完整的TCP报文的生命周期。分别为:三次握手建立连接,数据传输阶段,四次挥手断开连接。
接下来,我们开始对这9条报文进行分解吧。
2.1 TCP三次握手建立连接
第一条数据报文为客户端主动发送携带SYN标识符的连接请求给服务器,Seq=0,Len=0即代表这条数据报文的序号为0,长度为0,并没有任何的数据传输。只是作为连接请求的数据报文。
第二条数据报文为服务器响应客户端的连接请求,携带了SYN和ACK标识符,SYN表示服务器也发送了一个建立连接的请求,ACK是对上一条数据客户端发送给服务器数据报文的回复。Seq=0,ACK=1,表示当前报文的序号=0,同时ACK=1,表示客户端发送给服务器的seq序号<1的数据报文我都接受到了,也就是告诉客户端,你的第一条数据报文我接受到了,后面的所有ACK确认字符都同理。一般除了第一条客户端主动向服务器发送的连接请求没有ACK意外,这次TCP连接的所有数据报文都有ACK确认字符的。表示对让一条数据报文的确认接收。
而第三条数据报文为客户端对服务器连接请求的ACK确认,Seq=1,Ack=1,表示当前数据报文的序号为1以及ACK确认你(服务器)向我(客户端)发送的Seq<1的数据报文我都接收到了。
至此,所有SYN表示的连接请求都已发送,且都被ACK确认,客户端和服务器的TCP连接建立成功。图解如下,可抽象为“在吗?” “在啊,你还在吗?”“我也在啊!”,原图来源谢希仁教授的计算机网络一书。
2.2 数据传输阶段
在连接建立完成之后,就可以开始数据传输了。后面的两条数据报文正是数据传输的过程。数据传输阶段的第一条报文的详细信息如下图所示,"hello world"数据信息被顺利抓取。
可以发现这一条报文的除了ACK描述符对服务器报文的确认外,还多了一个PSH描述符,这个是将内存缓冲区中的”hello world”字符串数据经过OS协议栈封包传入网卡,由网卡对该数据报进行发送。它的序号Seq=1,长度Len=11(对应上字符串的长度),这条报文要被服务端完整接受的话,那么下一条服务端回发报文的ACK必须大于12,而下一条报文如下图所示:
这条报文的ACK=12只确认了客户端传过来的序号Seq小于12的数据报,而客户端发送过来Seq=12的数据报文没有接收到。所以理论上,客户端接下来需要重传Seq=12的数据报文。
2.3 TCP四次挥手断开连接
请看最后四条数据报文。
可以发现,客户端发送给服务端的第一条报文的Seq=12的报文,重传漏发的数据报文,验证了上面所述,同时携带了断开连接的描述符FIN,表示客户端主动向服务端发送一个FIN关键字表明断开连接操作。
而第二条数据报文服务端回发给客户端的数据报文中的描述符只有ACK,只对客户端的断开报文进行确认,并没有也发出FIN描述符表明我也可以断开了,这是因为可能此时服务器的内存中还有数据没有flush到网卡中,由网卡进行传输,这里是等待服务端把缓冲区中的数据发送完毕。
第三条报文仍然是服务端回发给客户端的数据报文,这次携带了FIN描述符,表明我数据也发完了,可以断开了。
最后一条报文是客户端对服务端断开请求的ACK确认。至此,TCP的连接就断开了。
整个TCP挥手流程如下图所示,原图同样来源于谢希仁教授的计算机网络,这里进行了抽象笔记表达。
3.后言
该文从网络通信代码,理论,网络中实际传输数据报三个方面进行学习。在整个过程中,实际都是对计算机网络中阐述的理论的辩证,让TCP连接-数据交流-断开的全流程在实际网络数据报的传输中得到充分的,立体的体现,从而突显理论的正确性。
最后我们在互联网中的交流并不是完全保密的,虽然有加密算法的机制,但破解机制依然强大,所以希望谨言,你的不当言语可能也会在某天被有心人抓取,读取到你的"hello world"。