之前学习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建立连接的三次握手。
结合下图,可以清晰的分析出三次握手的过程。
图片引用自
接着看第四个和第五个包,可以看到客户端发送的消息和服务器端返回的消息。我当时在客户端发送字符串“client”,服务器端返回“a msg from server:client”从图中可以看出。
其中psh表示有数据传输。
RawCap下载链接:
http://www.netresec.com/?page=RawCap
WireShark下载链接: