基于 Spring Boot 实现 TCP 请求-响应通信队列

在分布式系统或与硬件设备交互的场景中,我们常常需要通过 TCP 协议进行请求-响应式的双向通信。由于 TCP 是面向连接的流式协议,不具备像 HTTP 那样的天然请求/响应模型,因此我们需要手动设计一种机制来实现“发送指令并等待响应”的功能。

本文将基于 Spring Boot 框架,介绍如何构建一个支持多线程、异步接收、同步等待响应的 TCP 通信队列服务,并使用 CompletableFutureConcurrentHashMap 来管理请求与响应之间的匹配关系。


🎯 功能目标

我们希望实现以下功能:

  • 支持长连接下的 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 客户端通信模块,欢迎参考本方案。如果你有更复杂的通信需求,也可以留言交流,我们可以一起探讨更高级的设计模式和优化方向。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值