当前位置: 代码网 > it编程>编程语言>Java > SpringBoot项目中使用WebSocket实现实时通信功能

SpringBoot项目中使用WebSocket实现实时通信功能

2026年03月25日 Java 我要评论
一、前言在传统的 http 通信中,客户端发起请求,服务器给出响应,一次通信就此结束。这种模式对于静态页面展示完全够用,但对于需要实时推送的场景——如在线聊天、实时通知、股票行

一、前言

在传统的 http 通信中,客户端发起请求,服务器给出响应,一次通信就此结束。这种模式对于静态页面展示完全够用,但对于需要实时推送的场景——如在线聊天、实时通知、股票行情、设备状态监控——就力不从心了。

早期的解决方案是轮询:客户端每隔几秒就发一次请求问服务器"有新消息吗",效率低下且浪费资源。websocket 的出现彻底改变了这一局面。

二、websocket 是什么

websocket 是一种在单个 tcp 连接上进行全双工通信的网络协议,由 html5 规范引入,rfc 6455 正式定义。

2.1 与 http 的核心区别

对比项httpwebsocket
通信方向单向(客户端请求,服务器响应)双向(任意一方均可主动发送)
连接状态无状态,一问一答后断开有状态,连接建立后持续保持
协议头开销每次请求都携带完整 header握手一次后,后续帧头极小(2~10字节)
适用场景普通页面请求实时推送、聊天、游戏

2.2 握手过程

websocket 复用了 http 的握手机制,通过一次 http 请求完成协议升级:

客户端发送升级请求:
get /ws/chat http/1.1
host: localhost:8080
upgrade: websocket
connection: upgrade
sec-websocket-key: dghlihnhbxbszsbub25jzq==
sec-websocket-version: 13
服务器返回:
http/1.1 101 switching protocols
upgrade: websocket
connection: upgrade
sec-websocket-accept: s3pplmbitxaq9kygzzhzrbk+xoo=

握手成功后,协议从 http 升级为 websocket,后续通信不再经过 http 层,直接在 tcp 连接上传输 websocket 帧。

三、spring boot 集成 websocket

spring boot 提供了两种方式使用 websocket:

  • 方式一:基于 java ee 标准的 @serverendpoint(本文重点介绍)
  • 方式二:基于 spring 的 websockethandler

3.1 添加依赖

<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-websocket</artifactid>
</dependency>

3.2 注册配置类

使用内置 tomcat 时,需要手动注册 serverendpointexporter,让 spring 能扫描到 @serverendpoint 注解:

@configuration
public class websocketconfig {
    /**
     * 使用内置 tomcat 时必须注入此 bean
     * 若使用外部 tomcat 部署,则不需要,容器会自行管理
     */
    @bean
    public serverendpointexporter serverendpointexporter() {
        return new serverendpointexporter();
    }
}

3.3 编写 websocket 端点

@component
@serverendpoint("/ws/chat/{userid}")
public class chatwebsocket {
    private static final logger log = loggerfactory.getlogger(chatwebsocket.class);
    // 存储所有在线连接,key=userid,value=websocket会话
    private static final concurrenthashmap<string, session> sessions = new concurrenthashmap<>();
    /**
     * 连接建立时触发
     */
    @onopen
    public void onopen(session session, @pathparam("userid") string userid) {
        sessions.put(userid, session);
        log.info("用户 {} 上线,当前在线人数:{}", userid, sessions.size());
    }
    /**
     * 收到客户端消息时触发
     */
    @onmessage
    public void onmessage(string message, @pathparam("userid") string userid) {
        log.info("收到用户 {} 的消息:{}", userid, message);
        // 解析消息,转发给目标用户
        handlemessage(userid, message);
    }
    /**
     * 连接关闭时触发
     */
    @onclose
    public void onclose(@pathparam("userid") string userid) {
        sessions.remove(userid);
        log.info("用户 {} 下线,当前在线人数:{}", userid, sessions.size());
    }
    /**
     * 发生异常时触发
     */
    @onerror
    public void onerror(session session, throwable error) {
        log.error("websocket 发生异常:{}", error.getmessage());
    }
    /**
     * 向指定用户发送消息
     */
    public static void sendtouser(string userid, string message) {
        session session = sessions.get(userid);
        if (session != null && session.isopen()) {
            try {
                session.getbasicremote().sendtext(message);
            } catch (ioexception e) {
                log.error("向用户 {} 发送消息失败:{}", userid, e.getmessage());
            }
        }
    }
    /**
     * 广播消息给所有在线用户
     */
    public static void broadcast(string message) {
        sessions.foreach((userid, session) -> {
            if (session.isopen()) {
                try {
                    session.getbasicremote().sendtext(message);
                } catch (ioexception e) {
                    log.error("广播消息失败,userid={}:{}", userid, e.getmessage());
                }
            }
        });
    }
    /**
     * 处理消息逻辑(示例:解析json转发)
     */
    private void handlemessage(string fromuserid, string message) {
        try {
            // 假设消息格式为 json:{"touserid":"xxx","content":"hello"}
            jsonobject json = jsonobject.parseobject(message);
            string touserid = json.getstring("touserid");
            string content = json.getstring("content");
            jsonobject response = new jsonobject();
            response.put("fromuserid", fromuserid);
            response.put("content", content);
            response.put("time", system.currenttimemillis());
            sendtouser(touserid, response.tojsonstring());
        } catch (exception e) {
            log.error("消息处理失败:{}", e.getmessage());
        }
    }
}

3.4 在业务代码中主动推送

websocket 不只能被动接收消息,也可以在任意业务逻辑中主动向客户端推送:

@service
public class orderservice {
    public void completeorder(long orderid, long userid) {
        // 处理订单完成逻辑...
        // 订单完成后,主动推送通知给用户
        jsonobject notify = new jsonobject();
        notify.put("type", "order_complete");
        notify.put("orderid", orderid);
        notify.put("message", "您的订单已完成,请及时查看");
        chatwebsocket.sendtouser(string.valueof(userid), notify.tojsonstring());
    }
}

四、前端对接

<!doctype html>
<html>
<body>
<script>
    const userid = "user_001";
    const ws = new websocket(`ws://localhost:8080/ws/chat/${userid}`);
    // 连接建立
    ws.onopen = function () {
        console.log("websocket 连接成功");
        ws.send(json.stringify({
            touserid: "user_002",
            content: "你好!"
        }));
    };
    // 收到消息
    ws.onmessage = function (event) {
        const data = json.parse(event.data);
        console.log(`收到来自 ${data.fromuserid} 的消息:${data.content}`);
    };
    // 连接关闭
    ws.onclose = function () {
        console.log("websocket 连接已关闭");
    };
    // 发生错误
    ws.onerror = function (error) {
        console.error("websocket 错误:", error);
    };
</script>
</body>
</html>

五、生产环境注意事项

5.1 @serverendpoint 无法注入 spring bean 的问题

@serverendpoint 的实例由 tomcat 管理,不是 spring bean,所以直接用 @autowired 注入会失败:

@serverendpoint("/ws/chat/{userid}")
public class chatwebsocket {
    @autowired
    private userservice userservice;  // ❌ 注入失败,值为 null
}

解决方案:通过静态变量 + applicationcontext 获取

@serverendpoint("/ws/chat/{userid}")
public class chatwebsocket implements applicationcontextaware {
    private static userservice userservice;
    @override
    public void setapplicationcontext(applicationcontext context) {
        userservice = context.getbean(userservice.class);
    }
}

或者更简洁地,在配置类里提前拿到:

@component
public class websocketbeanfactory implements applicationcontextaware {
    private static applicationcontext context;
    @override
    public void setapplicationcontext(applicationcontext applicationcontext) {
        context = applicationcontext;
    }
    public static <t> t getbean(class<t> clazz) {
        return context.getbean(clazz);
    }
}
// 在 websocket 端点里使用
userservice userservice = websocketbeanfactory.getbean(userservice.class);

5.2 集群部署下的消息广播问题

单机部署时,所有连接都在同一个 jvm 里,concurrenthashmap 存储会话没问题。但集群部署时,用户 a 连接在节点1,用户 b 连接在节点2,节点1无法直接找到用户 b 的 session。

解决方案:引入消息中间件(如 rabbitmq / redis pub-sub)

节点1收到消息
    ↓
发布到 mq / redis channel
    ↓
所有节点订阅并消费消息
    ↓
各节点检查自己管理的 session 里有没有目标用户
    ↓
有则发送,无则忽略

5.3 连接心跳保活

长时间无数据交换时,防火墙或负载均衡器可能会断开连接,需要定期发送心跳:

// 前端每 30 秒发送一次心跳
setinterval(() => {
    if (ws.readystate === websocket.open) {
        ws.send(json.stringify({ type: "ping" }));
    }
}, 30000);
// 后端收到心跳回复
@onmessage
public void onmessage(string message, session session) {
    jsonobject json = jsonobject.parseobject(message);
    if ("ping".equals(json.getstring("type"))) {
        try {
            session.getbasicremote().sendtext("{\"type\":\"pong\"}");
        } catch (ioexception e) {
            log.error("心跳回复失败");
        }
    }
}

5.4 连接断开重连

网络抖动时前端需要自动重连:

function createwebsocket(userid) {
    const ws = new websocket(`ws://localhost:8080/ws/chat/${userid}`);
    ws.onclose = function () {
        console.log("连接断开,3秒后重连...");
        settimeout(() => createwebsocket(userid), 3000);
    };
    return ws;
}

六、完整流程总结

1. 引入 spring-boot-starter-websocket 依赖
         ↓
2. 注册 serverendpointexporter bean(内置tomcat必须)
         ↓
3. 编写 @serverendpoint 端点类
   实现 @onopen / @onmessage / @onclose / @onerror
         ↓
4. 用 concurrenthashmap 管理所有在线 session
         ↓
5. 业务代码调用静态方法主动推送消息
         ↓
6. 前端用 new websocket(url) 建立连接
   通过 onmessage 接收,ws.send() 发送

七、适用场景总结

场景说明
在线聊天用户之间实时发送消息
实时通知订单完成、充值到账、审批结果
设备监控硬件设备实时上报状态数据
协同编辑多人同时编辑同一文档
行情推送股票、加密货币实时价格
在线游戏多人游戏实时同步状态

websocket 适合高频、双向、实时的通信场景。如果只是服务器单向推送(如消息通知),也可以考虑更轻量的 sse(server-sent events);如果实时性要求不高,简单的短轮询也能满足需求。选择合适的技术,比盲目使用 websocket 更重要。

以上就是springboot项目中使用websocket实现实时通信功能的详细内容,更多关于springboot websocket实时通信的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com