An overview of the

The WebSocket story series is planned as a five-part, six-chapter introduction to WebSocket and how to quickly build and use the capabilities WebSocket provides in Springboot. This series is planned to include the following articles:

Part 1: What WebSocket is and what it Does Part 2: How to Use STOMP to Quickly Build WebSocket broadcast message Patterns in Spring In Springboot, how to use WebSocket and STOMP to quickly build point-to-point messaging mode (2) in Springboot, how to implement custom WebSocket message proxy in web chat rooms

The main line of this article

This article will show you how to use WebSocket to implement some specific product features through a close to real world web chat room Demo. This article will only use WebSocket itself, not STOMP and other packages. Hands-on implementation of message receiving, processing, sending and WebSocket session management. This is the most important one in this series, and whether you’re excited or not, I am. So let’s get started.

This article is suitable for readers

Students and aspiring young people want to know how to customize the implementation of more complex WebSocket product logic in Springboot.

Small web chat room needs

In order to express the technical points of this article with a clear goal, I designed a small chat room product that lists the requirements first so that you can see why when you look at the implementation.

These are the requirements we will implement in this article. To put it simply:

Users can join, quit a room, after joining can send messages to all people in the room, can also send a message to a person.

Requirements analysis and design

Designing user storage

It is easy to think that the main body of our design is the user, the session, and the room. In terms of user management, we can use the following diagram to represent the relationship between them:

This allows us to use a simple Map for room <-> user groups, and another Map for user name <-> Session (assuming no duplicate names) within user groups. In this way, we solve the storage and maintenance of relationships between rooms and user groups, users and sessions.

Design the relationship between user behavior and users

A brother saw this and said, “You have been talking about STOMP for a long time. You have something to do with STOMP and messaging agent in previous articles?” We learn STOMP, we learn message broker, we learn peer-to-peer messaging, and the most important thing is to learn ideas, right? So let’s use that.

When a user joins a room, the user will be notified of any movement in the room, such as joining, exiting, or sending a public message. At this point, we can think of creating a room as “creating a message broker”, and see the user joining the room as a “subscription” to the “message broker” of the room, and see the user exiting the room as a “unsubscription” to the “message broker” of the room.

So, the first person to join the room, which we define as “creating the room,” creates a message broker. To make sense of it, above:

The little red figure represents the first user to join the room, that is, the person who created the room. When a user sends a message, if he chooses to send the message to all the people in the chat room, that is, he sends a broadcast in the room, all the users who subscribe to the room will receive the broadcast message; If you choose to send a whisper, messages are only sent to users with a specific username, that is, point-to-point messages.

To summarize the main points we are trying to achieve:

  • User storage is the relationship between users, rooms, sessions, and object access.
  • The message broker (room) is dynamically created and users are bound to the room (subscription).
  • The ability to send a message individually to a user.

So much for the general design, a few more details, let’s look at the demo first, and then we’ll look at the implementation through the code.

Chat room effect display

After opening the client page with the browser, the input box and the Join button are displayed. Enter room number 1 and user name Xiao Ming, click to enter the room.

After entering the room successfully, show the number of people in the room and welcome words.

Display notification messages when other people join or leave the room. Can send public screen message and private chat message.

Let’s take a look at how these key features are implemented.

Code implementation

According to our above design, I will introduce the key part of the code design and technical points.

Server-side implementation

1. Configuration WebSocket

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), "/webSocket/{INFO}").setAllowedOrigins("*")
                .addInterceptors(newWebSocketInterceptor()); }}Copy the code

Key words:

  • registeredWebSocketHandler(MyHandler), a class that handles WebSocket setup and message handling, more on that later.
  • registeredWebSocketInterceptorInterceptor, which is used to record client interception information when the client initiates the first connection to the server.
  • Register the WebSocket address and attach it{INFO}Parameter to carry user information when registering.

All of this will be covered in more detail in subsequent code.

2. Implement handshake interceptor

public class WebSocketInterceptor implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        if (serverHttpRequest instanceof ServletServerHttpRequest) {
            String INFO = serverHttpRequest.getURI().getPath().split("INFO=") [1];
            if(INFO ! =null && INFO.length() > 0) {
                JSONObject jsonObject = new JSONObject(INFO);
                String command = jsonObject.getString("command");
                if(command ! =null && MessageKey.ENTER_COMMAND.equals(command)) {
                    System.out.println("Current session ID="+ jsonObject.getString("name"));
                    ServletServerHttpRequest request = (ServletServerHttpRequest) serverHttpRequest;
                    HttpSession session = request.getServletRequest().getSession();
                    map.put(MessageKey.KEY_WEBSOCKET_USERNAME, jsonObject.getString("name"));
                    map.put(MessageKey.KEY_ROOM_ID, jsonObject.getString("roomId")); }}}return true; }}Copy the code

Key words:

  • HandshakeInterceptorUsed to intercept a client’s first connection to a server, that is, a client connection/webSocket/{INFO}, we can get the correspondingINFOThe information.
  • implementationbeforeHandshakeMethod to save user information, here we save the user name and room number toSessionOn.

3. Implement the message processor WebSocketHandler

public class MyHandler implements WebSocketHandler {

    // Save the user, room, and session. Use a two-layer Map to implement the mapping.
    private static final Map<String, Map<String, WebSocketSession>> sUserMap = new HashMap<>(3);

    // This method is called after the user joins the room, and we send notification messages to other users from this node.
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("Connection established successfully");
        String INFO = session.getUri().getPath().split("INFO=") [1];
        System.out.println(INFO);
        if(INFO ! =null && INFO.length() > 0) {
            JSONObject jsonObject = new JSONObject(INFO);
            String command = jsonObject.getString("command");
            String roomId = jsonObject.getString("roomId");
            if(command ! =null && MessageKey.ENTER_COMMAND.equals(command)) {
                Map<String, WebSocketSession> mapSession = sUserMap.get(roomId);
                if (mapSession == null) {
                    mapSession = new HashMap<>(3);
                    sUserMap.put(roomId, mapSession);
                }
                mapSession.put(jsonObject.getString("name"), session);
                session.sendMessage(new TextMessage("Number of current room online" + mapSession.size() + "People"));
                System.out.println(session);
            }
        }
        System.out.println("Number of current online users:" + sUserMap.size());
    }

    // Message handling methods
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage
        webSocketMessage) {
        try {
            JSONObject jsonobject = new JSONObject(webSocketMessage.getPayload().toString());
            Message message = new Message(jsonobject.toString());
            System.out.println(jsonobject.toString());
            System.out.println(message + ": from" + webSocketSession.getAttributes().get(MessageKey.KEY_WEBSOCKET_USERNAME) + "The news");
            if(message.getName() ! =null&& message.getCommand() ! =null) {
                switch (message.getCommand()) {
                        // There are new people joining the room
                    case MessageKey.ENTER_COMMAND:
                        sendMessageToRoomUsers(message.getRoomId(), new TextMessage("【" + getNameFromSession(webSocketSession) + "Welcome to the room!"));
                        break;
                        // Chat messages
                    case MessageKey.MESSAGE_COMMAND:
                        if (message.getName().equals("all")) {
                            sendMessageToRoomUsers(message.getRoomId(), new TextMessage(getNameFromSession(webSocketSession) +
                                    "Say:" + message.getInfo()
                            ));
                        } else {
                            sendMessageToUser(message.getRoomId(), message.getName(), new TextMessage(getNameFromSession(webSocketSession) +
                                    "Whisper to you:" + message.getInfo()));
                        }
                        break;
                        // Someone left the room
                    case MessageKey.LEAVE_COMMAND:
                        sendMessageToRoomUsers(message.getRoomId(), new TextMessage("【" + getNameFromSession(webSocketSession) + "】 Left the room, welcome to come again."));
                        break;
                        default:
                            break; }}}catch(Exception e) { e.printStackTrace(); }}/** * Sends the message to the specified user */
    public boolean sendMessageToUser(String roomId, String name, TextMessage message) {
        if (roomId == null || name == null) return false;
        if (sUserMap.get(roomId) == null) return false;
        WebSocketSession session = sUserMap.get(roomId).get(name);
        if(! session.isOpen())return false;
        try {
            session.sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /** * Broadcasts a message to all users in a room */
    public boolean sendMessageToRoomUsers(String roomId, TextMessage message) {
        if (roomId == null) return false;
        if (sUserMap.get(roomId) == null) return false;
        boolean allSendSuccess = true;
        Collection<WebSocketSession> sessions = sUserMap.get(roomId).values();
        for (WebSocketSession session : sessions) {
            try {
                if(session.isOpen()) { session.sendMessage(message); }}catch (IOException e) {
                e.printStackTrace();
                allSendSuccess = false; }}return allSendSuccess;
    }

    // Exit the room
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) {
        System.out.println("Connection closed:" + closeStatus);
        Map<String, WebSocketSession> map = sUserMap.get(getRoomIdFromSession(webSocketSession));
        if(map ! =null) { map.remove(getNameFromSession(webSocketSession)); }}}Copy the code

Key words:

  • usesUserMapThis static variable holds user information. Corresponding to our diagram above.
  • implementationafterConnectionEstablishedMethod, when the user enters the room successfully, save the user information toMapAnd callsendMessageToRoomUsersBroadcast newbie information.
  • implementationhandleMessageMethod, processing user join, leave and send messages three types of messages.
  • implementationafterConnectionClosedMethod to handle information destruction when the user leaves the room. fromMapTo clear the user.
  • implementationsendMessageToUser,sendMessageToRoomUsersTwo methods for sending messages to clients. Directly throughSessionCan send structured data to the client.sendMessageToUserImplement point-to-point messaging,sendMessageToRoomUsersThe transmission of broadcast messages is realized.

Client-side implementation

On the client side, we will use the WebSocket JS interface provided by HTML5.

<html>
    <script type="text/javascript">
        function ToggleConnectionClicked() {
            if (SocketCreated && (ws.readyState == 0 || ws.readyState == 1)) {
                lockOn("Leave the chat room...");
                SocketCreated = false;
                isUserloggedout = true;
                var msg = JSON.stringify({'command':'leave'.'roomId':groom , 'name': gname,
                    'info':'Leave the room'});
                ws.send(msg);
                ws.close();
            } else if(document.getElementById("roomId").value == "Please enter your room number!") {
                Log("Please enter your room number!");
            } else {
                lockOn("Enter a chat room...");
                Log("Ready to connect to chat server...");
                groom = document.getElementById("roomId").value;
                gname = document.getElementById("txtName").value;
                try {
                    if ("WebSocket" in window) {
                        ws = new WebSocket(
                            'ws://localhost:8080/webSocket/INFO={"command":"enter","name":"'+ gname + '","roomId":"' + groom + '"}');
                    }
                    else if("MozWebSocket" in window) {
                        ws = new MozWebSocket(
                            'ws://localhost:8080/webSocket/INFO={"command":"enter","name":"'+ gname + '","roomId":"' + groom + '"}');
                    }
                    SocketCreated = true;
                    isUserloggedout = false;
                } catch (ex) {
                    Log(ex, "ERROR");
                    return;
                }
                document.getElementById("ToggleConnection").innerHTML = "Disconnect"; ws.onopen = WSonOpen; ws.onmessage = WSonMessage; ws.onclose = WSonClose; ws.onerror = WSonError; }};function WSonOpen() {
            lockOff();
            Log("Connection established."."OK");
            $("#SendDataContainer").show();
            var msg = JSON.stringify({'command':'enter'.'roomId':groom , 'name': "all".'info': gname + "Join the chat room"})
            ws.send(msg);
        };
</html>
Copy the code

Key words:

  • When initiating a server connection, note the address information:'ws://localhost:8080/webSocket/INFO={"command":"enter","name":"'+ gname + '","roomId":"' + groom + '"}'Here we areINFOAfter receiving the user’s personal information, the server can mark the session based on this information.
  • Once the connection is established, a join message is sent to the rest of the room. throughws.send()Method implementation.

That’s the end of the code section, and I’m not going to stack any more code. For more details, see the Github address below.

This paper summarizes

Through a relatively complete example of a web chat room, we introduce a few details of our own use of WebSocket:

  • The server wants to do something during the connection establishment, or handshake, phaseHandshakeInterceptor.
  • The server wants to process the messages sent by the client after establishing the connectionWebSocketHandler.
  • Through the serverWebSocketSessionCan send messages to the client through the user andSessionTo achieve the corresponding relationship.

For those of you who want to understand more, go deep into the code. Space is limited, and adding a lot of code to the article itself is not easy to read. So it’s better to actually understand the code.

The code covered in this article

Complete code implementation – small web chat room

Welcome to continue to pay attention to the original, like don’t forget to collect attention, code word is really too tired, your encouragement is my motivation!

Xiaoming produced, must be a boutique

Welcome to pay attention to xNPE technology forum, more original dry goods daily push.