V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Aidenboss
V2EX  ›  Java

基于 springboot websocket 的群聊实现

  •  1
     
  •   Aidenboss · 2021-02-02 01:08:37 +08:00 · 1835 次点击
    这是一个创建于 1416 天前的主题,其中的信息可能已经有所发展或是发生改变。

    基于 springboot websocket 的群聊实现

    功能列表

    • 分布式
    • 同一帐号多设备登录
    • 群聊
    • 多设备
    • 简单鉴权
    • 心跳检查

    依赖

    • maven
    • jdk11
    • redis

    redis 配置

    redis 默认使用 localhost:6379 。如果需要修改 host:port,可以修改 application.yml
    redis 仅仅用于存储用户 username / password

    源码分析

    Auth 过程
    String username = ((ServletServerHttpRequest) request)
      .getServletRequest().getParameter(USERNAME);
    String password = ((ServletServerHttpRequest) request)
      .getServletRequest().getParameter(PASSWORD);
    User user = userService.register(username, password);
    // 将 user 设置到 attributes 中
    attributes.put(USER, user);
    return true;
    
    连接创建
    // 1. 添加 session
    sessionService.add(session);
    // 2. 按 username + sessionId 生成 redis key,并进行订阅,这样做可以支持多设备同一个帐号登录
    MessageListener messageListener = (message, pattern) -> {
      log.info("Redis sub receive: [{}]", new String(message.getBody()));
      try {
        session.sendMessage(new TextMessage(message.getBody()));
      } catch (IOException e) {
        log.error("", e);
      }
    };
    redisMessageListenerContainer.addMessageListener(messageListener,
      new ChannelTopic(String.format("sub:%s:%s", SessionUtil.getUsernameFromSession(session), session.getId())));
    // 由于 session 会断开,需要保存下来,以待 removeListener
    messageListenerMap.put(session, messageListener);
    
    监听消息
    // 获取所有在线的 session,然后通过 redis pub 功能转发消息
    sessionService.getSessions()
      .forEach(session -> subPubService.pub(session, textMessage));
    
    心跳检查
    // 核心逻辑是一个定时任务,通过延时队列 poll 实现。
    // 其中 HeartbeatSessionTask 封装了 session 和对应的过期时间
    this.executorService.submit(() -> {
      while (true) {
        try {
          HeartbeatSessionTask task;
          while ((task = queue.poll()) != null) {
            task.getSession().close();
            log.warn("[{}] is dead, so close", SessionUtil.getUsernameFromSession(task.session));
          }
        } catch (Exception e) {
          log.error("", e);
        }
        Thread.sleep(TimeUnit.SECONDS.toMillis(1));
      }
    });
    

    使用

    服务端启动

    启动后,会监听 localhost:8080 端口
    其中,websocket url 为 ws:localhost:8080/ws
    获取在线用户数接口为 http://localhost:8080/session/page

    shell 测试

    使用 wscat 测试

    wscat -c 'ws://localhost:8080/ws?username=aiden&password=123'
    
    前端测试

    由于有简单的帐号体系,链接时需要制定 username / password,若 username 不存在,则直接注册成功;否则会判断 username / password 是否匹配
    如 ws://localhost:8080/ws?username=aiden&password=123 才能进行连接 图示:

    4 条回复    2021-02-02 19:51:18 +08:00
    wuvvu
        1
    wuvvu  
       2021-02-02 09:52:53 +08:00
    支持支持
    agentFitzzzzz
        2
    agentFitzzzzz  
       2021-02-02 11:30:29 +08:00
    不错
    razertory
        3
    razertory  
       2021-02-02 19:09:26 +08:00
    Aidenboss
        4
    Aidenboss  
    OP
       2021-02-02 19:51:18 +08:00
    @razertory 感觉定位不太一样
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5300 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 08:38 · PVG 16:38 · LAX 00:38 · JFK 03:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.