Using webSocket in Spring Boot to implement real-time message push

Before writing this blog, I looked through a lot of information and read many webSocket-related blog posts on CSDN about webSocket, but I did not find a relatively complete one. Most of them were incomplete and talked about related concepts, but In the actual demo process, most of the code is directly omitted, and then if you take it directly, most of it will not run, which means you have to figure it out yourself, but I know that as a developer, although this is not very It’s troublesome, but it’s still quite torturous. After all, what everyone hopes for most is that you provide the complete code and I can use it directly.

This blog teaches you how to use webSocket to complete one-to-many communication, one-to-one communication, and talk to yourself. I dare say that if you finish studying this blog, you will follow the demo I provide in this blog. For development, you can independently complete a web online chat system. The specific implementation depends on your own research.

What is webSocket?

WebSocket is a protocol that enables two-way communication in web applications. It allows the establishment of a persistent connection between the server and the client, and real-time two-way communication through this connection. Unlike the traditional HTTP request-response model, WebSocket provides a more efficient, real-time communication method.

To achieve two-way communication between server and client through WebSocket, you can follow the following steps:

  • Establish a WebSocket connection: The client creates a WebSocket object through JavaScript and specifies the server’s WebSocket endpoint (URL). The client handles the event that the WebSocket connection is successfully established by calling the onopen event listener of the WebSocket object.
  • Send and receive messages: The client and server can send and receive messages through the send method and onmessage event listener of the WebSocket object. The client can use the send method to send messages to the server, and the server can use the onmessage event listener to handle received messages.
  • Close WebSocket connection: The client and server can actively close the WebSocket connection by calling the close method of the WebSocket object. In addition, WebSocket connections can also be automatically closed due to server-side or network failures and other reasons.

webSocket case

Import related dependencies

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


        <dependency>
            <!-- websocket -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <!-- fastjson -->
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

Configure a WebSocketConfig

@Configuration
public class WebSocketConfig {<!-- -->
    /**
     * Inject a ServerEndpointExporter, and the Bean will automatically register the websocket endpoint declared using the @ServerEndpoint annotation
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {<!-- -->
        return new ServerEndpointExporter();
    }
}

Create a MyMessage entity class

to receive relevant information

public class MyMessage {<!-- -->

    private String userId;

    private String message;


    public String getUserId() {<!-- -->
        return userId;
    }

    public void setUserId(String userId) {<!-- -->
        this.userId = userId;
    }

    public String getMessage() {<!-- -->
        return message;
    }

    public void setMessage(String message) {<!-- -->
        this.message = message;
    }

    public MyMessage(String userId, String message) {<!-- -->
        this.userId = userId;
        this.message = message;
    }

    public MyMessage() {<!-- -->
    }

    @Override
    public String toString() {<!-- -->
        return "MyMessage{" +
                "userId='" + userId + ''' +
                ", message='" + message + ''' +
                '}';
    }
}

Create an enumeration class WebState

In fact, using an enumeration class is redundant in this demo. The reason why I created the enumeration class is to define error codes, that is, in order to look more standardized. As for why I define the enumeration in a demo Class objects, this is actually superfluous. Even the enumeration class parameters I defined, I haven’t used many of them. In fact, it just makes the project look more standardized.

public enum WebState {<!-- -->
    /**
     * connection succeeded
     */
    CONNECTED("200","Connection successful"),
    /**
     * Connection failed
     */
    DISCONNECTED("400","Connection failed"),
    /**
     * connecting
     */
    CONNECTING("199","Connecting"),
    /**
     * Disconnect
     */
    DISCONNECTING("401","The connection has been disconnected"),

    /**
     * unknown mistake
     */

    UNKNOWN_ERROR("-1","Unknown error"),

    /**
     * success
     */
    SUCCESS("200","Success"),
    /**
     * fail
     */
    FAIL("400","Failure"),
    /**
     * Parameter is illegal
     */
    PARAM_ERROR("201","Illegal parameter"),

    /**
     * Database exception
     */
    DATABASE_ERROR("202","Database exception")


    ;


    /**
     * error code
     */
    private String state;
    /**
     * wrong information
     */
    private String msg;
    WebState(String state, String msg) {<!-- -->
        this.state = state;
        this.msg = msg;
    }

    public String getState() {<!-- -->
        return state;
    }

    public String getMsg() {<!-- -->
        return msg;
    }
}

Realize yourself to speak to yourself

/**
 * The front-end and back-end interaction classes implement the reception and push of messages (send to yourself)
 *
 * @ServerEndpoint(value = "/test/one") The front end interacts with the back end through this URI to establish a connection
 */
@Slf4j
@ServerEndpoint(value = "/test/one")
@Component
public class OneWebSocket {<!-- -->

    /**
     * Record the current number of online connections
     */
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    /**
     * Method called when the connection is established successfully
     */
    @OnOpen
    public void onOpen(Session session) {<!-- -->
        onlineCount.incrementAndGet(); // Add 1 to the online count
        log.info("Detected status code {}, corresponding information: {}, new connection added: {}, current number of people online: {}", WebState.CONNECTED.getState(), WebState.CONNECTED.getMsg() ,session.getId(), onlineCount.get());
    }

    /**
     * Method called when the connection is closed
     */
    @OnClose
    public void onClose(Session session) {<!-- -->
        onlineCount.decrementAndGet(); // Decrease the online count by 1
        log.info("There is a connection closed: {}, the current number of people online is: {}", session.getId(), onlineCount.get());
    }

    /**
     * Method called after receiving client message
     *
     * @param message message sent by the client
     */
    @OnMessage
    public void onMessage(String message, Session session) {<!-- -->
        log.info("The server received the message from the client [{}]: {}", session.getId(), message);
        this.sendMessage("Hello, " + message, session);
    }

    @OnError
    public void onError(Session session, Throwable error) {<!-- -->
        log.error("An error occurred, the error parameter is {}, the error reason is {}", WebState.UNKNOWN_ERROR.getState(),WebState.UNKNOWN_ERROR.getMsg());
        error.printStackTrace();
    }

    /**
     * The server sends a message to the client
     */
    private void sendMessage(String message, Session toSession) {<!-- -->
        try {<!-- -->
            log.info("Server sends message {} to client [{}]", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        } catch (Exception e) {<!-- -->
            log.error("The server failed to send a message to the client: {}", e);
        }
    }
}

To support the front-end that speaks to itself, create a new static folder in the Resources directory to store index.html and other html files linked to index.html.

<!DOCTYPE HTML>
<html>
<head>
    <title>My WebSocket</title>
</head>

<body>
<input id="text" type="text" />
<br>

<a href="oneToOne.html">oneToOne</a>
<br>
<a href="oneToMany.html">oneToMany</a>
<br>
<button onclick="send()">Send</button>
<br>
<button onclick="closeWebSocket()">Close</button>
<div id="message"></div>
</body>

<script type="text/javascript">
    var websocket = null;

    //Determine whether the current browser supports WebSocket, mainly change it to your own address here
    if ('WebSocket' in window) {<!-- -->
        websocket = new WebSocket("ws://localhost:8021/test/one");
    } else {<!-- -->
        alert('Not support websocket')
    }

    //Callback method for connection errors
    websocket.onerror = function() {<!-- -->
        setMessageInnerHTML("error");
    };

    //Callback method for successful connection establishment
    websocket.onopen = function(event) {<!-- -->
        //setMessageInnerHTML("open");
    }

    //Callback method for receiving message
    websocket.onmessage = function(event) {<!-- -->
        setMessageInnerHTML(event.data);
    }

    //Callback method for connection closing
    websocket.onclose = function() {<!-- -->
        setMessageInnerHTML("close");
    }

    //Listen to the window closing event. When the window is closed, actively close the websocket connection to prevent the window from closing before the connection is disconnected. The server will throw an exception.
    window.onbeforeunload = function() {<!-- -->
        websocket.close();
    }

    //Display the message on the web page
    function setMessageInnerHTML(innerHTML) {<!-- -->
        document.getElementById('message').innerHTML + = innerHTML + '<br/>';
    }

    //Close the connection
    function closeWebSocket() {<!-- -->
        websocket.close();
    }

    //Send a message
    function send() {<!-- -->
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>

Front-end testing:

One-on-one chat

/**
 * The front-end and back-end interaction classes implement the reception and push of messages (send to another person by yourself)
 *
 * @ServerEndpoint(value = "/test/oneToOne") The front end interacts with the back end through this URI to establish a connection
 */
@Slf4j
@ServerEndpoint(value = "/test/oneToOne")
@Component
public class OneToOneWebSocket {<!-- -->

    /** Record the current number of online connections */
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    /** Store all online clients */
    private static Map<String, Session> clients = new ConcurrentHashMap<>();

    /**
     * Method called when the connection is established successfully
     */
    @OnOpen
    public void onOpen(Session session) {<!-- -->
        onlineCount.incrementAndGet(); // Add 1 to the online count
        clients.put(session.getId(), session);
        log.info("There is a new connection: {}, the current number of people online is: {}", session.getId(), onlineCount.get());
    }

    /**
     * Method called when the connection is closed
     */
    @OnClose
    public void onClose(Session session) {<!-- -->
        onlineCount.decrementAndGet(); // Decrease the online count by 1
        clients.remove(session.getId());
        log.info("There is a connection closed: {}, the current number of people online is: {}", session.getId(), onlineCount.get());
    }

    /**
     * Method called after receiving client message
     *
     * @param message
     * Message sent by the client
     */
    @OnMessage
    public void onMessage(String message, Session session) {<!-- -->
        log.info("The server received the message [{}] from the client [{}]", session.getId(), message);
        try {<!-- -->
            //MyMessage myMessage = new MyMessage();
            //myMessage.setUserId(session.getId());
            //myMessage.setMessage(message);
              MyMessage myMessage = JSON.parseObject(message, MyMessage.class);
            if (myMessage != null) {<!-- -->
                Session toSession = clients.get(myMessage.getUserId());
                if (toSession != null) {<!-- -->
                    this.sendMessage(myMessage.getMessage(), toSession);
                }
            }
        } catch (Exception e) {<!-- -->
            log.error("Parse failed: {}", e);
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {<!-- -->
        log.error("An error occurred");
        error.printStackTrace();
    }

    /**
     * The server sends a message to the client
     */
    private void sendMessage(String message, Session toSession) {<!-- -->
        try {<!-- -->
            log.info("The server sends a message [{}] to the client [{}]", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        } catch (Exception e) {<!-- -->
            log.error("The server failed to send a message to the client: {}", e);
        }
    }

}

Comparing the index.html above, it is easy to find that this html only changes one link, and the other parts have not changed.

<!DOCTYPE HTML>
<html>
<head>
    <title>My WebSocket</title>
</head>

<body>
<input id="text" type="text" />
<button onclick="send()">Send</button>
<button onclick="closeWebSocket()">Close</button>
<div id="message"></div>
</body>

<script type="text/javascript">
    var websocket = null;

    //Determine whether the current browser supports WebSocket, mainly change it to your own address here
    if ('WebSocket' in window) {<!-- -->
        websocket = new WebSocket("ws://localhost:8021/test/oneToOne");
    } else {<!-- -->
        alert('Not support websocket')
    }

    //Callback method for connection errors
    websocket.onerror = function() {<!-- -->
        setMessageInnerHTML("error");
    };

    //Callback method for successful connection establishment
    websocket.onopen = function(event) {<!-- -->
        //setMessageInnerHTML("open");
    }

    //Callback method for receiving message
    websocket.onmessage = function(event) {<!-- -->
        setMessageInnerHTML(event.data);
    }

    //Callback method for connection closing
    websocket.onclose = function() {<!-- -->
        setMessageInnerHTML("close");
    }

    //Listen to the window closing event. When the window is closed, actively close the websocket connection to prevent the window from closing before the connection is disconnected. The server will throw an exception.
    window.onbeforeunload = function() {<!-- -->
        websocket.close();
    }

    //Display the message on the web page
    function setMessageInnerHTML(innerHTML) {<!-- -->
        document.getElementById('message').innerHTML + = innerHTML + '<br/>';
    }

    //Close the connection
    function closeWebSocket() {<!-- -->
        websocket.close();
    }

    //Send a message
    function send() {<!-- -->
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>

There are conditions for one-to-one chat, that is, we need to specify the object we need to chat with. The value we need to pass here is that the parameter is json, which means that we need to process it when passing it on the front end:
{"message":"Hello", "userId":1}, no matter who you are, I specify to only send messages to user No. 1. In this way, messages can be sent one-to-one.

PS: {“message”:”Hello”, “userId”:1}, is very important. Since it is a test, it has not been actually processed. Note that if the parameters are not passed in JSON in one-to-one, an error will be reported


User No. 1 received:

One-to-many

For this one-to-many, the main thing is to open a few more boxes for verification. It should be noted that in this demo, if the author sends a message, the author will not receive the message, but as long as all users log in through oneToMany, they will Everyone can receive the message, and everyone except you can receive the message.

/**
 *
 * The front-end and back-end interaction class implements the reception and push of messages (sends to everyone (excluding yourself))
 *
 * @ServerEndpoint(value = "/test/oneToMany") The front end interacts with the back end through this URI to establish a connection
 */
@Slf4j
@ServerEndpoint(value = "/test/oneToMany")
@Component
public class OneToManyWebSocket {<!-- -->

    /** Record the current number of online connections */
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    /** Store all online clients */
    private static Map<String, Session> clients = new ConcurrentHashMap<>();

    /**
     * Method called when the connection is established successfully
     */
    @OnOpen
    public void onOpen(Session session) {<!-- -->
        onlineCount.incrementAndGet(); // Add 1 to the online count
        clients.put(session.getId(), session);
        log.info("There is a new connection: {}, the current number of people online is: {}", session.getId(), onlineCount.get());
    }

    /**
     * Method called when the connection is closed
     */
    @OnClose
    public void onClose(Session session) {<!-- -->
        onlineCount.decrementAndGet(); // Decrease the online count by 1
        clients.remove(session.getId());
        log.info("There is a connection closed: {}, the current number of people online is: {}", session.getId(), onlineCount.get());
    }

    /**
     * Method called after receiving client message
     *
     * @param message
     * Message sent by the client
     */
    @OnMessage
    public void onMessage(String message, Session session) {<!-- -->
        log.info("The server received the message from the client [{}]: {}", session.getId(), message);
        this.sendMessage(message, session);
    }

    @OnError
    public void onError(Session session, Throwable error) {<!-- -->
        log.error("An error occurred");
        error.printStackTrace();
    }

    /**
     * Group message
     *
     * @param message
     *            Message content
     */
    private void sendMessage(String message, Session fromSession) {<!-- -->
        for (Map.Entry<String, Session> sessionEntry : clients.entrySet()) {<!-- -->
            Session toSession = sessionEntry.getValue();
            // exclude yourself
            if (!fromSession.getId().equals(toSession.getId())) {<!-- -->
                log.info("Server sends message {} to client [{}]", toSession.getId(), message);
                toSession.getAsyncRemote().sendText(message);
            }
        }
    }
}

Change the access backend address of the previous page to: websocket = new WebSocket("ws://localhost:8021/test/oneToMany");



Clients 2 and 4 received the message from client 3: