WebSocket(三) -- 使用websocket+stomp实现群聊功能

这篇具有很好参考价值的文章主要介绍了WebSocket(三) -- 使用websocket+stomp实现群聊功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 前言:

SpringBoot+websocket的实现其实不难,你可以使用原生的实现,也就是websocket本身的OnOpen、OnClosed等等这样的注解来实现,以及对WebSocketHandler的实现,类似于netty的那种使用方式,而且原生的还提供了对websocket的监听,服务端能更好的控制及统计(即上文实现的方式)。

但是,真实项目中还是使用Stomp实现的居多,因为独立服务更方便,便于后期搭建集群环境做横向扩展,且内置的方法也很简单,既然如此,我们还是以主流实现方式为准来学习吧。

2. stomp

当直接使用WebSocket时(或SockJS)就很类似于使用TCP套接字来编写Web应用。因为没有高层级的线路协议(wire protocol),因此就需要我们定义应用之间所发送消息的语义,还需要确保连接的两端都能遵循这些语义。

就像HTTP在TCP套接字之上添加了请求-响应模型层一样,STOMP在WebSocket之上提供了一个基于帧的线路格式(frame-based wire format)层,用来定义消息的语义。

与HTTP请求和响应类似,STOMP帧由命令、一个或多个头信息以及负载所组成。例如,如下就是发送数据的一个STOMP帧:

>>> SEND
transaction:tx-0
destination:/app/marco
content-length:20

{"message":"Marco!"}

3. 需求:

  1. 登陆:
    WebSocket(三) -- 使用websocket+stomp实现群聊功能

  2. 1号用户加入,发送消息:
    WebSocket(三) -- 使用websocket+stomp实现群聊功能

  3. 2号:
    WebSocket(三) -- 使用websocket+stomp实现群聊功能

  4. 3号:
    WebSocket(三) -- 使用websocket+stomp实现群聊功能

  5. 退出:
    WebSocket(三) -- 使用websocket+stomp实现群聊功能

  6. 要求使用stomp完成

4. websocket配置:

// websocket核心配置类
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /**
     * 注册stomp端点
     * @param registry stomp端点注册对象
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 配置stomp端点地址
        registry.addEndpoint("/ws").withSockJS();
    }

    /**
     * 配置消息代理
     * @param registry 消息代理注册对象
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 定义客户端访问服务端消息接口时的前缀
        registry.setApplicationDestinationPrefixes("/app");

        // 配置服务端推送消息给客户端的代理路径
        registry.enableSimpleBroker("/topic");

        // 定义点对点推送时的前缀为/queue
        registry.setUserDestinationPrefix("/queue");


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

其中:

  1. @EnableWebSocketMessageBroker:用于开启stomp协议,这样就能支持@MessageMapping注解,类似于@requestMapping一样,同时前端可以使用Stomp客户端进行通讯;
  2. WebSocketMessageBrokerConfigurer接口:实现了其中的两个方法:
    1. registerStompEndpoints实现:
      1. 注册一个websocket端点,客户端将使用它连接到我们的websocket服务器。
      2. withSockJS()是用来为不支持websocket的浏览器启用后备选项,使用了SockJS。
      3. 方法名中的STOMP是来自Spring框架STOMP实现。 STOMP代表简单文本导向的消息传递协议。它是一种消息传递协议,用于定义数据交换的格式和规则。为啥我们需要这个东西?因为WebSocket只是一种通信协议。它没有定义诸如以下内容:如何仅向订阅特定主题的用户发送消息,或者如何向特定用户发送消息。我们需要STOMP来实现这些功能
    2. configureMessageBroker实现:主要用来设置客户端订阅消息的路径(可以多个)、点对点订阅路径前缀的设置、访问服务端@MessageMapping接口的前缀路径、心跳设置等;
      1. 第一行定义了以“/app”开头的消息应该路由到消息处理方法(之后会定义这个方法)。
      2. 第二行定义了以“/topic”开头的消息应该路由到消息代理。消息代理向订阅特定主题的所有连接客户端广播消息

5. model对象:

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

    @Override
    public String toString() {
        return "ChatMessage{" +
                "type=" + type +
                ", content='" + content + '\'' +
                ", sender='" + sender + '\'' +
                '}';
    }
}

6. controller接收和发送消息:

/**
 * 发送广播消息
 * -- 说明:
 *       1)、@MessageMapping注解对应客户端的stomp.send('url');
 *       2)、用法一:要么配合@SendTo("转发的订阅路径"),去掉messagingTemplate,同时return msg来使用,return msg会去找@SendTo注解的路径;
 *       3)、用法二:要么设置成void,使用messagingTemplate来控制转发的订阅路径,且不能return msg
 */
@Controller
@Slf4j
public class ChatController {

    // 实现向浏览器发送消息的功能
    private final SimpMessagingTemplate messagingTemplate;

    public ChatController(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    // 用法一:
    @MessageMapping("/send")
    public void sendAll(@RequestParam String msg) {

        log.info("[发送消息]>>>> msg: {}", msg);

        // 发送消息给客户端
        // 第一个参数是浏览器中订阅消息的地址,第二个参数是消息本身
        messagingTemplate.convertAndSend("/topic/public", msg);
    }

    @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;
    }
}
  1. 消息接口使用@MessageMapping注解,前面讲的配置类@EnableWebSocketMessageBroker注解开启后才能使用这个
  2. 我们在websocket配置中,以/app开头的客户端发送的所有消息都将路由到这些使用@MessageMapping注释的消息处理方法
    例如,具有目标/app/chat.sendMessage的消息将路由到sendMessage()方法,并且具有目标/app/chat.addUser的消息将路由到addUser()方法
  3. 这里稍微提一下,真正线上项目都是把websocket服务做成单独的网关形式,提供rest接口给其他服务调用,达到共用的目的,本项目因为不涉及任何数据库交互,所以直接用@MessageMapping注解,后续完整IM项目接入具体业务后会做一个独立的websocket服务
  4. 发送消息的两个用法:
    1. 用法一:要么配合@SendTo(“转发的订阅路径”),去掉messagingTemplate,同时return msg来使用,return msg会去找@SendTo注解的路径;
    2. 用法二:要么设置成void,使用messagingTemplate来控制转发的订阅路径,且不能return msg

7. 添加websocket监听事件

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

@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会话中提取用户名,并向所有连接的客户端广播用户离开事件。

8. 主要前端代码:

'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()函数采用一种回调方法,只要消息到达订阅主题,就会调用该方法。
  • 其它的代码用于在屏幕上显示和格式化消息。

参考:
https://blog.csdn.net/xiangyangsanren/article/details/123970860
https://blog.csdn.net/qqxx6661/article/details/98883166
https://blog.csdn.net/qq_53021672/article/details/124313430
https://blog.csdn.net/qq_35387940/article/details/108276136(最全的)文章来源地址https://www.toymoban.com/news/detail-402719.html

到了这里,关于WebSocket(三) -- 使用websocket+stomp实现群聊功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • flutter开发实战-长链接WebSocket使用stomp协议stomp_dart_client

    flutter开发实战-长链接WebSocket使用stomp协议stomp_dart_client 在app中经常会使用长连接进行消息通信,这里记录一下基于websocket使用stomp协议的使用。 1.1 stomp介绍 stomp,Streaming Text Orientated Message Protocol,是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件

    2024年02月13日
    浏览(46)
  • python的WebSocket编程详解,案例群聊系统实现

    1.1为什么要用websocket 如果有需求要实现服务端向客户端主动推送消息时(比如聊天室,群聊室)有哪几种方案 轮训:让浏览器每隔两秒发送一次请求,缺点:有延时,请求太多网站压力大; 长轮训:客户端向服务端发送请求,服务端最多夯20秒,一旦有新的数据就立即返回

    2024年02月02日
    浏览(34)
  • 基于Springboot+WebSocket+Netty实现在线聊天、群聊系统

    此文主要实现在好友添加、建群、聊天对话、群聊功能,使用Java作为后端语言进行支持,界面友好,开发简单。 2.1、下载安装IntelliJ IDEA(后端语言开发工具),Mysql数据库,微信Web开发者工具。 1.创建maven project 先创建一个名为SpringBootDemo的项目,选择【New Project】 然后在弹出

    2024年02月14日
    浏览(41)
  • Springboot 整合 WebSocket ,使用STOMP协议 ,前后端整合实战 (一)(1)

    server: port: 9908 3.WebSocketConfig.java import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springfra

    2024年04月25日
    浏览(44)
  • Springboot 整合 WebSocket ,使用STOMP协议+Redis 解决负载场景问题

    ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); template.setValueSerializer(jacksonSeial); template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template

    2024年04月14日
    浏览(52)
  • SpringBoot与webSocket实现在线聊天室——实现私聊+群聊+聊天记录保存

    引用参考:原文章地址:https://blog.csdn.net/qq_41463655/article/details/92410518 在此基础上实现对聊天记录的保存。 代码地址:链接:https://pan.baidu.com/s/1IJFZDa4S_DF08773sKJWeA 提取码:jkui 思路:新建一个实体类用于保存聊天记录,在消息发送时,设置对象的各个值然后保存到数据库中。

    2024年02月02日
    浏览(51)
  • 使用 PHP WorkerMan 构建 WebSocket 全双工群聊通信(二)

    在很早很早以前,WebSocket 协议还没有被发明的时候,人们在 Web 端制作类实时数据动态更新时,一般采用轮询、 长连接 (Long Polling) 来实现。大概就是: 轮询:客户端不停发送 HTTP 请求给服务端,服务端返回最新数据 长连接:客户端发送一条 HTTP 请求给服务端,服务端 HOLD

    2024年02月09日
    浏览(35)
  • 使用WebSocket方式能将群聊信息实时群发给所有在线用户

    1.1 什么是WebSocket WebSocket是一种在单个TCP连接上进行全双工通信的网络协议。它是为了在Web浏览器和Web服务器之间提供实时、双向的通信而设计的。传统的HTTP协议是一种单向通信协议,客户端发送请求,服务器响应,然后连接就关闭了。而WebSocket允许在客户端和服务器之间建

    2024年02月03日
    浏览(35)
  • Spring Boot 3 + Vue 3 整合 WebSocket (STOMP协议) 实现广播和点对点实时消息

    🚀 作者主页: 有来技术 🔥 开源项目: youlai-mall 🍃 vue3-element-admin 🍃 youlai-boot 🌺 仓库主页: Gitee 💫 Github 💫 GitCode 💖 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请纠正! WebSocket是一种在Web浏览器与Web服务器之间建立双向通信的协议,而Spring Boot提供了便捷的WebSocket支持

    2024年02月02日
    浏览(47)
  • 使用WebSocket实现聊天功能

    使用WebSocket实现一对一的聊天功能与未读消息功能 会话表 字段名 字段类型 长度 注释 conversation_id int 11 会话ID create_time datetime 创建时间 conversation_type int 1 会话类型 消息表 字段名 字段类型 长度 注释 message_id int 11 消息ID conversation_id int 11 会话ID sender_id int 11 发送者ID receiver

    2024年02月11日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包