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
Spring WebSocket Chat Demo
Connecting...
< / div > < / div > < script SRC = "https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js" > < / script > < script SRC = "https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js" > < / script > < script SRC = "/ js/main js" > < / script > < / body > < / HTML >
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