使用java socket理解tcp协议

来源:互联网 发布:mac maven .m2文件夹 编辑:程序博客网 时间:2024/06/01 19:20

之前学习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抓取数据包,如图。

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

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


图片引用自https://www.cnblogs.com/TankXiao/archive/2012/10/10/2711777.html

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



其中psh表示有数据传输。

RawCap下载链接:

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

WireShark下载链接:

https://www.wireshark.org/

原创粉丝点击