springBoot Tcp服务端实现 webSocket Tcp

该博客介绍了如何使用Springboot构建TCP服务,通过WebSocket实现服务监听和客户端连接处理。服务端创建ServerSocket监听特定端口,接收到连接请求后,创建线程处理客户端的读写操作。SocketHandler类负责数据的读写、销毁链接和资源回收,而ClientSocket对象则用于封装每个客户端的连接信息。整个流程确保了TCP连接的稳定性和数据交换的可靠性。
摘要由CSDN通过智能技术生成

使用Springboot+webSocket 构建TCP 服务

1. 服务监听


/**
 * @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();
    }
}

2. socket任务处理器 SocketHandler,里面封装读写数据,销毁链接,等方法.

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;
        }
    }
}

3. 每个链接都是一个ClientSocket对象

/**
 * @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);
    }
}

4. 消息处理

/**
 * @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;
            }
        }
    }
}
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值