为了研究tcp连接建立过程,用java编写了一个tcp客户端(client),一个tcp服务端(Server)。程序的逻辑很简单,client连接server,向server发送消息,server收到消息作出响应,如果发送的消息是”close”字符串,双方关闭连接。
用抓包工具wireshark
进行抓包,分析整个通信过程。
server
public class TcpServer { private final int SERVER_PORT = 8001; private ServerSocket servSocket; private Socket socket; public static void main(String[] args) throws Exception { TcpServer server = new TcpServer(); server.acceptRequest(); } /** * 接收客户端请求 * * @throws Exception */ public void acceptRequest() throws Exception { servSocket = new ServerSocket(SERVER_PORT); while (true) { System.out.println("准备接受请求...."); socket = servSocket.accept(); System.out.println("接收来自" + socket.getInetAddress().getHostName() + "的请求"); while (true) { try { InputStream in = socket.getInputStream(); int size; byte[] msg = new byte[1024]; ByteArrayOutputStream bout = new ByteArrayOutputStream(); size = in.read(msg); if (size == -1) { System.out.println("关闭连接:" + socket.getInetAddress()); socket.close(); break; } bout.write(msg, 0, size); String content = bout.toString(); bout.close(); System.out.println("接收到的报文为:"); System.out.println(content); String response = "recived message:" + content; socket.getOutputStream().write(response.getBytes("utf-8")); socket.getOutputStream().flush(); System.out.println("报文已发送,等待接收"); } catch (Exception ex) { ex.printStackTrace(); socket.close(); break; } } } } }
client
public class TcpClient { public static void main(String[] cmd) throws Exception { waiteForUserInput("回车建立连接"); Socket socket = new Socket("localhost", 8001); OutputStream os = socket.getOutputStream(); InputStream is = socket.getInputStream(); while (true) { String content = waiteForUserInput("连接建立,回车发送消息(close关闭)"); os.write(content.getBytes()); os.flush(); ByteArrayOutputStream fos = new ByteArrayOutputStream(); copyStream(is, fos); System.out.println("收到响应:" + fos.toString("UTF-8")); if ("close".equals(content)) { socket.shutdownInput(); socket.shutdownOutput(); socket.close(); break; } } System.out.println("bye bye"); } private static String waiteForUserInput(String tip) { System.out.println(tip); String content = ""; Scanner scanner = new Scanner(System.in); if (scanner.hasNextLine()) { content = scanner.nextLine(); } return content; } /** * 流复制 * * @param from * @param to */ public static void copyStream(InputStream from, OutputStream to) { byte[] buf = new byte[1024]; int size = 0; do { try { size = from.read(buf); if (size > 0) { to.write(buf, 0, size); if (size < 1024) { break; } } else { break; } } catch (IOException e) { e.printStackTrace(); } } while (true); } }
首先启动server端,接受请求,再启动client端,配置好Wireshark后,在client端按回车开始建立连接。
根据程序监听的端口配置好过滤条件,如监听的是8001端口,过滤条件可以设置为tcp port 8001

如果你是在自己本地PC启动client和server,由于程序默认监听地址绑定localhost,localhost的流量都是通过
lo0
网卡进行流转,因此wireshark在配置的时候网卡需要选择lo0
回传网卡。
连接建立
当client连接server端时,也就是client执行代码Socket socket = new Socket("localhost", 8001)
,报文如下:

前面三个是tcp建立连接三次握手报文,第四个是连接建立后client和server重新调整窗口大小,我们主要分析前面三个报文。
- client向server发送一个SYN包请求建立连接,SYN报文就是将报文SYN标志位设置为1

- server向client发送ack包表示确认收到上面发送的syn,并设置SYN位,同意建立连接

- client向server发送ack包表示确认收到,双方连接正式建立

发送数据
我们通过client向server发送一个hello
的消息,该消息未超过MSS(Maximum Segment Size)
即最大报文段长度,因此不会被拆分。
报文5client向server发送数据

在该报文里可以看到三个信息
- ack标志位被设置,表示这是一个确认包
- psh标志位被设置,表示发送方已经发送完所有数据或者已经达到最大缓冲区,期望接收方尽快将数据交付给应用程序(即read返回),一般用于数据传输时设置。
- 可以在报文里看到数据
hello
报文6server响应ack报文,表示已收到数据

报文7server向client发送数据,同样设置ack标志位和psh标志位

报文8client向server发送ack包,确认收到数据

至此,整个数据传输过程结束,简单来说就是这么一个过程
client:server,我给你发了一堆数据
server:client,我确认收到了
server:client,我也给你发送了一堆数据
client:server,我也确认收到了
连接关闭
client输入close,表明会话结束,双方进行四次挥手
关闭连接,结束会话。
报文13client向server发送FIN
包,请求关闭连接

报文14 server向client回复ack报文,表示收到client的报文

报文15 server向client发送FIN
包,请求关闭连接

报文16 client向server回复ack报文,表示收到server的报文

至此连接断开,会话结束,简单概述就是以下过程
client:我要求请求断开连接
server:好的,我知道了。
server:我这边可以断开连接了
client:好的,我知道了,那我们就断开了
至于为什么要四次挥手,因为tcp连接是双工的,也就是两边互相连接,互相发送数据,一方请求断开连接,另外一方的数据可能还未发送完,因此client发送FIN后,server依然可以向client发送数据,但client无法向server发送数据了,server的FIN被client确认后,双方就完全断开连接。这个跟男女朋友一样
男方:分手吧
女方:好的,让我考虑下
女方:我想好了,分手吧
男方:好的,再见
no comment untill now