Java中的WebSocket:从聊天室到实时股票图表

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Java中的WebSocket:当HTTP力不从心时

\n\n

想象一下在线股票交易所。你正盯着比特币的价格图表。每秒数字都在变化。或者一个多人在线游戏,你移动角色,而其他上百名玩家能立即看到。又或者只是一个企业聊天室,消息无需按F5就能收到。

\n\n

这一切在底层是如何工作的?过去,开发者们想尽办法:轮询(客户端每秒向服务器询问“有消息吗?”)、长轮询(服务器保持连接直到有新消息)、服务器推送事件(单向数据流)。所有这些都是权宜之计。HTTP协议最初被设计为请求-响应模式。它无法主动“推送”数据。

\n\n

这时WebSocket就派上了用场。这是一个协议,它建立一次连接——并保持其打开状态。服务器可以随时向客户端发送数据,反之亦然。实时双向通信,无需HTTP头部的开销。

\n\n

在本文中,我们将全面剖析Java中的WebSocket。编写一个完整的聊天室,探讨潜在陷阱,并比较流行的库。如果您是一名Java开发者,想要构建Java实时应用——那么您来对地方了。

\n\n

WebSocket如何工作?快速入门

\n\n

WebSocket并非魔法,而只是一个基于TCP的协议。它以一个普通的HTTP请求(握手)开始,然后“升级”为一个持久的双向通道。

\n\n

阶段1:握手

\n

客户端发送一个带有Upgrade: websocket头部的普通HTTP GET请求。服务器以状态码101 Switching Protocols响应。就这样。从此刻起,数据不再以HTTP数据包的形式传输,而是作为WebSocket帧。

\n\n

阶段2:数据帧

\n

数据以小数据包(帧)的形式传输。帧可以是文本(普通字符串)或二进制(图片、音频)。Java库隐藏了这种复杂性——您只需处理字符串或字节。

\n\n

阶段3:关闭

\n

任何一方都可以通过发送一个特殊的Close帧来发起连接关闭。

\n\n

听起来很简单?确实如此。但魔鬼在于细节:超时、重连、扩展。不过这些稍后再谈。

\n\n

Java中的WebSocket库

\n\n

在Java生态系统中,主要有三种方法:

\n\n
    \n
  • Jakarta WebSocket (JSR 356) — Java EE / Jakarta EE标准。可在任何兼容的服务器(Tomcat, Jetty, GlassFish)上运行。注解多,代码少。
  • \n
  • Spring WebSocket — Spring生态系统中对标准的扩展。提供与Security、STOMP(基于WebSocket的协议,用于订阅主题)的集成以及便捷的模板。
  • \n
  • Netty / Vert.x / Undertow — 底层异步框架。性能最高,但代码量更大。
  • \n
\n\n

对于90%的任务,Jakarta或Spring就足够了。我们将通过实际示例来探讨这两种方案。

\n\n

示例1:基于Jakarta WebSocket的简单聊天室(无Spring)

\n\n

假设我们有一个老牌的Tomcat 10+(Jakarta EE)。我们需要一个服务器端点和一个客户端(HTML+JS)。

\n\n

服务器:端点类

\n
import jakarta.websocket.*;\nimport jakarta.websocket.server.ServerEndpoint;\nimport java.io.IOException;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArraySet;\n\n@ServerEndpoint("/chat")\npublic class ChatEndpoint {\n\n    // 存储所有活跃会话(线程安全集合)\n    private static final Set<Session> sessions = new CopyOnWriteArraySet<>();\n\n    @OnOpen\n    public void onOpen(Session session) {\n        sessions.add(session);\n        broadcast("用户已连接。当前聊天室人数:" + sessions.size());\n    }\n\n    @OnMessage\n    public void onMessage(String message, Session session) {\n        // 可以添加逻辑:仅发送给                

博客

书籍推荐