使用Springboot+webSocket 构建TCP 服务
/**
* @author guoshunli
* @version 1.0
* @description: TODO TCP 服务
* @date 2022/9/5 13:50
*/
@Slf4j
@Data
@Component
@NoArgsConstructor
public class SocketServer implements ApplicationRunner {
private Integer port = 9090;
private boolean started;
private ServerSocket serverSocket;
// 网关设备
@Autowired
private DevInstallProfileService devInstallProfileService;
// 防止重复创建socket线程链接对象浪费资源
private ExecutorService executorService = Executors.newCachedThreadPool();
public void start() throws IOException {
start(null);
}
public void start(Integer port) throws IOException {
log.info("port: {}, {}", this.port, port);
try {
serverSocket = new ServerSocket(port == null ? this.port : port);
started = true;
log.info("Socket服务已启动,占用端口: {}", serverSocket.getLocalPort());
UpdateWrapper updateWrapper =new UpdateWrapper();
updateWrapper.set("sys_state", OFFLINE);
devInstallProfileService.update(updateWrapper);
} catch (IOException e) {
log.error("端口冲突,异常信息:{}", e);
System.exit(0);
}
while (true){
try {
// 开启socket监听 //阻塞io
Socket socket = serverSocket.accept();
log.info("=======开启socket监听======");
//注册连接
ClientSocket register = register(socket);
log.info("======={}:注册连接======",register.getKey());
// 在此判断是否重复创建socket对象线程
if (register != null){
executorService.submit(register);
log.info("======={}:创建一个线程池任务======",register.getKey());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void run(ApplicationArguments args) throws Exception {
start();
}
}
package com.tsy.energy.webSocket.tcp;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import static com.tsy.energy.webSocket.tcp.SocketPool.add;
import static com.tsy.energy.webSocket.tcp.SocketPool.remove;
/**
* @author guoshunli
* @version 1.0
* @description: TODO socket任务处理器 SocketHandler,里面封装读写数据,销毁链接,等方法.
* @date 2022/9/5 11:59
*/
@Slf4j
public class SocketHandler {
/**
* 将连接的Socket注册到Socket池中
* @param socket
* @return
*/
public static ClientSocket register(Socket socket){
ClientSocket clientSocket = new ClientSocket();
clientSocket.setSocket(socket);
try {
clientSocket.setInputStream(new DataInputStream(socket.getInputStream()));
clientSocket.setOutputStream(new DataOutputStream(socket.getOutputStream()));
byte[] bytes = new byte[1024];
//getInputStream 一个阻塞io 用来获取注册包 第一次进行未发送注册包进行一次阻塞
int read= clientSocket.getInputStream().read(bytes);
// 防止 第一次注册包什么也没有 然后有断开连接
if (read !=-1) {
// read 数据长度
String key = new String(bytes, 0, read);
clientSocket.setKey(key);
add(clientSocket);
return clientSocket;
}else {
close(clientSocket);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 向指定客户端发送信息
* @param clientSocket
* @param message
*/
public static void sendMessage(ClientSocket clientSocket, String message){
try {
log.info("发送消息到客户端 : >>>>>" + message);
clientSocket.getOutputStream().write(message.getBytes("utf-8"));
//clientSocket.getOutputStream().writeUTF(message);
} catch (IOException e) {
log.error("发送信息异常:{}", e);
close(clientSocket);
}
}
/**
* 获取指定客户端的上传信息
* @param clientSocket
* @return
*/
public static void onMessage(ClientSocket clientSocket){
byte[] keyByte = new byte[1024];
byte[] msgByte = new byte[1];
try {
// 第一次先发送序列号
if(StringUtils.isEmpty(clientSocket.getKey())) {
clientSocket.getInputStream().read(keyByte);
clientSocket.setKey(new String(keyByte, "UTF-8"));
}else {
String info = "";
//接收收到的数据
/* while (true){
if (clientSocket.getInputStream().available() >0) {
clientSocket.getInputStream().read(msgByte);
//十六进制
String tempStr = HexEcodeUtil.ByteArrayToHexStr(msgByte);
info += tempStr;
//已经读完
if (clientSocket.getInputStream().available() == 0) {
//重置,不然每次收到的数据都会累加起来
clientSocket.setMessage(info);
System.out.println("clientSocket.setMessage(info);");
break;
}
}
}
*/
// 十进制数据
int readLine = clientSocket.getInputStream().read(msgByte);
// 当读取长度为 -1 时,则TCP以断开连接 判断客户端断开连接 ClientSocket类会执行一次 isSocketClosed
if (readLine == -1){
return;
}
while (readLine!=-1) {
clientSocket.setMessage(new String(msgByte, "UTF-8"));
break;
}
}
} catch (IOException e) {
e.printStackTrace();
/* close(clientSocket);*/
}
//return null;
}
/**
* 指定Socket资源回收
* @param clientSocket
*/
public static void close(ClientSocket clientSocket){
log.info("进行资源回收");
if (clientSocket != null){
log.info("开始回收socket相关资源,其Key为{}", clientSocket.getKey());
remove(clientSocket.getKey());
Socket socket = clientSocket.getSocket();
try {
socket.shutdownOutput();
socket.shutdownInput();
} catch (IOException e) {
log.error("关闭输入输出流异常,{}", e);
}finally {
try {
socket.close();
} catch (IOException e) {
log.error("关闭socket异常{}", e);
}
}
}
}
/**
* 发送数据包,判断数据连接状态
* @param clientSocket
* @return
*/
public static boolean isSocketClosed(ClientSocket clientSocket){
try {
clientSocket.getSocket().sendUrgentData(1);
log.info("==========》给客户端设备:{} 发送数据包",clientSocket.getKey());
return false;
} catch (IOException e) {
return true;
}
}
}
/**
* @author guoshunli
* @version 1.0
* @description:
* TODO 封装socket对象 ClientSocket 每个链接都是一个ClientSocket对象.
* TODO 我们可以在此类中和客户端进行收到等操作达到监听效果.也可以将收到的数据保存到数据库中,以便后续业务需求
* @date 2022/9/5 11:57
*/
public class SocketPool {
private static final ConcurrentHashMap<String, ClientSocket> ONLINE_SOCKET_MAP = new ConcurrentHashMap<>();
public static void add(ClientSocket clientSocket){
if (clientSocket != null && !clientSocket.getKey().isEmpty()) {
ONLINE_SOCKET_MAP.put(clientSocket.getKey(), clientSocket);
}
}
public static void remove(String key){
if (!StringUtils.isEmpty(key))
ONLINE_SOCKET_MAP.remove(key);
}
}
/**
* @author guoshunli
* @version 1.0
* @description: TODO 处理客户端消息
* @date 2022/9/5 11:57
*/
@Slf4j
@Data
public class ClientSocket implements Runnable {
private Socket socket;
private DataInputStream inputStream;
private DataOutputStream outputStream;
private String key;
private String message;
private int mark =1;
// 网关设备
@Autowired
private DevInstallProfileService devInstallProfileService;
ClientSocket(){
devInstallProfileService = SpringUtil.getBean(DevInstallProfileService.class);
}
@Override
public void run() {
while (true){
try {
onMessage(this);
if (mark == 1){
UpdateWrapper updateWrapper =new UpdateWrapper();
updateWrapper.eq("topic",this.key);
updateWrapper.set("sys_state", ONLINE);
Boolean isUpdate = devInstallProfileService.update(updateWrapper);
if (isUpdate){
this.mark = 0;
}
log.info(" 当前设备:["+this.key+"] 已上线:[{}]",isUpdate);
}
log.info(LocalDateTime.now()+" 当前设备:"+this.key+" 接收到数据: <<<<<< "+ this.message);
}catch (Exception e) {
e.printStackTrace();
}
//在接受到消息后进行回调一次进行检查
if (isSocketClosed(this)){
log.info("客户端已关闭,其Key值为:{}", this.getKey());
//关闭对应的服务端资源
close(this);
UpdateWrapper updateWrapper =new UpdateWrapper();
updateWrapper.eq("topic",this.key);
updateWrapper.set("sys_state", OFFLINE);
Boolean isUpdate = devInstallProfileService.update(updateWrapper);
log.info(" 当前设备:"+this.key+"下线:[{}]",isUpdate);
this.mark =0;
break;
}
}
}
}