6.3 WebSocket 升级与 Spring Boot 内嵌 本节摘要:终点站的两个延伸形态。WebSocket 用一次特殊的 HTTP 握手把连接升级为全双工长通道,服务端可以主动推送;Spring Boot 则反转了部署关系——不再是应用住进 Tomcat,而是 Tomcat 被装进应用打成的一个可执行包。本节写一个最小 WebSocket 端点、讲清升级握手的两个要点,然后完整过一遍内嵌模式的配置方法与两种部署形态的取舍。 管道漫游的最后一站接待两类特殊旅客。前六章的请求模型都是"一问一答",WebSocket 打破它;前六章的部署模型都是"应用住进容器",Spring Boot 反转它。两者都是 Tomcat 生态的主流形态,终点站不讲它们就不完整。
本节摘要:终点站的两个延伸形态。WebSocket 用一次特殊的 HTTP 握手把连接升级为全双工长通道,服务端可以主动推送;Spring Boot 则反转了部署关系——不再是应用住进 Tomcat,而是 Tomcat 被装进应用打成的一个可执行包。本节写一个最小 WebSocket 端点、讲清升级握手的两个要点,然后完整过一遍内嵌模式的配置方法与两种部署形态的取舍。
管道漫游的最后一站接待两类特殊旅客。前六章的请求模型都是"一问一答",WebSocket 打破它;前六章的部署模型都是"应用住进容器",Spring Boot 反转它。两者都是 Tomcat 生态的主流形态,终点站不讲它们就不完整。
普通 HTTP 像传纸条:问一句答一句,答完各忙各的。WebSocket 像接通电话:握手之后双方随时说话,连接一直保持。升级动作用 HTTP 语法发起——一个带特殊头部的请求,服务端同意后原连接改走 WebSocket 帧协议,从此不再是一问一答。
// 服务端最小端点:连接建立 接收消息 回声 import jakarta.websocket.*; import jakarta.websocket.server.ServerEndpoint; import java.io.IOException; @ServerEndpoint("/ws/chat") public class ChatEndpoint { @OnOpen public void onOpen(Session session) { System.out.println("客户端接入 会话 " + session.getId()); } @OnMessage public String onMessage(String message) { // 返回值自动作为一条消息发回 客户端收到的就是它 return "服务端收到 " + message; } @OnClose public void onClose(Session session) { System.out.println("会话关闭 " + session.getId()); } @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } }
握手请求的两个要点:路径由注解声明,端口就是连接器端口——WebSocket 没有独立端口,它搭 HTTP 的车;升级请求头里带着约定字段,容器看到它就调出 WebSocket 处理器接管连接,此后这条连接脱离 3.1 的请求流水线(但仍在 maxConnections 的账上)。用浏览器控制台可以当场验证。
// 浏览器控制台直接跑 三行验证回声 const ws = new WebSocket('ws://localhost:8080/order/ws/chat'); ws.onmessage = e => console.log('收到 ' + e.data); ws.send('你好管道');
收到 服务端收到 你好管道
工程上两个注意。其一,会话对象线程安全但要自己管并发广播——给所有在线会话群发时遍历集合逐个发送,发送方法支持同步与异步两档,高频推送用异步档防止互相阻塞。其二,长连接改变了容量模型:3.2 的算术按"短请求"设计,换成几万条长连接后,连接容量与线程占用的关系重新洗牌,NIO 模型(一条连接不再占一个线程)在 WebSocket 场景下的优势比普通 HTTP 更明显——这也是 3.2 结论的又一次印证。
传统模式是"容器等应用":装好 Tomcat,把 WAR 放进去。Spring Boot 模式是"应用带容器":构建产物是一个可执行包,里面打包了内嵌的 Tomcat,运行一条命令,应用自己就是服务进程。
<!-- 构建配置关键片段:打成可执行包并排除独立容器 --> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 这个启动器默认引入内嵌 Tomcat 无需额外配置 --> </dependency> </dependencies>
# 应用配置文件:内嵌连接器三件套 语义与 server.xml 完全对应 server.port=8080 server.tomcat.threads.max=200 server.tomcat.accept-count=100
# 发布就是一条命令 启动日志里能看到熟悉的组件 java -jar order-service.jar
Tomcat initialized with port 8080 Tomcat started on port 8080 (http) with context path ''
关键认知:内嵌不等于另一款 Tomcat。3.2 的线程模型、4.1 的容器层级、5.1 的过滤器机制,全部原样生效,只是配置入口从 XML 换成了配置文件与代码。配置文件里那三行与 server.xml 里的三个属性一一对应——管道知识完全迁移,这是本册坚持以管道为主线讲述的最大红利。
两种形态怎么选?一张表说清。
| 维度 | 独立 Tomcat 加 WAR | Spring Boot 内嵌 |
|---|---|---|
| 部署产物 | WAR 包 | 可执行包 |
| 升级容器 | 换 Tomcat 目录即可,应用不动 | 要随应用重新构建发布 |
| 多应用共享实例 | 天然支持,资源集中 | 一应用一进程,资源独占 |
| 运维界面 | 成熟的脚本与日志体系 | 依赖应用自身与外挂运维 |
| 适用场景 | 多应用集中托管、遗留体系 | 微服务、云原生、快速交付 |
取舍的本质是"谁是最小交付单位"。最小单位是应用(微服务、容器化部署),内嵌模式顺流而下;最小单位是机器上的服务集合(传统企业机房、一台机器养多个应用),独立容器仍是正解。两种形态在本册的知识地图上位于同一条管道——变化的只是管道的包装方式。
💡 关键直觉:判断一个团队该用哪种形态,看他们的发布频率与交付单位。按天发布、按应用扩缩,内嵌;按月发布、按机器规划,独立容器。形态跟节奏走,不跟潮流走。
至此,请求从进城到终点的全部旅程走完。第 7 章转入回程与保养——怎么让这条管道跑得快、看得见、坏了好修、坏人进不来。