Spring boot + websocket to realize real-time chat

Keywords: Session Javascript Java Spring

Spring boot + websocket for real-time chat

Recently, a little time, the last project just used websocket to realize broadcasting messages. Now, I want to sort out some previous codes and share them with you.

WebSocket protocol is a new network protocol based on TCP. It realizes full duplex communication between browser and server, which allows server to send information to client actively.

1, Environment introduction

Development tool: IntelliJ IDEA

Running environment: SpringBoot2.x, reconnecting websocket, JDK1.8 +, Maven 3.6+

Reconnecting websocket is a small JavaScript library, which encapsulates the mechanism of automatic reconnection when the WebSocket API is disconnected.

Simply:

ws = new WebSocket('ws://....');

Replace with:

ws = new ReconnectingWebSocket('ws://....');

WebSocket properties ws.readyState :

0 - indicates that the connection has not been established.

1 - indicates that the connection has been established and can be communicated.

2 - indicates that the connection is closing.

3 - indicates that the connection is closed or cannot be opened.

WebSocket event:

event Event handler describe
open ws.onopen Triggered when a connection is established
message ws.onmessage Triggered when the client receives data from the server
error ws.onerror Triggered when a communication error occurs
close ws.onclose Triggered when connection is closed

WebSocket method:

method describe
Socket.send() Send data using connection
Socket.close() Close connection

2, Code implementation

(1) . create a SpringBoot project

(2) , add pom dependency

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
   <!-- springbooot integrate websocket -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.5</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.71</version>
    </dependency>
</dependencies>

(3) . write front-end template index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>SpringBoot-ws</title>
    <script src="../js/reconnectingwebsocket.js" type="text/javascript" charset="utf-8"></script>
    <!--    <script src="../js/sockjs.min.js" type="text/javascript" charset="utf-8"></script>-->
    <script src="../js/jquery.min.js" type="text/javascript" charset="utf-8"></script>
    <link rel="stylesheet" type="text/css" href="../css/style.css">
</head>
<body>
<div id="info">
    <div>Sender:<input type="text" id="suer" required="required" placeholder="Please enter sender"></div>
    <div>Receiver:<input type="text" id="ruser" required="required" placeholder="Please enter receiver"></div>
</div>
<div id="index">
</div>
<div class="msg">
    <textarea id="send_content" placeholder="Enter a message here..."></textarea>
</div>
<div class="ibtn c">
    <button onclick=openWebsocket()>Open connection</button>
    <button onclick=closeWebsocket()>Close connection</button>
    <button onclick=sendMessage()>send message</button>
</div>
<script type="text/javascript">
    document.getElementById('send_content').focus();

    var websocket = null;

    //Close websocket
    function closeWebsocket() {
        //3 represents closed
        if (3 != websocket.readyState) {
            websocket.close();
        } else {
            alert("websocket Previously closed");
        }
    }

    // Open websocket
    function openWebsocket() {
        username = $("#suer").val()
        if (username != "") {

            //Whether websocket is supported before current browsing
            if ("WebSocket" in window) {
                websocket = new ReconnectingWebSocket("ws://localhost:8080/send/" + username);
                websocket.reconnectInterval = 3000 //Reconnection every 3s, default is per second
            } else if ('MozWebSocket' in window) {
                websocket = new MozWebSocket("ws://localhost:8080/send/" + username);
            } else {
                //Lower version
                websocket = new SockJS("http://localhost:8080/sockjs/send/" + username);
            }
        }
        websocket.onopen = function (event) {
            setMessage("open a connection");
        }

        websocket.onclose = function (event) {
            setMessage("Close connection");
        }

        websocket.onmessage = function (event) {
            // setMessage(event.data);
            setMessageTxt(event.data)

        }

        websocket.onerror = function (event) {
            setMessage("Connection exception, reconnecting...");
        }

        //Monitor the window closing event. When the window is closed, take the initiative to close the websocket connection to prevent closing the window before the connection is disconnected. The server will throw an exception.
        window.onbeforeunload = function () {
            closeWebsocket();
        }
    }

    //Show messages on Web pages
    function setMessage(message) {
        alert(message)
    }

    function setMessageTxt(message) {
        mObj = JSON.parse(message)
        var div = document.createElement('div')
        div.innerHTML = "<div class='name l'><h2>" + mObj['from_topic'] + "</h2></div>" +
            "<div class='content w l'>" + mObj['content'] + "</div>"
        div.setAttribute("class", "from_info")
        document.getElementById('index').appendChild(div)
    }

    // send message
    function sendMessage() {
        //1 for connecting
        if (1 == websocket.readyState) {
            var message = document.getElementById('send_content').value;
            var div = document.createElement('div')
            div.innerHTML = "<div class='name r rcontent'><h2> Me </h2></div>" +
                "<div class='content w r'>" + message + "</div>"
            div.setAttribute("class", "send_info")
            document.getElementById('index').appendChild(div)
            ruser = document.getElementById("ruser").value;
            message = "{'content':'" + message + "','to_topic':'" + ruser + "'}"
            websocket.send(message);
        } else {
            alert("websocket Not connected");
        }
        document.getElementById('send_content').value = "";
        document.getElementById('send_content').focus();
    }
</script>
</body>
</html>

(4) . server code writing

  1. to write SWCrontroller.java class
 package com.jhzhong.swchat.controller;
 
 import com.jhzhong.swchat.websocket.WebSocketServer;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 
 @Controller
 public class SWController {
 
     @Autowired
     private WebSocketServer webSocketServer;
 
     /**
      * author: jhzhong95@gmail.com
      * date: 2020-06-24 12:35 AM
      * desc: Jump index.html page
      * @return
      */
     @RequestMapping("/")
     public String index() {
         return "index";
     }
 }
  1. to write WebSocketConfig.java Class, enable WebSocket support.
 package com.jhzhong.swchat.websocket;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.socket.server.standard.ServerEndpointExporter;
 
 /**
  * author: jhzhong95@gmail.com
  * date: 2020-06-24 12:28 AM
  * desc: Enable WebSocket support
  */
 @Configuration
 public class WebSocketConfig {
 
     @Bean
     public ServerEndpointExporter serverEndpointExporter(){
         return new ServerEndpointExporter();
     }
 }
  1. Write core code class WebSocketServer.java
 package com.jhzhong.swchat.websocket;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import freemarker.log.Logger;
 import org.apache.commons.lang.StringUtils;
 import org.springframework.stereotype.Component;
 
 import javax.websocket.*;
 import javax.websocket.server.PathParam;
 import javax.websocket.server.ServerEndpoint;
 import java.io.IOException;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * author: jhzhong95@gmail.com
  * date: 2020-06-24 12:40 AM
  * desc: WebSocket Server
  */
 @ServerEndpoint("/send/{topic}")
 @Component
 public class WebSocketServer {
     static Logger logger = Logger.getLogger("WebSocketServer");
     /**
      * Static variable, used to record the current number of online connections. It should be designed to be thread safe.
      */
     private static int onlineCount = 0;
     /**
      * concurrent The thread safe Set of the package is used to store the MyWebSocket object corresponding to each client.
      */
     private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
     /**
      * A connection session with a client through which data is sent to the client
      */
     private Session session;
     /**
      * Receive channel topic
      */
     private String topic = "";
 
     /**
      * Method successfully called for connection establishment
      */
     @OnOpen
     public void onOpen(Session session, @PathParam("topic") String topic) {
         this.session = session;
         this.topic = topic;
         if (webSocketMap.containsKey(topic)) {
             webSocketMap.remove(topic);
             webSocketMap.put(topic, this);
             //Add to set
         } else {
             webSocketMap.put(topic, this);
             //Add to set
             addOnlineCount();
             //Online data plus 1
         }
 
         logger.info("User connection:" + topic + ",The current number of people online is:" + getOnlineCount());
         try {
             sendMessage("Connection successful");
         } catch (IOException e) {
             logger.error("user:" + topic + ",Network exception!!!!!!");
         }
     }
 
 
     /**
      * Method of connection close call
      */
     @OnClose
     public void onClose() {
         if (webSocketMap.containsKey(topic)) {
             webSocketMap.remove(topic);
             //Remove from set
             subOnlineCount();
         }
         logger.info("User exit:" + topic + ",The current number of people online is:" + getOnlineCount());
     }
 
     /**
      * Method of calling after receiving client message
      *
      * @param message Messages sent by clients
      */
     @OnMessage
     public void onMessage(String message, Session session) {
         logger.info("user:" + topic + ",information:" + message);
         //Can send messages in groups
         //Save messages to database and redis
         if (StringUtils.isNotBlank(message)) {
             try {
                 //Analyze the message sent
                 JSONObject jsonObject = JSON.parseObject(message);
                 //Add sender (prevent string change)
                 jsonObject.put("from_topic", this.topic);
                 String to_topic = jsonObject.getString("to_topic");
                 //websocket sent to the corresponding toUserId user
                 if (StringUtils.isNotBlank(to_topic) && webSocketMap.containsKey(to_topic)) {
                     webSocketMap.get(to_topic).sendMessage(jsonObject.toJSONString());
                 } else {
                     logger.error("Requested to_topic:" + to_topic + "Not on this server");
                     //Otherwise, it will not be on this server and will be sent to mysql or redis
                 }
             } catch (Exception e) {
                 e.printStackTrace();
             }
         }
     }
 
     /**
      * @param session
      * @param error
      */
     @OnError
     public void onError(Session session, Throwable error) {
         logger.error("user error:" + this.topic + ",reason:" + error.getMessage());
         error.printStackTrace();
     }
 
     /**
      * Implement server active push
      */
     public void sendMessage(String message) throws IOException {
         this.session.getBasicRemote().sendText(message);
     }
 
 
     /**
      * Send custom message
      */
     public static void sendInfo(String message, @PathParam("topic") String topic) throws IOException {
         logger.info("Send message to:" + topic + ",information:" + message);
         if (StringUtils.isNotBlank(topic) && webSocketMap.containsKey(topic)) {
             webSocketMap.get(topic).sendMessage(message);
         } else {
             logger.error("user" + topic + ",Not online!");
         }
     }
 
     public static synchronized int getOnlineCount() {
         return onlineCount;
     }
 
     public static synchronized void addOnlineCount() {
         WebSocketServer.onlineCount++;
     }
 
     public static synchronized void subOnlineCount() {
         WebSocketServer.onlineCount--;
     }
 }

3, Running screenshot

  1. Screenshot of home page

  2. Video effects

SpringBoot+WebSocket for message broadcasting

For source code, please refer to: sw-chat.zip Source download

Posted by mady on Thu, 25 Jun 2020 23:14:13 -0700