在分布式系统或与硬件设备交互的场景中,我们常常需要通过 TCP 协议进行请求-响应式的双向通信。由于 TCP 是面向连接的流式协议,不具备像 HTTP 那样的天然请求/响应模型,因此我们需要手动设计一种机制来实现“发送指令并等待响应”的功能。
本文将基于 Spring Boot 框架,介绍如何构建一个支持多线程、异步接收、同步等待响应的 TCP 通信队列服务,并使用 CompletableFuture
和 ConcurrentHashMap
来管理请求与响应之间的匹配关系。
🎯 功能目标
我们希望实现以下功能:
- 支持长连接下的 TCP 客户端;
- 发送请求后能同步等待响应;
- 接收到响应后自动触发对应的 Future;
- 支持超时控制和异常处理;
- 可扩展为通用组件,适配多种业务消息格式(如 JSON、自定义协议等);
🧱 核心组件设计
1. 请求-响应映射结构
为了实现“发送一条请求并等待唯一响应”的逻辑,我们引入了一个线程安全的 ConcurrentHashMap<String, CompletableFuture<String>>
,用于存储请求标识符(Key)与响应 Future 的映射。
private final ConcurrentHashMap<String, CompletableFuture<String>> futureMap = new ConcurrentHashMap<>();
2. 同步发送方法
该方法负责发送请求,并阻塞等待响应结果(可设置超时时间):
public String send(String message, String key) {
...
}
3. 异步接收方法
使用 @Async
注解开启异步线程持续监听输入流,接收到数据后解析出 Key 并触发对应的 Future:
@Async
public void receive() {
...
}
📦 示例代码:GenericTcpClient.java
package com.example.tcp.client;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.io.*;
import java.net.Socket;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
@Service
@Slf4j
public class GenericTcpClient {
private static final long RESPONSE_TIMEOUT = 5000; // 响应超时时间(毫秒)
private final ConcurrentHashMap<String, CompletableFuture<String>> futureMap = new ConcurrentHashMap<>();
private volatile Socket socket;
public void connect(String host, int port) throws IOException {
if (socket != null && !socket.isClosed()) {
socket.close();
}
socket = new Socket(host, port);
log.info("TCP 连接已建立到 {}:{}", host, port);
}
/**
* 发送消息并等待响应
*/
public String send(String message, String key) {
if (!isConnected()) {
throw new RuntimeException("TCP 未连接");
}
CompletableFuture<String> future = new CompletableFuture<>();
futureMap.put(key, future);
try {
sendMessage(message, key);
} catch (IOException e) {
futureMap.remove(key);
throw new RuntimeException("消息发送失败", e);
}
try {
return future.get(RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
futureMap.remove(key);
throw new RuntimeException("响应超时");
} catch (InterruptedException | ExecutionException e) {
futureMap.remove(key);
throw new RuntimeException("执行异常", e);
}
}
private synchronized void sendMessage(String message, String key) throws IOException {
if (!isConnected()) {
throw new IOException("Socket 已关闭");
}
try {
OutputStream out = socket.getOutputStream();
out.write((key + ":" + message + "\n").getBytes());
out.flush();
log.debug("发送消息[key:{}]: {}", key, message);
} catch (IOException e) {
closeSocket();
throw e;
}
}
@Async
public void receive() {
byte[] buffer = new byte[1024];
while (isConnected()) {
try {
InputStream in = socket.getInputStream();
int bytesRead = in.read(buffer);
if (bytesRead == -1) {
log.warn("服务器断开连接");
closeSocket();
break;
}
String response = new String(buffer, 0, bytesRead).trim();
log.info("收到响应: {}", response);
String[] parts = response.split(":", 2);
if (parts.length >= 2) {
String key = parts[0];
String body = parts[1];
CompletableFuture<String> future = futureMap.remove(key);
if (future != null) {
future.complete(body);
} else {
log.warn("找不到对应的请求 key: {}", key);
}
}
} catch (IOException e) {
log.error("接收数据失败", e);
closeSocket();
break;
}
}
log.warn("接收线程退出");
}
private boolean isConnected() {
return socket != null && !socket.isClosed() && socket.isConnected();
}
private synchronized void closeSocket() {
try {
if (socket != null) {
socket.close();
log.info("Socket 已关闭");
}
} catch (IOException e) {
log.error("关闭 Socket 异常", e);
} finally {
socket = null;
}
}
}
📌 使用方式示例
@Autowired
private GenericTcpClient tcpClient;
// 建立连接
tcpClient.connect("127.0.0.1", 9090);
// 启动异步接收线程
tcpClient.receive();
// 发送请求并等待响应
String response = tcpClient.send("Hello Server!", "req_001");
System.out.println("收到响应:" + response);
✅ 特性总结
功能 | 描述 |
---|---|
同步请求-响应 | 使用 CompletableFuture 实现同步等待响应 |
异步接收线程 | 使用 @Async 持续监听响应 |
线程安全 | 使用 ConcurrentHashMap 管理请求-响应映射 |
超时控制 | 设置最大等待时间,避免线程挂起 |
异常处理完善 | 处理 IO、超时、中断等常见异常 |
🔧 扩展建议
你可以根据实际需求进一步扩展此模块:
- ✅ 添加心跳机制,保持连接活跃;
- ✅ 实现自动重连逻辑;
- ✅ 支持 SSL/TLS 加密连接;
- ✅ 使用 Netty 替代原生 Socket 提升性能;
- ✅ 支持泛型返回值(如
CompletableFuture<T>
); - ✅ 添加日志拦截器、监控统计等功能;
📚 结语
通过本文,我们实现了一个轻量级但功能完善的 TCP 请求-响应通信队列服务,适用于与外部系统、硬件设备、IoT 设备等进行稳定、高效的通信。结合 Spring Boot 的依赖注入和异步任务能力,使得整个通信过程简洁、易维护、可扩展。
如果你正在开发类似的 TCP 客户端通信模块,欢迎参考本方案。如果你有更复杂的通信需求,也可以留言交流,我们可以一起探讨更高级的设计模式和优化方向。