SpringBoot -- SpringBoot integrates WebSocket to realize simple multi person chat room

Posted by cjcdadams on Sat, 29 Jan 2022 05:28:06 +0100

Article directory:

1. What is WebSocket?

2. WebSocket API in Java

2.1 relevant annotations and API methods in websocket development

2.2 front end technology support for WebSocket

3. Implementation source code of multi person chat room

3.1 add related dependencies to POM file

2.2 configure the view parser in the core configuration file

2.3 add relevant static resource files

2.4 write control layer controller

2.5 write a configuration class to enable SpringBoot's support for WebSocket

2.6 write a tool class

2.7 core classes of websocket

2.8 finally is our jsp page

2.9 test results

1. What is WebSocket?

WebSocket protocol is a network protocol defined by HTML5 and implemented based on TCP protocol. Through this protocol, the server can actively send information to the client;

WebSocket protocol was born in 2008 and became the W3C international standard in 2011;

We already have HTTP protocol. Why is there a websocket protocol?

http protocol is a short connection, because after the request, the connection will be closed. The next time you re request data, you need to open the link again;

WebSocket protocol is a long connection, which only needs one request to initialize the connection, and then all requests and responses communicate through this TCP connection;

Therefore, the HTTP protocol communication can only be that the client sends a request to the server, and the server returns the response result. The HTTP protocol can not make the server actively push information to the client, but websocket can realize the full duplex communication between the server and the client;

What is full duplex

Information can only be transmitted in one direction as simplex; If information can be transmitted in both directions but cannot be transmitted in both directions at the same time, it is called half duplex, and if information can be transmitted in both directions at the same time, it is called full duplex;

Basic implementation principle

WebSocket protocol is implemented based on TCP protocol. After the client and server only need to make a handshake, a fast channel between the client and server is formed. After that, multiple two-way transmission of data frames can be carried out between the client and the server;

The purpose of this implementation is that when the client and the server communicate frequently, the server can avoid frequently creating HTTP connections, save resources, and improve work efficiency and resource utilization;

Implementation of traditional Web push

How does the server push messages to the browser before there is no WebSocket protocol?

At this time, the usual implementation method is to periodically poll the page through Ajax, such as sending an HTTP request to the server every 1 second, asking the server whether there is a new message, and the server returns the result;

The disadvantages of this form are obvious. The browser needs to constantly send HTTP requests to the server, and HTTP requests contain long headers and relatively few effective information. Repeated invalid requests occupy a lot of bandwidth and CPU resources, resulting in a great waste. Therefore, WebSocket should be born;

WebSocket protocol defined in HTML5 can better save server resources and bandwidth, and can communicate in real time;

WebSocket protocol is essentially a TCP based protocol, so it has nothing to do with HTTP protocol;

Characteristics of WebSocket

Full duplex communication, client and server can communicate equally in both directions;

Based on TCP protocol, the implementation of server-side is relatively easy;

The data format is light, the performance overhead is small, and the communication is efficient;

You can send text or binary data;

Communication has stronger real-time performance;

The protocol identifier is WS. For example, the server address is ws://www.abc.com com/some/path

The address of HTTP protocol is: http://......

websocket business scenario

WebSocket chat room;

Real time stock price display and other applications;

Instant messaging, games, visual large screen display and other fields;

Enterprise internal management communication and other functions, the main communication protocol is websocket;

Implementation of web chat and customer service system;

System reminder, user online and offline reminder, client synchronization, real-time data update, multi screen synchronization, user online status, message notification, scanning QR code login / QR code payment, bullet screen, various information reminders, online seat selection, real-time monitoring of large screen, etc;

2. WebSocket API in Java

In Java EE 7, the Java language begins to support websocket protocol. Java EE 7 defines a set of Websocket API specifications, that is, a series of interfaces, which are not implemented and are located in the package javax Websocket includes client-side API and server-side API. The Java API of websocket is only a specification. The specific implementation needs to be provided by web container (for example, tomcat implements Java websocket api), Java EE server or framework;

javax.websocket

This package contains all the WebSocket APIs common to both the client and server side.

javax.websocket.server

This package contains all the WebSocket APIs used only by server side applications.

1. Tomcat: the implementation of websocket in java, which requires tomcat 7.0.47 + or above to support Java EE7;

2. Spring websocket requires spring 4 x. So spring boot can also be used;

2.1 relevant annotations and API methods in websocket development

@ServerEndpoint("/websocket/{uid}")

Declare that this is a websocket service;

You need to specify the address to access the service. You can specify parameters in the address, which needs to be occupied through {};

@OnOpen

Usage: public void onOpen(Session session, @PathParam("uid") String uid) throws IOException {}

This method will be executed after the connection is established, and the session object will be passed in, that is, the long connection channel established between the client and the server, and the parameters declared in the url will be obtained through @ PathParam;

@OnClose

Usage: public void onClose() {}

The method is executed after the connection is closed;

@OnMessage

Usage: public void onmessage (string message, session) throws IOException {}

The method is used to receive the message sent by the client;

Message: message data sent;

Session: session object (also a long connection channel);

Send a message to the client;

Usage: session getBasicRemote(). sendText("hello,websocket.");

Send messages through session;

2.2 front end technology support for WebSocket

Websocket is an html5 specification, which is supported by mainstream browsers; (not supported by some older browsers)

jQuery, vueJS, React {JS, angularjs, etc. can support websocket objects;

The bottom layer is a js object of websocket supported by javascript. The connection of websocket can be established through this object: ws://localhost:8080/websocket/12345

3. Implementation source code of multi person chat room

3.1 add related dependencies to POM file

    <dependencies>

        <!-- SpringBoot frame web Project start dependence -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <!-- SpringBoot Frame infill Tomcat yes jsp Resolution dependency of -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <!-- lombok rely on -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

    </dependencies>

    <build>

        <!-- SpringBoot Framework compilation and packaging plug-in -->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>

        <!-- src/main/webapp Lower jsp Page compilation to META-INF/resources Can't access until -->
        <resources>
            <resource>
                <directory>src/main/webapp</directory>
                <targetPath>META-INF/resources</targetPath>
                <includes>
                    <include>*.*</include>
                </includes>
            </resource>
        </resources>
    </build>

2.2 configure the view parser in the core configuration file

#Configure view parser
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp

2.3 add relevant static resource files

2.4 write control layer controller

There is only one request, / chat, which will jump to our index JSP page.

package com.szh.springboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 */
@Controller
public class ChatController {

    //Declare atomic variable classes to ensure the atomicity and visibility of operations between the server and the client
    private AtomicInteger atomicInteger=new AtomicInteger();

    @RequestMapping("/chat")
    public String chat(Model model) {
        model.addAttribute("username","user" + atomicInteger.getAndIncrement());
        return "index";
    }
}

2.5 write a configuration class to enable SpringBoot's support for WebSocket

package com.szh.springboot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 *
 */
@EnableWebSocket //Enable SpringBoot support for WebSocket
@Configuration //Declare that this class is a configuration class
public class ChatConfig {

    /**
     * Configure the bean of ServerEndpointExporter
     * The Bean will automatically register the Websocket endpoint declared with the @ ServerEndpoint annotation
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpoint() {
        return new ServerEndpointExporter();
    }
}

2.6 write a tool class

This tool class encapsulates a large number of static methods for external calls.

package com.szh.springboot.endpoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A tool class for the realization of chat room function
 */
public class ChatUtils {

    //Define log object
    private static final Logger logger= LoggerFactory.getLogger(ChatUtils.class);

    //Define a map set to ensure data sharing and security. Here, use ConcurrentHashMap
    //The user name is key and the session information is value
    public static final Map<String, Session> CLIENTS=new ConcurrentHashMap<>();

    /**
     * Send messages using connections
     * @param session User's session
     * @param message Message content sent
     */
    public static void sendMessage(Session session,String message) {
        if (session == null) {
            return;
        }

        final RemoteEndpoint.Basic basic=session.getBasicRemote();
        if (basic == null) {
            return;
        }

        try {
            basic.sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
            logger.error("sendMessage IOException",e);
        }
    }

    /**
     * Send a message to everyone
     * @param message
     */
    public static void sendMessageAll(String message) {
        CLIENTS.forEach((sessionId,session) -> sendMessage(session,message));
    }

    /**
     * Get all online users
     */
    public static String getOnlineInfo() {
        Set<String> userNames=CLIENTS.keySet();
        if (userNames.size() == 0) {
            return "No one is currently online......";
        }
        return userNames.toString() + "on-line";
    }
}

2.7 core classes of websocket

package com.szh.springboot.endpoint;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

/**
 * @ServerEndpoint Specify the address of the WebSocket protocol in the annotation
 * @OnOpen,@OnMessage,@OnClose,@OnError The annotation corresponds to the listening event in WebSocket
 */
@Slf4j //Generate some log code
@Component
@ServerEndpoint("/websocket/{username}")
public class ChatServerEndpoint {

    /**
     * Triggered when the connection is established
     */
    @OnOpen
    public void onOpen(@PathParam("username") String username, Session session) {
        log.info("user{}Sign in",username);
        String message= "user[" + username + "]Entered the chat room!";

        //Send this user's login message to others
        ChatUtils.sendMessageAll(message);

        //Add your own information to the map collection
        ChatUtils.CLIENTS.put(username,session);

        //Get the current number of people online and send it to yourself for viewing
        String onlineInfo=ChatUtils.getOnlineInfo();
        ChatUtils.sendMessage(session,onlineInfo);
    }

    /**
     * Triggered when the client receives data from the server
     */
    @OnMessage
    public void onMessage(@PathParam("username") String username,String message) {
        log.info("Send message:{}, {}",username,message);
        //Broadcast and synchronize messages to other clients
        ChatUtils.sendMessageAll("[" + username + "]: " + message);
    }

    /**
     * Triggered when the connection is closed
     */
    @OnClose
    public void onClose(@PathParam("username") String username,Session session) {
        //Remove the user from the current map collection
        ChatUtils.CLIENTS.remove(username);

        //Notify others of the user's offline message
        ChatUtils.sendMessageAll("[" + username + "]Offline!");

        try {
            //Close the Seesion session under WebSocket
            session.close();
            log.info("{} Offline......",username);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("onClose error",e);
        }
    }

    /**
     * Triggered when an error occurs in chat communication
     */
    @OnError
    public void onError(Session session,Throwable throwable) {
        try {
            //Close the Seesion session under WebSocket
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
            log.error("onError Exception",e);
        }
        log.info("Throwable msg " + throwable.getMessage());
    }
}

2.8 finally is our jsp page

To put it simply, when we initiate the corresponding request in the controller, we will jump to this page. After the page is loaded (that is, the code before the < script > tag is executed), and then go to the script content. The following is ajax + jQuery. First, we get the request address of websocket, and then trigger onopen, onmessage onclose and onerror. In fact, it is not difficult to understand.

<%@ page contentType="text/html;charset=utf-8" language="java" %>
<html>
<head>
    <title>SpringBoot + WebSocket + JSP</title>
    <link rel="stylesheet" href="${pageContext.request.contextPath}/css/bootstrap.min.css">
    <script src="${pageContext.request.contextPath}/js/jquery.min.js"></script>
    <script src="${pageContext.request.contextPath}/js/bootstrap.min.js"></script>
</head>
<body style="margin: 45px">
    <h4>Zhang Qiling online chat room</h4>
    <div class="form-group">
        <label for="content"></label>
        <textarea id="content" readonly="readonly" cols="80" rows="15"></textarea>
    </div>
    <div class="form-group" style="margin-top: 8px">
        <textarea id="message" cols="80" rows="5" placeholder="Please enter a message"></textarea>
        <div style="margin-top: 10px">
            <button id="toSend" class="btn btn-info">send out</button>
            <button id="toExit" class="btn btn-danger">off-line</button>
            <input id="username" value="${username}" style="display: none">
        </div>
    </div>

    <script type="text/javascript">
        $(function () {
            var ws;
            //If the browser supports WebSocket
            if ("WebSocket" in window) {
                var baseUrl='ws://localhost:8080/websocket/';
                var username=$('#username').val();
                ws=new WebSocket(baseUrl + username);

                //After the connection is established, the event is triggered
                ws.onopen=function () {
                    console.log("establish websocket connect......");
                };

                //Receive the message from the background server and trigger the event
                ws.onmessage=function (event) {
                    $('#content').append(event.data + '\n\n');
                    console.log("Received the message sent by the server......" + event.data + '\n');
                };

                //An event is triggered when the connection is closed
                ws.onclose=function () {
                    $('#content').append('[' + username + '] offline');
                    console.log("close websocket connect......");
                };

                //An event is triggered when an error occurs
                ws.onerror=function (event) {
                    console.log("websocket An error occurred......" + event + '\n');
                };
            } else { //If the browser does not support WebSocket
                alert("Sorry, your browser doesn't support it WebSocket!!!");
            }

            //The behavior triggered by the send button. The client sends a message to the server
            $('#toSend').click(function () {
                sendMsg();
            });
            //Support the Enter key to send messages
            $(document).keyup(function (event) {
                if (event.keyCode == 13) {
                    sendMsg();
                }
            });

            //Function to send message
            function sendMsg() {
                ws.send($('#message').val());
                $('#message').val("");
            }

            //Offline button triggered behavior
            $('#toExit').click(function () {
                if (ws) {
                    ws.close();
                }
            })
        })
    </script>
</body>
</html>

2.9 test results

I have three users here, one user0, one user1 and one user2.

Visit the request address three times in the browser to open the chat room of three users.

Then we close the window of user2 or click the offline button.

When user2 is offline, you can see his offline message in the chat window of user0 and user1.

Finally, click the offline buttons of user1 and user0 in turn. Because Slf4j was used in our previous code, we return to IDEA to view the log information printed on the console.

 

Topics: Spring Boot