一、WebSocket简介
WebSocket是一种网络通信协议,旨在实现客户端和服务器之间的双向、全双工通信。它在HTML5规范中被引入,用于替代基于传统HTTP协议的长轮询、轮询和流传输等方式,以提供更高效的实时数据传输。
WebSocket的特点
-
双向通信:与传统的HTTP协议不同,WebSocket允许客户端和服务器之间的双向通信。客户端和服务器都可以在任何时候发送数据,而无需等待对方先发起请求。
-
持久连接:WebSocket连接一旦建立,将会保持打开状态,直到客户端或服务器主动关闭连接。这减少了因频繁建立和关闭连接带来的开销。
-
低延迟:由于WebSocket减少了请求头信息和频繁的握手过程,通信延迟显著降低,非常适合实时应用。
-
基于消息:WebSocket通信是基于消息的,而不是基于请求-响应。这样可以更加灵活地传输数据。
WebSocket的工作原理
-
握手阶段:客户端通过HTTP请求向服务器发起WebSocket握手请求。这个请求包含一个特殊的
Upgrade
头部,指示服务器将连接从HTTP升级到WebSocket协议。 -
建立连接:服务器收到握手请求后,如果同意升级连接,则会返回一个101状态码的响应,表示协议切换成功。此时,WebSocket连接正式建立。
-
数据传输:连接建立后,客户端和服务器可以通过WebSocket协议相互发送消息。这些消息可以是文本或二进制数据。
-
关闭连接:当任意一方不再需要连接时,可以发送关闭请求来关闭WebSocket连接。连接关闭后,通信停止,资源释放。
二、数据实时推送的实现方式和应用场景
轮询 (Polling)
优点
- 实现简单:实现和理解起来相对容易,使用传统的HTTP请求。
- 浏览器兼容性好:几乎所有浏览器都支持,适用于所有的客户端。
缺点
- 资源浪费:频繁的HTTP请求会消耗大量带宽和服务器资源,即使没有新数据时也会发送请求。
- 延迟高:实时性差,因为需要等待下一个请求周期才能获取新数据。
使用场景
- 适用于低频更新和对实时性要求不高的应用,如定时刷新页面内容。
长连接 (HTTP Persistent Connection)
优点
- 减少开销:在同一个TCP连接上进行多个HTTP请求,减少了建立和关闭连接的开销。
- 提高效率:适用于连续请求的场景,提高了传输效率。
缺点
- 资源占用:服务器需要维护连接状态,占用资源。
- 不适合实时通信:虽然减少了建立连接的开销,但仍然是基于请求-响应模式,实时性不如WebSocket。
使用场景
- 适用于需要频繁请求数据的应用,如加载大量静态资源的网页。
WebSocket
优点
- 双向通信:客户端和服务器都可以主动发送消息,实现全双工通信。
- 低延迟:一旦连接建立,数据可以实时传输,延迟极低。
- 高效:减少了HTTP头部和握手的开销,适合高频率的数据传输。
缺点
- 复杂度高:实现和维护起来比轮询复杂,需要处理连接管理、心跳检测等。
- 浏览器兼容性:虽然现代浏览器都支持,但在非常旧的浏览器上可能不支持。
使用场景
- 适用于高频率和低延迟的实时应用,如在线游戏、实时聊天、股票行情、协作编辑。
三、代码实现
1.pom.xml添加WebSocke依赖
<!-- SpringBoot Websocket -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.WebSocke配置类
package com.example.websocketdemo;import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;@Configuration
@EnableWebSocket // 启用WebSocket支持
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 注册WebSocket处理程序,并设置WebSocket路径为 "/websocket"registry.addHandler(new MyWebSocketHandler(), "/websocket").setAllowedOrigins("*");}
}
3.WebSocke服务类
package com.example.websocketdemo;import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import java.util.Collections;
import java.util.HashSet;
import java.util.Set;public class MyWebSocketHandler extends TextWebSocketHandler {// 用于存储所有活动的WebSocket会话private static final Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<>());// 处理接收到的文本消息@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {String payload = message.getPayload(); // 获取消息内容System.out.println("Received: " + payload); // 打印接收到的消息session.sendMessage(new TextMessage("Server received: " + payload)); // 回复消息给客户端// 发送当前连接人数broadcast("Current number of connections: " + sessions.size());}// 处理WebSocket连接建立@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {sessions.add(session); // 添加会话到集合System.out.println("Connected: " + session.getId());// 发送当前连接人数broadcast("New connection established. Current number of connections: " + sessions.size());}// 处理WebSocket连接关闭@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {sessions.remove(session); // 从集合中移除会话System.out.println("Disconnected: " + session.getId());// 发送当前连接人数broadcast("Connection closed. Current number of connections: " + sessions.size());}// 向所有连接的客户端广播消息private void broadcast(String message) {for (WebSocketSession session : sessions) {if (session.isOpen()) {try {session.sendMessage(new TextMessage(message));} catch (Exception e) {e.printStackTrace();}}}}
}
3.创建启动类
package com.example.websocketdemo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class WebsocketDemoApplication {public static void main(String[] args) {SpringApplication.run(WebsocketDemoApplication.class, args); // 启动Spring Boot应用}
}
四、测试WebSocket连接
可以使用浏览器的控制台进行测试:
let socket = new WebSocket("ws://localhost:8080/websocket");socket.onopen = function(e) {console.log("[open] Connection established");socket.send("Hello Server!"); // 发送消息给服务器
};socket.onmessage = function(event) {console.log(`[message] Data received from server: ${event.data}`); // 处理服务器返回的消息
};socket.onclose = function(event) {if (event.wasClean) {console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);} else {console.error('[close] Connection died'); // 处理连接关闭}
};socket.onerror = function(error) {console.error(`[error] ${error.message}`); // 处理错误
};