[Implementation scheme] springboot uses websocket to communicate with the client in real time (with multi-person chat room + heartbeat detection)

foreword

In a previous project, there was a need for real-time communication to be realized, so I initially came into contact with the websocket protocol and made a simple business implementation. As a preliminary understanding, give everyone a reference.

1. Why use websocket?

In other words, what problem does websocket solve? The answer is, two main problems are solved:

  • Only clients can send requests
  • Frequent message sending over a period of time

Assuming that it is necessary to design a notification module of a real-time early warning system, how should we, as engineers, design the notification function? If we only have the http protocol now, then we can only let the client poll the server continuously. The smaller the polling interval, the closer to real-time effect. However, polling is inefficient and wastes resources. For such a scenario, websocket came into being.

2. websocket

The WebSocket protocol was born in 2008 and became an international standard in 2011. All browsers already support it.
Its biggest feature is that the server can actively push information to the client, and the client can also actively send information to the server. It is a real two-way equal dialogue and belongs to a kind of server push technology.
insert image description here
Other features include:
(1) Based on the TCP protocol, the implementation of the server side is relatively easy, and it is a reliable transmission protocol.
(2) It has good compatibility with HTTP protocol. The default ports are also 80 and 443, and the handshake phase uses the HTTP protocol, so it is not easy to shield during the handshake, and can pass through various HTTP proxy servers.
(3) The data format is relatively light, the performance overhead is small, and the communication is efficient.
(4) Text or binary data can be sent.
(5) There is no same-origin restriction, and the client can communicate with any server.
(6) The protocol identifier is ws (or wss if encrypted), and the server URL is the URL.

The above content is taken from http://www.ruanyifeng.com/blog/2017/05/websocket.html

3. Implementation of websocket in springboot

Directly upload the code, omitting the package and import, and realize a demo with multiple chat rooms.

Among them, the concurrent processing of the onOpen method and the data structure of webSocketSet need to be well understood.

/**
 * websocket,实时信息回传。
 *
 * @author amber
 * @date 2020-08-05
 */

@Component
@ServerEndpoint("/websocket/{uniCode}") //通过 unicode 识别加入的用户(连接),放在一个集合中。
//每一个连接,就是一个实例。
public class RealTimeWebSocketServer{
    
    


    /**
     * 与某个客户端的连接对话,需要通过它来给客户端发送消息。
     * 每个用户(连接)私有。
     */
    private Session session;

    /**
     * static
     * 储存链接 <uniCode, set<instance>>
     * ConcurrentHashMap 线程安全,读取非阻塞
     * CopyOnWriteArraySet 线程安全
     */
    private static final ConcurrentHashMap<String, CopyOnWriteArraySet<RealTimeWebSocketServer>> webSocketSet = new ConcurrentHashMap<>();

	//每一个新连接进来执行的方法
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "uniCode") String uniCode) {
    
    
        this.session = session;
        //先查找是否有uniCode
        CopyOnWriteArraySet<RealTimeWebSocketServer> users = webSocketSet.get(uniCode);
        if (users == null) {
    
    
            //处理读并发
            synchronized (webSocketSet) {
    
    
                if (!webSocketSet.contains(uniCode)) {
    
    
                    users = new CopyOnWriteArraySet<>();
                    webSocketSet.put(uniCode, users);
                    generateRealTimeInfo(uniCode);
                }
            }
        }
        users.add(this);
        logger.info("连接成功,当前房间数为:" + webSocketSet.size()
                + ",连接ID:" + uniCode
                + ",房间号:" + uniCode
                + ",当前房间人数:" + webSocketSet.get(uniCode).size());
    }

	//关闭连接执行的方法
    @OnClose
    public void onClose(Session session) {
    
    
        // 避免多人同时在线直接关闭通道。
        Object[] objects = webSocketSet.get(this.uniCode).toArray();
        for (int i = 0; i < objects.length; i++) {
    
    
            if(((RealTimeWebSocketServer) objects[i]).session.equals(session)){
    
    
                //删除房间中当前用户
                webSocketSet.get(this.uniCode).remove((RealTimeWebSocketServer) objects[i]);
            }
        }
        if(webSocketSet.get(uniCode).size() <= 0){
    
    
            //删除房间
            webSocketSet.remove(uniCode);
            logger.info("ID:" + uniCode+ "退出成功 ");
        }else{
    
    
            logger.info("ID:" + uniCode+ " 1名用户退出,剩余" + webSocketSet.get(uniCode).size() + "名用户在线");
        }
    }

	//实例收到消息执行此方法
    @OnMessage
    public void onMessage(String message) {
    
    
        logger.info("收到消息:" + message);
        //刷新实时巡检进度
        CopyOnWriteArraySet<RealTimeWebSocketServer> users = webSocketSet.get(uniCode);
        if (users != null) {
    
    
            for (RealTimeWebSocketServer user : users) {
    
    
                    user.session.getAsyncRemote().sendText(message);
                    logger.info("发送消息:" + message);
            }
        }
    }

	//未知错误执行此方法
    @OnError
    public void onError(Session session, Throwable error) {
    
    
        logger.info("发生错误" + new Date());
        logger.error(error.getMessage(), error);
    }

}

4. Heartbeat detection

What is heartbeat detection?

Generally, in a long connection, there may be no data exchange for a long period of time, so generally periodically, the front end will send a heartbeat packet to the back end to ensure the validity of the connection.

Why is the heartbeat detection not sent by the backend, but by the frontend?

  • The backend may maintain a large number of connections. If it is used as a scheduled task to send a heartbeat packet to a connection without data, the overhead will be high.
  • From a business point of view, it is more reasonable for each front end to be the sender and receiver of most of the information, and it is more reasonable to control the connection.
Implementation

The application layer, which is a function that the front and back ends cooperate with each other.

front-end code

//心跳检测
var heartCheck = {
    
    
      timeout: 60000, //每隔60秒发送心跳
      severTimeout: 5000,  //服务端超时时间
      timeoutObj: null,
      serverTimeoutObj: null,
      start: function(){
    
    
        var _this = this;
        this.timeoutObj && clearTimeout(this.timeoutObj);
        this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
        this.timeoutObj = setTimeout(function(){
    
    
              //这里发送一个心跳,后端收到后,返回一个心跳消息,
              //onmessage拿到返回的心跳就说明连接正常
              ws.send("心跳包"); // 心跳包
              //计算答复的超时时间
              _this.serverTimeoutObj = setTimeout(function() {
    
    
                  ws.close();
              }, _this.severTimeout);
        }, this.timeout)
      }
}

====================================================================
  ws.onopen = function () {
    
    
    //心跳检测重置
    heartCheck.start();
  };
  
  ws.onmessage = function (event) {
    
    
    //拿到任何消息都说明当前连接是正常的
    console.log('接收到消息');
    heartCheck.start();
  }
  

References

https://www.cnblogs.com/FatKee/articles/10250854.html

Guess you like

Origin blog.csdn.net/weixin_43742184/article/details/112555102
Recommended