之前学习tcp协议,都是通过一些理论、图例,看不到摸不着,感觉很抽象、很遥远。现在使用java socket来实现tcp通信,并通过RawCap结合wireshark,来实操一次,用看得见的方式理解tcp协议。tcp只是协议,是概念。java socket是对tcp协议的实现。可以理解为接口和实现类直接的关系。代码中使用到了log4j来打印日志,如何使用自行百度。

服务器端实现如下:

package com.cfysu.socket;

import org.apache.log4j.Logger;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by cj on 17-6-25.
 */
public class SocketServer {

    private static final Logger LOGGER = Logger.getLogger(SocketClient.class);

    public static void main(String[] args){
        try {
            new SocketServer().startServer();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void startServer() throws IOException {
        final ServerSocket serverSocket = new ServerSocket(8888);
        LOGGER.info(Thread.currentThread().getName() + ":服务器端已启动,正在监听...");
        ExecutorService pool = Executors.newFixedThreadPool(2);
        while (true){
            //一直监听
            //阻塞等待新connection
            Socket socket = serverSocket.accept();
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
            //final PrintStream printStream = new PrintStream(socket.getOutputStream());

            //启动新线程处理客户端请求
            pool.submit(new Worker(reader, writer));
        }
    }

    private class Worker implements Runnable{

        private PrintWriter writer;
        private BufferedReader reader;

        public Worker(BufferedReader reader, PrintWriter writer){
            this.writer = writer;
            this.reader = reader;
        }

        @Override
        public void run() {
            LOGGER.info("启动新线程处理客户端请求,threadId:" + Thread.currentThread().getName());
            while (true){
                //for(int i = 0;i < 5;i++){

                String clientMsg = null;
                try {
                    clientMsg = reader.readLine();
                } catch (IOException e) {
                    LOGGER.error("客户端退出", e);
                }
                if(null == clientMsg){
                    LOGGER.info(Thread.currentThread().getName() + ":服务器端收消息线程退出");
                    return;
                }
                LOGGER.info(Thread.currentThread().getName() + ":服务器端接受到了消息===>>>" + clientMsg);
                //}
                //响应客户端
                writer.write("a msg from server:" + clientMsg);
                //如果不加换行符,则readline()一直阻塞
                writer.write(System.getProperty("line.separator"));
                //writer.println("a msg from server:" + clientMsg);
                writer.flush();

                LOGGER.info(Thread.currentThread().getName() + ":已回复客户端消息");
            }
        }
    }
}




客户端实现如下:

package com.cfysu.socket;

import org.apache.log4j.Logger;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * Created by cj on 17-6-25.
 */
public class SocketClient {

    private static final Logger LOGGER = Logger.getLogger(SocketClient.class);

    public static void main(String[] args){
        try {
            new SocketClient().startClient();
        } catch (IOException e) {
            LOGGER.error("IO异常", e);
        }
    }

    public void startClient() throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);
        PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        //启动线程接收消息
        new Thread(new FutureTask<String>(new Receiver(reader))).start();
        //主线程,用户发送消息
        Scanner scanner = new Scanner(System.in);
        while (true){
            String userMsg = scanner.next();
            if("bye".equals(userMsg)){
                LOGGER.info(Thread.currentThread().getName() + ":bye bye");
                break;
            }
            writer.println(userMsg);
            writer.flush();
            LOGGER.info(Thread.currentThread().getName() + ":用户信息已发出");
        }
        LOGGER.info(Thread.currentThread().getName() + ":主线程退出");
    }

    private class Receiver implements Callable<String>{

        private BufferedReader reader;

        public Receiver(BufferedReader reader){
            this.reader = reader;
        }

        @Override
        public String call() {
            int msgCount = 0;
            while (true){
                //Thread.sleep(10000);
                //msgCount++;
                //printWriter.println("---thread msg---" + msgCount);
                //printWriter.flush();
                LOGGER.info(Thread.currentThread().getName() + ":等待服务器回复消息");
                String receiveMsg = null;
                try {
                    receiveMsg = reader.readLine();
                } catch (IOException e) {
                    LOGGER.error("服务器端退出", e);
                }
                if(null == receiveMsg){
                    LOGGER.info(Thread.currentThread().getName() + ":客户端收消息线程退出");
                    return "exit";
                }
                LOGGER.info(Thread.currentThread().getName() + ":收到新消息===>>>" + receiveMsg);
            }
        }
    }
}


稍微解释下上面的代码。客户端使用主线程读取用户输入,并发送给服务器端。另外起了一个线程用于读取服务器端返回数据。服务器开启一个容量为2的线程池,接受到客户端请求后启动线程响应客户端请求,这里只是简单把客户端数据返回。

在启动socket之前,先打开RawCap抓取数据包,如图。

Java tcp 协议 完整代码 拆包 粘包 java解析tcp协议_tcp

启动ServerSocket、ClientSocket并使用客户端发送文字给服务端。结束后使用WireShark打开抓包生成的数据文件。可以看到前三个数据包为tcp建立连接的三次握手。

Java tcp 协议 完整代码 拆包 粘包 java解析tcp协议_java_02

结合下图,可以清晰的分析出三次握手的过程。

Java tcp 协议 完整代码 拆包 粘包 java解析tcp协议_tcp_03

图片引用自

接着看第四个和第五个包,可以看到客户端发送的消息和服务器端返回的消息。我当时在客户端发送字符串“client”,服务器端返回“a msg from server:client”从图中可以看出。

Java tcp 协议 完整代码 拆包 粘包 java解析tcp协议_socket_04

Java tcp 协议 完整代码 拆包 粘包 java解析tcp协议_wireshark_05

其中psh表示有数据传输。

RawCap下载链接:

http://www.netresec.com/?page=RawCap

WireShark下载链接:

https://www.wireshark.org/