使用TCP Server+WebSocket实现数据实时展现

使用TCP Server+WebSocket实现数据实时展现

项目背景

由于业务需求,需要把RFID读写器读取到的标签信息解析后展示到页面中,考虑到RFID读写器可以充当TCP Client给TCP Server发送消息,于是使用了自定义TCP Server的方式来接收读写器发送的标签信息,使用了WebSocket来给客户端发送解析后的数据

 自定义TCP Server

自定义TCP Server我们可以通过java.net包中的ServerSocket类创建,由于在主线程中使用accept()方法会出现阻塞的情况,所以使用一个新线程来处理客户端连接,关键代码如下

// 创建一个端口为9900的TCP Server对象
ServerSocket serverSocket = new ServerSocket(9900);
log.info("the server is start,waiting client connect");

// 创建新线程处理客户端请求,把父线程的bean传递到子
Thread acceptThread = new Thread(() -> acceptConnections(serverSocket));


   /**
     * 接收客户端连接
     *
     * @param serverSocket 客户端连接
     */
    private void acceptConnections(ServerSocket serverSocket) {
        try {
            while (true) {
                // 等待客户端连接,等待时处于阻塞
                Socket clientSocket = serverSocket.accept();
                log.info("Client connection is successful");

                // 创建新线程处理客户端请求,把父线程的 bean 传递到子线程
                Thread clientThread = new Thread(ClientHandler.builder()
                        .socket(clientSocket)
                        .machineService(machineService)
                        .redisUtil(redisUtil)
                        .build());
                clientThread.start();
            }
        } catch (IOException e) {
            log.error("Error accepting connections", e);
        }
    }
接收客户端数据

当我们把客户端连接给到新线程中后,我们可以通过socket对象来获取输入流,从而达到读取客户端传输过来的数据,关键代码如下

             try {
                // 通过 Socket 对象获取输入流,读取客户端发来的数据
                InputStream inputStream = socket.getInputStream();
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    String text = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8);
                    //处理客户端传输的数据
                    handleData(text);
                }
            } catch (Exception e) {
                log.error("client monitor error:{0}", e);
            }
自定义WebSocket连接

WebSocket的概念我相信大家都有所了解,WebSocket与HTTP不同,WebSocket可以实现服务端与客户端双向通信,WebSocket协议适用于需要实时或频繁通信的应用程序,一般用在股票、游戏、直播上。它提供了一种更高效、更实时的通信机制,与传统的轮询或长轮询相比,WebSocket可以减少网络延迟和服务器负载。

使用WebSocket之前,我们需要先引入WebSocket的依赖

        <!-- SpringBoot Websocket -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

引入WebSocket的依赖后,我们可以还需要注入WebSocketConfig

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

代码中ServerEndpointExporter的作用是用于扫描项目中所有包含@ServerEndpoint注解的节点,并把节点注册到Spring中,接下来我们实现一个WebSocket节点

@Slf4j
@Component
@ServerEndpoint(value = "/webSocketServer/{userId}")
public class WebSocketServer {

    private static final ConcurrentHashMap<String, CopyOnWriteArraySet<WebSocketServer>> connectionMap = new ConcurrentHashMap<>();

    private Session session;

    /**
     * 建立客户端连接
     */
    @OnOpen
    public void start(@PathParam("userId") String userId, Session session) {
        this.session = session;
        CopyOnWriteArraySet<WebSocketServer> connections = new CopyOnWriteArraySet<>();
        if (connectionMap.containsKey(userId)) {
            connections = connectionMap.get(userId);
        }
        connections.add(this);
        connectionMap.put(userId, connections);
        log.info("{}create connection", userId);
    }

    /**
     * 关闭客户端连接
     */
    @OnClose
    public void end(@PathParam("userId") String userId) {
        CopyOnWriteArraySet<WebSocketServer> connections = connectionMap.get(userId);
        connections.remove(this);
        if (CollectionUtils.isEmpty(connections)) {
            connectionMap.remove(userId);
        }
        log.info("{}exit connection", userId);
    }

    /**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void pushMsg(String message) {
        log.info("收到客户端消息{}", message);
    }

    @OnError
    public void onError(Throwable t) {
        log.error("连接异常!{0}", t);
    }

    /**
     * 发送消息
     *
     * @param msg 消息内容
     */
    public static void broadcast(String userId, String msg) {
        CopyOnWriteArraySet<WebSocketServer> connections = connectionMap.get(userId);
        ReentrantLock lock = new ReentrantLock();
        if(ObjectUtil.isNotEmpty(connections)){
            connections.parallelStream().forEach(client -> {
                if (client != null) {
                    try {
                        lock.lock();
                        client.session.getBasicRemote().sendText(msg);
                    } catch (IOException e) {
                        connections.remove(client);
                        try {
                            client.session.close();
                        } catch (IOException e1) {
                            e.printStackTrace();
                        }
                        String message = String.format("* %s", "disconnect");
                        broadcast(userId, message);
                    }finally {
                        lock.unlock();
                    }
                }
            });
        }
    }

实现Websocket节点之后,只需要调用broadcast方法即可向客户端发送信息

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值