快捷搜索:  汽车  科技

聊天服务器websocket怎么配置(手把手教你使用WebSocket搭建多人在线聊天室)

聊天服务器websocket怎么配置(手把手教你使用WebSocket搭建多人在线聊天室)新建工程https://github.com/qqxx6661/springboot-websocket-demo此外,在下一篇文章中,我们将做到:WebSocket多人在线聊天室本文工程源代码:

聊天服务器websocket怎么配置(手把手教你使用WebSocket搭建多人在线聊天室)(1)

前言

本文中搭建了一个简易的多人聊天室,使用了WebSocket的基础特性。

源代码来自老外的一篇好文:

https://www.callicoder.com/spring-boot-websocket-chat-example/

本文内容摘要:

  • 初步理解WebSocket的前后端交互逻辑
  • 手把手使用 SpringBoot WebSocket 搭建一个多人聊天室Demo
  • 代码源码及其解释
  • 前端展示页面

聊天服务器websocket怎么配置(手把手教你使用WebSocket搭建多人在线聊天室)(2)

此外,在下一篇文章中,我们将做到:

  • 对该WebSocket聊天室进行分布式改造,同时部署多台机器来作为集群,支撑高并发。
  • 保存用户session,并且在集群上实现session同步,比如实时展示当前在线的用户!
正文

WebSocket多人在线聊天室

本文工程源代码:

https://github.com/qqxx6661/springboot-websocket-demo

新建工程

我们新建一个SpringBoot2的项目工程,在默认依赖中,添加websocket依赖:

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

WebSocket 配置

我们先来设置websocket的配置,新建config文件夹,在里面新建类WebSocketConfig

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"); } }

代码解释:

@EnableWebSocketMessageBroker用于启用我们的WebSocket服务器。

我们实现了WebSocketMessageBrokerConfigurer接口,并实现了其中的方法。

在第一种方法中,我们注册一个websocket端点,客户端将使用它连接到我们的websocket服务器。

withSockJS()是用来为不支持websocket的浏览器启用后备选项,使用了SockJS。

方法名中的STOMP是来自Spring框架STOMP实现。 STOMP代表简单文本导向的消息传递协议。它是一种消息传递协议,用于定义数据交换的格式和规则。为啥我们需要这个东西?因为WebSocket只是一种通信协议。它没有定义诸如以下内容:如何仅向订阅特定主题的用户发送消息,或者如何向特定用户发送消息。我们需要STOMP来实现这些功能。

在configureMessageBroker方法中,我们配置一个消息代理,用于将消息从一个客户端路由到另一个客户端。

第一行定义了以“/app”开头的消息应该路由到消息处理方法(之后会定义这个方法)。

第二行定义了以“/topic”开头的消息应该路由到消息代理。消息代理向订阅特定主题的所有连接客户端广播消息。

在上面的示例中,我们使用的是内存中的消息代理。

之后也可以使用RabbitMQ或ActiveMQ等其他消息代理。

创建 ChatMessage 实体

ChatMessage用来在客户端和服务端中交互

我们新建model文件夹,创建实体类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; } }

实体中,有三个字段:

  • type:消息类型
  • content:消息内容
  • sender:发送者

类型有三种:

  • CHAT: 消息
  • JOIN:加入
  • LEAVE:离开

创建Controller来接收和发送消息

创建controller文件夹,在controller文件夹添加类ChatController

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()); return chatMessage; } }

代码解释:

我们在websocket配置中,从目的地以/app开头的客户端发送的所有消息都将路由到这些使用@MessageMapping注释的消息处理方法。

例如,具有目标/app/chat.sendMessage的消息将路由到sendMessage()方法,并且具有目标/app/chat.addUser的消息将路由到addUser()方法

添加WebSocket事件监听

完成了上述代码后,我们还需要对socket的连接和断连事件进行监听,这样我们才能广播用户进来和出去等操作。

创建listener文件夹,新建WebSocketEventListener类

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); } } }

代码解释:

我们已经在ChatController中定义的addUser()方法中广播了用户加入事件。因此,我们不需要在SessionConnected事件中执行任何操作。

在SessionDisconnect事件中,编写代码用来从websocket会话中提取用户名,并向所有连接的客户端广播用户离开事件。

创建前端聊天室页面

我们在src/main/resources文件下创建前端文件,结构类似这样:

static └── css └── main.css └── js └── main.js └── index.html

1. HTML文件 index.html

HTML文件包含用于显示聊天消息的用户界面。 它包括sockjs和stomp 两个js库。

SockJS是一个WebSocket客户端,它尝试使用本机WebSockets,并为不支持WebSocket的旧浏览器提供支持。 STOMP JS是javascript的stomp客户端。

笔者在文件里使用了国内的CDN源

<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width initial-scale=1.0 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</h2> </noscript> <div id="username-page"> <div class="username-page-container"> <h1 class="title">Type your username</h1> <form id="usernameForm" name="usernameForm"> <div class="form-group"> <input type="text" id="name" placeholder="Username" autocomplete="off" class="form-control" /> </div> <div class="form-group"> <button type="submit" class="accent username-submit">Start Chatting</button> </div> </form> </div> </div> <div id="chat-page" class="hidden"> <div class="chat-container"> <div class="chat-header"> <h2>Spring WebSocket Chat Demo</h2> </div> <div class="connecting"> Connecting... </div> <ul id="messageArea"> </ul> <form id="messageForm" name="messageForm"> <div class="form-group"> <div class="input-group clearfix"> <input type="text" id="message" placeholder="Type a message..." autocomplete="off" class="form-control"/> <button type="submit" class="primary">Send</button> </div> </div> </form> </div> </div> <script src="https://img.aigexing.comhttps://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script> <script src="https://img.aigexing.comhttps://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script> <script src="https://img.aigexing.com/js/main.js"></script> </body> </html>

2. JavaScript main.js

添加连接到websocket端点以及发送和接收消息所需的javascript。

'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)

代码解释:

connect()函数使用SockJS和stomp客户端连接到我们在Spring Boot中配置的/ws端点。

成功连接后,客户端订阅/topic/public,并通过向/app/chat.addUser目的地发送消息将该用户的名称告知服务器。

stompClient.subscribe()函数采用一种回调方法,只要消息到达订阅主题,就会调用该方法。

其它的代码用于在屏幕上显示和格式化消息。

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; 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.6rem 1rem; border-radius: 2px; transition: all 0.2s 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 { line-height: 1.5rem; 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.1em; } }

整个项目结构如下:

聊天服务器websocket怎么配置(手把手教你使用WebSocket搭建多人在线聊天室)(3)

启动

启动SpringBoot项目

效果入下:

聊天服务器websocket怎么配置(手把手教你使用WebSocket搭建多人在线聊天室)(4)

聊天服务器websocket怎么配置(手把手教你使用WebSocket搭建多人在线聊天室)(5)

补充:使用RabbitMQ代替内存作为消息代理

添加依赖:

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

然后将WebSocketConfig类中configureMessageBroker方法改为使用RabbitMq,完成!

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"); }

如此一来,便可以通过RabbitMq进行消息的订阅。

总结

我们在本文中搭建了一个简易的多人聊天室,使用了WebSocket的特性。

本文工程源代码:

https://github.com/qqxx6661/springboot-websocket-demo

在下一篇文章中,我们将做到:

  • WebSocket进行分布式改造,同时部署多台机器来作为集群,支撑高并发。
  • 保存用户session,并且在集群上实现session同步,比如实时展示当前在线的用户!

下一篇文章过两天奉上,也可以关注我的公众号:后端技术漫谈,就不会忘记了看,哈哈哈哈哈。

关注我

我目前是一名后端开发工程师。主要关注后端开发,数据安全,爬虫,边缘计算等方向。

yangzd1102

Github:@qqxx6661

个人博客:

  • CSDN:@Rude3Knife
  • 知乎:@Zhendong
  • 简书:@蛮三刀把刀
  • 掘金:@蛮三刀把刀

原创博客主要内容

  • Java知识点复习全手册
  • Leetcode算法题解析
  • 剑指offer算法题解析
  • SpringCloud菜鸟入门实战系列
  • SpringBoot菜鸟入门实战系列
  • 爬虫相关技术文章
  • 后端开发相关技术文章

个人公众号:后端技术漫谈

聊天服务器websocket怎么配置(手把手教你使用WebSocket搭建多人在线聊天室)(6)

个人公众号:后端技术漫谈

如果文章对你有帮助,不妨收藏起来并转发给您的朋友们~

猜您喜欢: