SpringBoot整合TCP服务器长连接(BIO方式)
前言: 自学java以来,这是第一次写博客,是为了记录自己在工作中积累的经验和成果,希望可以坚持下来。看到的也就当个参考,自己技术也不是很好,还有很多需要学习的地方,有不对的地方,希望大牛们多多指点。
刚从事开发不久,接到一个任务,是将原来项目中的 mqtt 连接改为 tcp 长连接,所以需要在 SpringBoot 中整合 tcp 服务器,并实现和其他设备的长连接,与设备之间进行交互。在查阅很多网上的资料后,发现有一个Netty的框架可以做到高效稳定的连接,但是由于任务完成有时限,而我也没有学习过 Netty,NIO 编程在自学 java 时也没有深入了解,所以就先使用了最原始的 BIO 来编写了服务端。在写代码的同时,也在网上找了韩顺平老师的 Netty 课程自学,最后也是将 BIO 这种方式转成了 Netty。后面有空也会将 SpringBoot 整合 Netty 记录下来。
BIO 基本介绍
BIO(Blocking I/O):同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理。
Java BIO 工作机制:
TCP 服务器启动类 —— SocketRunner
该类实现了 CommandLineRunner 接口,这个接口字面意思是命令行启动,会随着 SpringBoot 启动,重写该类的 run 方法即可。启动后循环监听是否有新的连接接入,若有新的连接则创建一个线程对这个连接进行处理。
提示:如果要处理并发量较大的 TCP 连接,推荐各位还是使用 NIO 或者 netty。
/**
* TCP服务器启动类
* @author Leaves Ye
* @date Created on 14:40 2020/12/15
* @since JDK 1.8
*/
@Component
public class SocketRunner implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(SocketRunner.class);
@Resource
//tcp配置类,里面存放了监听的端口号
private SocketConfig socketConfig;
@Resource
private ThreadPoolTaskExecutor socketTaskExecutor;
@Override
public void run(String... args) throws Exception {
try (ServerSocket serverSocket = new ServerSocket(socketConfig.getPort())) {
logger.debug("tcp服务器已启动,正在监听端口-->{}", socketConfig.getPort());
while (true) {
//获取连接socket对象
Socket socket = serverSocket.accept();
logger.debug("tcp获取连接,连接ip-->{}", socket.getInetAddress().getHostAddress());
//将socket存放到SocketSend类中
if (SocketSend.addSocket(socket)) {
//启动线程并传入socket对象,对连接到的socket进行处理
SocketRunnable socketRunnable = SpringUtil.getBean(SocketRunnable.class);
socketRunnable.setSocket(socket);
socketTaskExecutor.execute(socketRunnable);
}
}
} catch (IOException e) {
logger.error("tcp服务器异常,异常信息-->{}", e.getMessage());
}
}
}
TCP 消息发送类 —— SocketSend
TCP 通讯是属于全双工的通讯,也就是说在接收的同时也是可以进行发送的,所以我将获取到的 Socket 对象用一个 Map 集合进行存储,Key 是 Socket 的 ip 地址,Value 是 Socket 对象,并且限制每个 ip 只能建立唯一的一个连接。
该对象中封装了添加和移除 Socket 对象的方法,并且封装向对应 ip 连接发送消息的方法。
由于是私有协议,所以我将数据封装成了两个对象:TcpPackage 和 TcpMessage。
TcpPackage:私有协议传输的最小单位,通过包头信息可将多个包组合成 TcpMessage 对象,获得传输真正需要的数据。
TcpMessage:私有协议所要传输的真实数据对象,由多个 TcpPackage 合并成。
由于发送的最小单位是 TcpPackage,如果有多个线程调用同一个 Socket 对象进行数据传输,由于 TCP 传输时流的方式,同时传输则会造成数据混杂,所以当进行 TcpPackage 传输的时候,我加上了同步锁,锁对象则是当前传输的 Socket 对象,防止数据混乱。
/**
* TCP消息发送类
*
* @author Leaves Ye
* @date Created on 11:17 2020/12/18
* @since JDK 1.8
*/
@Component<