传统 HTTP
短轮询 (Polling)
短轮询的实现思路就是浏览器端每隔几秒钟向服务器端发送 HTTP 请求,服务端在收到请求后,不论是否有数据更新,都直接进行响应,服务端响应之后就会关闭这个 TCP 连接
优点:实现简单
缺点:会造成数据在一小段时间内不同步和大量无效的请求,安全性差、浪费资源
长轮询 (Long Polling)
客户端发送请求后服务器端不会立即返回数据,而是阻塞请求连接,直到服务器端有数据更新或者是连接超时才返回,客户端才再次发出请求新建连接,如此反复从而获取最新数据
什么是 WebSocket
WebSocket协议是基于TCP的一种新的网络协议,它实现了浏览器与服务器全双工(full-duplex)通信,即允许服务器主动发送信息给客户端
为什么需要 WebSocket
我们已经有了 HTTP 协议,为什么还需要 WebSocket 协议?
答案很简单,HTTP 协议有一个很大的缺陷:通信只能由客户端发起,然后服务端接收并响应,却做不到服务端主动向客户端推送消息
试想这么一种情况:
我们想要知道某个商品是否已经上架,如果单方面采用轮询方式,向服务端发送查询请求,效率非常低,而且浪费资源
如果商品上架时,服务端能主动推送该消息给我们,这样省心又省力
WebSocket 如何建立连接
WebSocket 的工作原理很简单,首先与服务器建立常规 HTTP 连接,然后通过发送 Upgrade
标头将其升级为双向 Websocket 连接
Demo
SpringBoot 集成 WebSocket
服务端
@ServerEndpoint("/server/{userId}")
@Component
public class WebSocketServer {
private static Log log = LogFactory.get(WebSocketServer.class);
private static int onlineCount = 0;
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
private Session session;
private String userId;
@OnOpen
public void onOpen(Session session,
@PathParam("userId") String userId){
this.session = session;
this.userId = userId;
// 同一个人
if (webSocketMap.containsKey(userId)){
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
}else {
webSocketMap.put(userId, this);
onlineCount++;
}
log.info("用户连接: " + userId + " 当前在线人数为:" + onlineCount);
sendMessage("连接建立成功.");
}
@OnClose
public void onClose(){
if (webSocketMap.containsKey(userId)){
webSocketMap.remove(userId);
onlineCount--;
}
log.info("用户退出: " + userId + " 当前在线人数为:" + onlineCount);
}
@OnMessage
public void onMessage(String message, Session session){
log.info("用户消息:" + userId + ",报文:" + message);
if (!message.isBlank()){
JSONObject jsonObject = JSON.parseObject(message);
jsonObject.put("fromUserId", userId);
String toUserId = jsonObject.getString("toUserId");
if (!toUserId.isBlank() && webSocketMap.containsKey(toUserId)){
webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
}else {
log.error("请求的用户不在服务器上:" + toUserId);
}
}
}
@OnError
public void onError(Session session, Throwable throwable){
log.error("用户: " + userId + "发生错误: " + throwable.getMessage());
throwable.printStackTrace();
}
public void sendMessage(String message){
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void sendMessageById(String message,
@PathParam("userId") String userId){
if (!userId.isBlank() && webSocketMap.containsKey(userId)){
webSocketMap.get(userId).sendMessage(message);
}else {
log.error("用户不在线:" + userId);
}
}
}
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
@RestController
public class Controller {
@GetMapping("index")
public ResponseEntity<String> index(){
return ResponseEntity.ok("请求成功");
}
@GetMapping("page")
public ModelAndView page(){
return new ModelAndView("WebSocket");
}
@RequestMapping("/push/{toUserId}")
public ResponseEntity<String> push(String message,
@PathVariable String toUserId){
WebSocketServer.sendMessageById(message, toUserId);
return ResponseEntity.ok("发送消息成功");
}
}
客户端
不同用户修改 UserId 即可
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>websocket通讯</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
var socket;
function openSocket() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
//等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
//var socketUrl="${request.contextPath}/im/"+$("#userId").val();
var socketUrl="http://localhost:9999/server/"+$("#userId").val();
socketUrl=socketUrl.replace("https","ws").replace("http","ws");
console.log(socketUrl);
if(socket!=null){
socket.close();
socket=null;
}
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function() {
console.log("websocket已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function(msg) {
console.log(msg.data);
//发现消息进入 开始处理前端触发逻辑
};
//关闭事件
socket.onclose = function() {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function() {
console.log("websocket发生了错误");
}
}
}
function sendMessage() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
console.log("您的浏览器支持WebSocket");
console.log('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}');
socket.send('{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}');
}
}
</script>
<body>
<p>【userId】:<div><input id="userId" name="userId" type="text" value="114514"></div>
<p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value="1919"></div>
<p>【toUserId】:<div><input id="contentText" name="contentText" type="text" value="hello websocket"></div>
<p>【操作】:<div><a onclick="openSocket()">开启socket</a></div>
<p>【操作】:<div><a onclick="sendMessage()">发送消息</a></div>
</body>
</html>
测试结果
Chrome 打开控制台
- Post link: http://example.com/2022/05/01/WebSocket/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.