


摘要
只懂协议理论不动手等于没学。本文基于 Java TCP
Socket,从零实现一个网络版计算器:约定"运算符+操作数"的请求格式与运算结果的响应格式,完成 Request/Response 的序列化与反序列化,服务端用线程池支持多客户端并发,客户端从控制台循环读取输入。文末附完整可运行代码与运行效果图。

代码示例
约定好协议:
明确传递的数据
-
请求:运算符(±*/);操作数
-
响应:运算结果
组织传输 数据格式
-
自定义文本格式
-
请求:运算符,操作数1,操作数\n
-
响应:结果\n
请求Request:
package application;
//表示一个请求
public class Request {
private String operator;
private double num1;
private double num2;
public Request(String operator, double num1, double num2) {
this.operator = operator;
this.num1 = num1;
this.num2 = num2;
}
//对象-->字符串
public String covertToString(){
//String.format():类似printf,但只是构造到String中,没有打印
return String.format("%s,%f,%f\n",operator,num1,num2);
}
public static Request convertFromString(String request){
//1.获取参数
String[] split = request.split(",");//切分
String opertor = split[0];
double num1 = Double.parseDouble(split[1]);//字符串转成double
double num2 = Double.parseDouble(split[2]);
//2.创建对象
return new Request(opertor,num1,num2);
}
public String getOperator() {
return operator;
}
public double getNum1() {
return num1;
}
public double getNum2() {
return num2;
}
}
响应Response:
package application;
public class Response {
private double result;
Response(double result){
this.result = result;
}
//转换成字符串
public String convertToString(){
return String.format("%f\n",result);
}
//把字符串转换成对象
public static Response convertFromString(String response){
double result = Double.parseDouble(response);
return new Response(result);
}
public double getResult() {
return result;
}
}
服务器:
package application;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpCalcServer {
private ServerSocket serverSocket = null;
public TcpCalcServer(int port)throws IOException{
serverSocket = new ServerSocket(port);
}
public void start() throws IOException{
System.out.println("服务器启动");
ExecutorService service = Executors.newCachedThreadPool();
while (true){
Socket socket = serverSocket.accept();
service.submit(()->{
try {
processConnection(socket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
private void processConnection(Socket socket) throws IOException {
System.out.printf("[%s:%d 客户端上线!]",socket.getInetAddress().toString(),socket.getPort());
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while (true){
//1.读取请求并解析
if (!scanner.hasNext()){
break;
}
String requestString = scanner.next();
//反序列化
Request request = Request.convertFromString(requestString);
//2.根据请求计算响应
Response response = process(request);
//3.把响应写回客户端
String responseString = response.convertToString();
//构造String时已经带了\n,直接print写入即可
printWriter.print(responseString);
printWriter.flush();
}
}catch (IOException e){
e.printStackTrace();
}finally {
System.out.printf("[%s:%d] 客户端下线!\n",socket.getInetAddress().toString(),socket.getPort());
socket.close();
}
}
private Response process(Request request) {
//根据参数和运算符完成计算,构造响应
if(request.getOperator().equals("+")){
return new Response(request.getNum1() + request.getNum2());
}else if(request.getOperator().equals("-")){
return new Response(request.getNum1() - request.getNum2());
}else if(request.getOperator().equals("*")){
return new Response(request.getNum1() * request.getNum2());
}else if(request.getOperator().equals("/")){
return new Response(request.getNum1() / request.getNum2());
}else {
throw new RuntimeException("不支持的运算符:"+request.getOperator());
}
}
public static void main(String[] args) throws IOException {
TcpCalcServer server = new TcpCalcServer(9090);
server.start();
}
}

客户端:
package application;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpCalcClient {
private Socket socket = null;
public TcpCalcClient(String serverIp,int serverPort) throws IOException {
socket = new Socket(serverIp,serverPort);
}
public void start(){
System.out.println("客户端运行中");
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner scannerConsole = new Scanner(System.in);
Scanner scannerNetwork = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while (true){
//1.从控制台读取用户输入内容
System.out.println("请输入要进行的运算(+ - * /)");
String operator = scannerConsole.next();
System.out.println("请输入第一个数字:");
double num1 = scannerConsole.nextDouble();
System.out.println("请输入第二个数字:");
double num2 = scannerConsole.nextDouble();
//2.构造请求并发送
Request request = new Request(operator,num1,num2);
String requsetString = request.covertToString();
printWriter.println(requsetString);
printWriter.flush();
//3.读取服务器响应结果
if(!scannerNetwork.hasNext()){
break;
}
String responseString = scannerNetwork.next();
Response response = Response.convertFromString(responseString);
//4.打印响应结果
System.out.println(response.getResult());
}
}catch (IOException e){
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpCalcClient client = new TcpCalcClient("127.0.0.1",9090);
client.start();
}
}

总结
一个标准的网络程序 = 协议设计 + 序列化/反序列化 + Socket 收发循环。服务端固定 accept → 读请求 → 算响应 → 写回的流程,客户端则是 构造请求 → 发送 → 等响应 → 展示。多客户端并发借助线程池即可实现。这套骨架就是以后写任何 TCP业务服务端的模板。


转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/user340/article/details/166009973




