WebSocket和长轮询
WebSocket 原理与实现
原理
WebSocket 是一种在单个TCP连接上进行全双工通信的协议。它允许服务器主动向客户端推送数据,而不需要客户端发起请求。一旦连接建立,客户端和服务器可以随时互相发送消息。
- 握手阶段:客户端通过HTTP请求升级到WebSocket协议。
- 数据传输阶段:一旦握手成功,双方可以通过TCP连接进行双向通信。
- 关闭阶段:当不再需要通信时,任意一方可以关闭连接。
实现步骤
- 添加依赖:在
pom.xml
中添加Spring Boot WebSocket依赖。 - 配置WebSocket:创建一个配置类来注册WebSocket处理器。
- 实现处理器:创建一个处理器类来处理WebSocket消息。
- 启动应用:创建Spring Boot启动类。
- 前端页面:编写HTML页面,使用JavaScript与WebSocket服务器进行通信。
代码示例
后端( Spring Boot)
package com.example.websocketdemo.config;
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
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyWebSocketHandler(), "/ws").setAllowedOrigins("*");
}
}
package com.example.websocketdemo.handler;
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.io.IOException;
import java.util.HashSet;
import java.util.Set;
public class MyWebSocketHandler extends TextWebSocketHandler {
private final Set<WebSocketSession> sessions = new HashSet<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("客户端已连接: " + session.getId());
sessions.add(session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("收到消息: " + message.getPayload());
for (WebSocketSession sess : sessions) {
try {
sess.sendMessage(new TextMessage("服务器已收到: " + message.getPayload()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("客户端已断开连接: " + session.getId());
sessions.remove(session);
}
}
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);
}
}
前端(HTML + JavaScript)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>WebSocket 示例</title>
</head>
<body>
<h1>WebSocket 示例</h1>
<input type="text" id="messageInput" placeholder="输入消息">
<button onclick="sendMessage()">发送消息</button>
<div id="messages"></div>
<script>
const socket = new WebSocket('ws://localhost:8080/ws');
socket.addEventListener('open', function (event) {
console.log('已连接到服务器');
});
socket.addEventListener('message', function (event) {
console.log('收到消息:', event.data);
document.getElementById('messages').innerHTML += `<p>${event.data}</p>`;
});
function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value;
if (message.trim()) {
socket.send(message);
input.value = '';
}
}
</script>
</body>
</html>
长轮询 原理与实现
原理
长轮询是一种模拟服务器推送的技术。客户端向服务器发送一个请求,服务器会保持这个连接打开,直到有新的数据可用或者超时。一旦服务器返回响应,客户端立即再次发起请求以保持连接。
- 客户端发起请求:客户端发送HTTP请求到服务器。
- 服务器等待数据:服务器保持连接打开,直到有新数据或超时。
- 服务器返回响应:一旦有新数据或超时,服务器返回响应。
- 客户端处理响应并重新发起请求:客户端处理响应并立即发起新的请求。
实现步骤
- 创建控制器:编写一个控制器来处理长轮询请求和数据更新请求。
- 启动应用:创建Spring Boot启动类。
- 前端页面:编写HTML页面,使用JavaScript发起长轮询请求和发送更新数据。
代码示例
后端(Spring Boot)
package com.example.longpollingdemo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@RestController
public class LongPollingController {
private final ConcurrentHashMap<String, String> dataStore = new ConcurrentHashMap<>();
private final Object lock = new Object();
@GetMapping("/poll")
public String poll(@RequestParam(value = "timeout", defaultValue = "5000") long timeout) throws InterruptedException {
synchronized (lock) {
if (dataStore.isEmpty()) {
lock.wait(timeout); // 等待新数据或超时
}
return dataStore.pollFirstEntry().getValue(); // 返回第一条数据并移除
}
}
@PostMapping("/update")
public String update(@RequestParam String data) {
synchronized (lock) {
dataStore.put("latest", data); // 存储新数据
lock.notifyAll(); // 通知等待的线程
}
return "数据更新成功";
}
}
package com.example.longpollingdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LongPollingDemoApplication {
public static void main(String[] args) {
SpringApplication.run(LongPollingDemoApplication.class, args);
}
}
前端(HTML + JavaScript)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>长轮询 示例</title>
</head>
<body>
<h1>长轮询 示例</h1>
<button onclick="sendUpdate()">发送更新</button>
<div id="messages"></div>
<script>
let polling = false;
function pollServer() {
if (!polling) {
polling = true;
fetch('/poll?timeout=5000')
.then(response => response.text())
.then(data => {
document.getElementById('messages').innerHTML += `<p>收到数据: ${data}</p>`;
polling = false;
pollServer(); // 立即发起新的请求
})
.catch(error => console.error('Error:', error));
}
}
function sendUpdate() {
const newData = prompt('请输入要发送的数据:');
fetch(`/update?data=${newData}`, { method: 'POST' })
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.error('Error:', error));
}
// 初始调用
pollServer();
</script>
</body>
</html>
WebSocket 和长轮询的区别
特性 | WebSocket | 长轮询 |
---|---|---|
连接类型 | 持久连接 | 短暂连接 |
通信方式 | 全双工通信 | 单向通信 |
延迟 | 低延迟 | 较高延迟 |
资源消耗 | 相对较低 | 相对较高 |
适用场景 | 实时交互性强的应用,如聊天、游戏 | 不支持WebSocket的老式浏览器 |
复杂度 | 较高 | 较低 |
兼容性 | 需要浏览器和服务器的支持 | 广泛支持 |
注意事项
WebSocket 注意事项
- 安全性:确保使用WSS(WebSocket Secure)协议进行加密通信。
- 心跳检测:定期发送心跳包以保持连接活跃,防止网络中断导致连接失效。
- 错误处理:处理连接失败、重连等异常情况。
- 负载均衡:在分布式环境中,确保WebSocket连接能够正确路由到正确的服务器实例。
长轮询 注意事项
- 超时设置:合理设置超时时间,避免过长时间的等待影响用户体验。
- 并发控制:限制同时发起的请求数量,避免服务器压力过大。
- 错误处理:处理请求失败、超时等情况,并提供重试机制。
- 性能优化:尽量减少不必要的请求次数,提高响应速度。
总结
- WebSocket 提供了高效的全双工通信机制,适用于实时性要求高的应用场景,但实现相对复杂,需要考虑安全性和稳定性。
- 长轮询 是一种简单有效的模拟服务器推送技术,适用于不支持WebSocket的老式浏览器或简单应用场景,但存在较高的延迟和资源消耗问题。
根据具体需求选择合适的技术方案,如果需要高效实时通信且支持现代浏览器,建议使用WebSocket;如果需要兼容性广且实现简单,可以选择长轮询。