preface

This article set up a simple multi-person chat room, using the basic features of WebSocket.

Source code from a foreign article:

www.callicoder.com/spring-boot…

Abstract:

  • Preliminary understanding of WebSocket front-end and back-end interaction logic
  • Use SpringBoot + WebSocket to build a multiplayer chat room Demo
  • The source code and its interpretation
  • Front-end display page

In addition, in the next article we will do:

  • The WebSocket chat room is distributed, and multiple machines are deployed as a cluster to support high concurrency.
  • Save user sessions and synchronize sessions across the cluster, such as displaying current online users in real time!

The body of the

WebSocket Multiplayer online chat room

This article project source code:

Github.com/qqxx6661/sp…

New construction

We will create a new project for SpringBoot2 and add the websocket dependency to the default dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
Copy the code

WebSocket configuration

Let’s first set up the webSocket configuration, create a config folder, and create the WebSocketConfig class in it

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.*;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app");
        registry.enableSimpleBroker("/topic"); }}Copy the code

Code explanation:

@ EnableWebSocketMessageBroker for enabling our WebSocket server.

We implemented WebSocketMessageBrokerConfigurer interface, and implements the method of them.

In the first approach, we register a WebSocket endpoint that the client will use to connect to our WebSocket server.

WithSockJS () is used to enable the fallback option for browsers that do not support Websocket, using SockJS.

The STOMP in the method name comes from the Spring framework STOMP implementation. STOMP stands for simple text-oriented messaging protocol. It is a messaging protocol that defines the format and rules for data exchange. Why do we need this? Because WebSocket is just a communication protocol. It does not define things like how to send messages only to users who subscribe to a particular topic, or how to send messages to a particular user. We need STOMP to implement these functions.

In the configureMessageBroker method, we configure a message broker that routes messages from one client to another.

The first line defines that messages starting with “/app” should be routed to the message handling method (which will be defined later).

The second line defines that messages starting with “/topic” should be routed to the message broker. The message broker broadcasts messages to all connected clients that subscribe to a particular topic.

In the example above, we used an in-memory message broker.

Other message brokers such as RabbitMQ or ActiveMQ can then be used.

Create the ChatMessage entity

The ChatMessage is used to communicate between the client and server

Let’s create a new Model folder and create the entity class ChatMessage.

public class ChatMessage {
    private MessageType type;
    private String content;
    private String sender;

    public enum MessageType {
        CHAT,
        JOIN,
        LEAVE
    }

    public MessageType getType() {
        return type;
    }

    public void setType(MessageType type) {
        this.type = type;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) { this.sender = sender; }}Copy the code

In the entity, there are three fields:

  • Type: indicates the message type
  • Content: indicates the message content
  • Sender: indicates the sender

There are three types:

  • CHAT: message
  • The JOIN: JOIN
  • LEAVE, LEAVE

Create controllers to receive and send messages

Create the Controller folder and add the ChatController class to the Controller folder

import com.example.websocketdemo.model.ChatMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;

@Controller
public class ChatController {

    @MessageMapping("/chat.sendMessage")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
        return chatMessage;
    }

    @MessageMapping("/chat.addUser")
    @SendTo("/topic/public")
    public ChatMessage addUser(@Payload ChatMessage chatMessage, 
                               SimpMessageHeaderAccessor headerAccessor) {
        // Add username in web socket session
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        returnchatMessage; }}Copy the code

Code explanation:

In our WebSocket configuration, all messages sent from clients whose destinations start with /app will be routed to these MessageMapping annotation message handling methods.

For example, a message with a destination /app/chat.sendMessage will be routed to the sendMessage () method, and a message with a destination /app/ chat.adduser will be routed to the addUser () method

Add WebSocket event listener

After completing the above code, we also need to listen for socket connection and disconnection events, so that we can broadcast users in and out of the operation.

Create a Listener folder and create a WebSocketEventListener class

import com.example.websocketdemo.model.ChatMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;

@Component
public class WebSocketEventListener {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketEventListener.class);

    @Autowired
    private SimpMessageSendingOperations messagingTemplate;

    @EventListener
    public void handleWebSocketConnectListener(SessionConnectedEvent event) {
        logger.info("Received a new web socket connection");
    }

    @EventListener
    public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());

        String username = (String) headerAccessor.getSessionAttributes().get("username");
        if(username ! = null) { logger.info("User Disconnected : " + username);

            ChatMessage chatMessage = new ChatMessage();
            chatMessage.setType(ChatMessage.MessageType.LEAVE);
            chatMessage.setSender(username);

            messagingTemplate.convertAndSend("/topic/public", chatMessage); }}}Copy the code

Code explanation:

We already broadcast the user join event in the addUser () method defined in the ChatController. Therefore, we do not need to perform any operations in the SessionConnected event.

In the SessionDisconnect event, code is written to extract the user name from the Websocket session and broadcast the user exit event to all connected clients.

Create a front-end chat room page

We create a front-end file under SRC /main/resources with a structure similar to this:

Static └ ─ ─ CSS └ ─ ─. Main CSS └ ─ ─ js └ ─ ─ main. Js └ ─ ─ index. The HTMLCopy the code

1. HTML file index. HTML

The HTML file contains the user interface for displaying chat messages. It includes two JS libraries, SOckJS and STOMp.

SockJS is a WebSocket client that attempts to use native WebSockets and provides support for older browsers that do not support WebSockets. STOMP JS is the STOMP client for javascript.

The author used domestic CDN sources in the document

<! DOCTYPE html> <html> <head> <meta name="viewport" content="Width = device - width, initial - scale = 1.0, the minimum - scale = 1.0">
      <title>Spring Boot WebSocket Chat Application</title>
      <link rel="stylesheet" href="/css/main.css" />
  </head>
  <body>
    <noscript>
      <h2>Sorry! Your browser doesn't support Javascript  
      

Type your username

Copy the code

2. JavaScript main.js

Add the javascript needed to connect to the WebSocket endpoint and send and receive messages.

'use strict';

var usernamePage = document.querySelector('#username-page');
var chatPage = document.querySelector('#chat-page');
var usernameForm = document.querySelector('#usernameForm');
var messageForm = document.querySelector('#messageForm');
var messageInput = document.querySelector('#message');
var messageArea = document.querySelector('#messageArea');
var connectingElement = document.querySelector('.connecting');

var stompClient = null;
var username = null;

var colors = [
    '#2196F3'.'#32c787'.'#00BCD4'.'#ff5652'.'#ffc107'.'#ff85af'.'#FF9800'.'#39bbb0'
];

function connect(event) {
    username = document.querySelector('#name').value.trim();

    if(username) {
        usernamePage.classList.add('hidden');
        chatPage.classList.remove('hidden');

        var socket = new SockJS('/ws');
        stompClient = Stomp.over(socket);

        stompClient.connect({}, onConnected, onError);
    }
    event.preventDefault();
}


function onConnected() {
    // Subscribe to the Public Topic
    stompClient.subscribe('/topic/public', onMessageReceived);

    // Tell your username to the server
    stompClient.send("/app/chat.addUser",
        {},
        JSON.stringify({sender: username, type: 'JOIN'})
    )

    connectingElement.classList.add('hidden');
}


function onError(error) {
    connectingElement.textContent = 'Could not connect to WebSocket server. Please refresh this page to try again! ';
    connectingElement.style.color = 'red';
}


function sendMessage(event) {
    var messageContent = messageInput.value.trim();
    if(messageContent && stompClient) {
        var chatMessage = {
            sender: username,
            content: messageInput.value,
            type: 'CHAT'
        };
        stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
        messageInput.value = ' ';
    }
    event.preventDefault();
}


function onMessageReceived(payload) {
    var message = JSON.parse(payload.body);

    var messageElement = document.createElement('li');

    if(message.type === 'JOIN') {
        messageElement.classList.add('event-message');
        message.content = message.sender + ' joined! ';
    } else if (message.type === 'LEAVE') {
        messageElement.classList.add('event-message');
        message.content = message.sender + ' left! ';
    } else {
        messageElement.classList.add('chat-message');

        var avatarElement = document.createElement('i');
        var avatarText = document.createTextNode(message.sender[0]);
        avatarElement.appendChild(avatarText);
        avatarElement.style['background-color'] = getAvatarColor(message.sender);

        messageElement.appendChild(avatarElement);

        var usernameElement = document.createElement('span');
        var usernameText = document.createTextNode(message.sender);
        usernameElement.appendChild(usernameText);
        messageElement.appendChild(usernameElement);
    }

    var textElement = document.createElement('p');
    var messageText = document.createTextNode(message.content);
    textElement.appendChild(messageText);

    messageElement.appendChild(textElement);

    messageArea.appendChild(messageElement);
    messageArea.scrollTop = messageArea.scrollHeight;
}


function getAvatarColor(messageSender) {
    var hash = 0;
    for (var i = 0; i < messageSender.length; i++) {
        hash = 31 * hash + messageSender.charCodeAt(i);
    }
    var index = Math.abs(hash % colors.length);
    return colors[index];
}

usernameForm.addEventListener('submit', connect, true)
messageForm.addEventListener('submit', sendMessage, true)
Copy the code

Code explanation:

The connect () function uses SockJS and stomp clients to connect to the/WS endpoint we configured in Spring Boot.

After a successful connection, the client subscribes to /topic/public and informs the server of the user’s name by sending a message to the /app/ chat.adduser destination.

The stompClient.subscribe() function takes a callback method that is called whenever a message arrives at the subscribed topic.

Other code is used to display and format messages on the screen.

3. CSS main.css

* {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}

html,body {
    height: 100%;
    overflow: hidden;
}

body {
    margin: 0;
    padding: 0;
    font-weight: 400;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 1rem; The line - height: 1.58; color:# 333;
    background-color: #f4f4f4;
    height: 100%;
}

body:before {
    height: 50%;
    width: 100%;
    position: absolute;
    top: 0;
    left: 0;
    background: #128ff2;
    content: "";
    z-index: 0;
}

.clearfix:after {
    display: block;
    content: "";
    clear: both;
}

.hidden {
    display: none;
}

.form-control {
    width: 100%;
    min-height: 38px;
    font-size: 15px;
    border: 1px solid #c8c8c8;} .form-group { margin-bottom: 15px; } input { padding-left: 10px; outline: none; } h1, h2, h3, h4, h5, h6 { margin-top: 20px; margin-bottom: 20px; } h1 {font-size: 1.7em; } a { color:#128ff2;} button { box-shadow: none; border: 1px solid transparent; font-size: 14px; outline: none; line-height: 100%; white-space: nowrap; vertical-align: middle; Padding: 0.6 rem 1 rem; border-radius: 2px; The transition: all 0.2 s ease - in-out; cursor: pointer; min-height: 38px; } button.default { background-color:#e8e8e8;
    color: # 333;Box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12); } button.primary { background-color:#128ff2;Box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12); color:#fff;
}

button.accent {
    background-color: #ff4743;Box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12); color:#fff;
}

#username-page {
    text-align: center;
}

.username-page-container {
    background: #fff;Box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27); border-radius: 2px; width: 100%; max-width: 500px; display: inline-block; margin-top: 42px; vertical-align: middle; position: relative; padding: 35px 55px 35px; min-height: 250px; position: absolute; top: 50%; left: 0; right: 0; margin: 0 auto; margin-top: -160px; } .username-page-container .username-submit { margin-top: 10px; }#chat-page {
    position: relative;
    height: 100%;
}

.chat-container {
    max-width: 700px;
    margin-left: auto;
    margin-right: auto;
    background-color: #fff;Box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27); margin-top: 30px; height: calc(100% - 60px); max-height: 600px; position: relative; }#chat-page ul {
    list-style-type: none;
    background-color: #FFF;
    margin: 0;
    overflow: auto;
    overflow-y: scroll;
    padding: 0 20px 0px 20px;
    height: calc(100% - 150px);
}

#chat-page #messageForm {
    padding: 20px;
}

#chat-page ul li {The line - height: 1.5 rem; padding: 10px 20px; margin: 0; border-bottom: 1px solid#f4f4f4;
}

#chat-page ul li p {
    margin: 0;
}

#chat-page .event-message {
    width: 100%;
    text-align: center;
    clear: both;
}

#chat-page .event-message p {
    color: # 777;
    font-size: 14px;
    word-wrap: break-word;
}

#chat-page .chat-message {
    padding-left: 68px;
    position: relative;
}

#chat-page .chat-message i {
    position: absolute;
    width: 42px;
    height: 42px;
    overflow: hidden;
    left: 10px;
    display: inline-block;
    vertical-align: middle;
    font-size: 18px;
    line-height: 42px;
    color: #fff;
    text-align: center;
    border-radius: 50%;
    font-style: normal;
    text-transform: uppercase;
}

#chat-page .chat-message span {
    color: # 333;
    font-weight: 600;
}

#chat-page .chat-message p {
    color: #43464b;
}

#messageForm .input-group input {
    float: left;
    width: calc(100% - 85px);
}

#messageForm .input-group button {
    float: left;
    width: 80px;
    height: 38px;
    margin-left: 5px;
}

.chat-header {
    text-align: center;
    padding: 15px;
    border-bottom: 1px solid #ececec;
}

.chat-header h2 {
    margin: 0;
    font-weight: 500;
}

.connecting {
    padding-top: 5px;
    text-align: center;
    color: # 777;
    position: absolute;
    top: 65px;
    width: 100%;
}


@media screen and (max-width: 730px) {

    .chat-container {
        margin-left: 10px;
        margin-right: 10px;
        margin-top: 10px;
    }
}

@media screen and (max-width: 480px) {
    .chat-container {
        height: calc(100% - 30px);
    }

    .username-page-container {
        width: auto;
        margin-left: 15px;
        margin-right: 15px;
        padding: 25px;
    }

    #chat-page ul {
        height: calc(100% - 120px);
    }

    #messageForm .input-group button {
        width: 65px;
    }

    #messageForm .input-group input {width: calc(100% - 70px); } .chat-header { padding: 10px; } .connecting { top: 60px; }. Chat-header h2 {font-size: 1.3em; }}Copy the code

The overall project structure is as follows:

Start the

Start the SpringBoot project

Effect:

Added: Use RabbitMQ instead of memory as the message broker

Add dependencies:

<! -- RabbitMQ Starter Dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <! -- Following additional dependency is requiredfor Full Featured STOMP Broker Relay -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-reactor-netty</artifactId>
</dependency>
Copy the code

Then change the configureMessageBroker method in the WebSocketConfig class to use RabbitMq. Complete!

public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.setApplicationDestinationPrefixes("/app");

    // Use this for enabling a Full featured broker like RabbitMQ
    registry.enableStompBrokerRelay("/topic")
            .setRelayHost("localhost")
            .setRelayPort(61613)
            .setClientLogin("guest")
            .setClientPasscode("guest");
}
Copy the code

This way, messages can be subscribed to via RabbitMq.

conclusion

In this article, we set up a simple multiplayer chat room, using WebSocket features.

This article project source code:

Github.com/qqxx6661/sp…

In the next article, we will do:

  • WebSocket implements distributed transformation, deploying multiple machines as a cluster at the same time to support high concurrency.
  • Save user sessions and synchronize sessions across the cluster, such as displaying current online users in real time!

Next post will be posted in two days, you can also follow my public account: Rude3Knife, you will not forget to read, hahahaha.

Pay attention to my

I am currently working as a back-end development engineer. Focus on back-end development, data security, crawler, edge computing, etc.

WeChat: yangzd1102

Making: @ qqxx6661

Personal Blog:

  • CSDN: @ Rude3Knife
  • Zhihu: @ Zhendong
  • Jane: @pretty three knives a knife
  • Nuggets: @ pretty three knife knife

Original blog main content

  • Java knowledge points review the full manual
  • Leetcode algorithm parsing
  • Sword point offer algorithm analysis
  • SpringCloud rookie combat series
  • SpringBoot rookie combat series
  • Crawler related technical articles
  • Technical articles related to back-end development

Personal public account: Rude3Knife

If this article is helpful to you, save it and send it to your friends