目录
[HTTP协议与 webSoket协议之间的对比](#HTTP协议与 webSoket协议之间的对比)
[1. 轮询(Polling)](#1. 轮询(Polling))
[2. 长轮询(Long Polling)](#2. 长轮询(Long Polling))
[3. 服务器发送事件(Server-Sent Events, SSE)](#3. 服务器发送事件(Server-Sent Events, SSE))
[4. WebSocket](#4. WebSocket)
webSocketConfig(注入ServerEndpoint注解)
GetHttpSessionConfig(存储Session会话对象)
WebSocket 是一种基于 TCP 的网络通信协议,允许在客户端和服务器之间建立持久的双向通信连接。
基本概念
-
全双工通信(Full Duplex):WebSocket 支持客户端和服务器在同一连接上同时发送和接收数据,允许数据在两个方向上同时传输。
-
半双工(Half Duplex):允许数据在两个方向上传输,但是同一个时间段内只允许一个方向上传输。
-
持久连接:通过一次握手建立连接后,连接会一直保持,无需每次通信都重新建立
-
低延迟与高效性:减少了传统 HTTP 请求-响应模式中的频繁连接开销,数据传输更高效
工作原理
- 握手阶段:客户端通过 HTTP 请求向服务器发起 WebSocket 升级请求,服务器响应后将连接升级为 WebSocket
-
数据传输:连接建立后,数据以帧的形式在客户端和服务器之间传输
-
关闭连接:当一方关闭连接时,WebSocket 会发送关闭帧,通知对方关闭连接

优势
- 实时性:适合需要快速响应和更新的场景
-
节省资源:减少了不必要的网络请求和响应,降低了带宽和服务器资源消耗
-
跨平台支持:主流浏览器和服务器端语言均支持 WebSocket
应用场景
- 实时通信:如在线聊天、实时游戏
-
数据推送:服务器主动向客户端推送数据,如股票行情、天气预报
-
实时监控:如视频监控、设备状态监控
WebSocket 是现代 Web 开发中实现高效实时通信的重要技术,广泛应用于各种需要快速数据交互的场景。
HTTP协议与 webSoket协议之间的对比

用到最多的其中一个场景就是 消息推送。
消息推送场景
消息推送是指服务器主动向客户端发送信息的技术。以下是几种常见的消息推送方式:
1. 轮询(Polling)
-
原理:客户端每隔固定时间向服务器发送请求,查询是否有新消息。
-
优点:
- 实现简单,兼容性好,不需要额外的服务器支持。
-
缺点:
-
频繁请求会增加服务器负担,浪费带宽。
-
实时性较差,消息延迟取决于轮询间隔。
-
-
适用场景:对实时性要求不高的场景,如邮件通知。
2. 长轮询(Long Polling)
-
原理:客户端向服务器发送请求,服务器保持连接打开,直到有新消息才返回响应。
-
优点:
-
减少了无效请求,相比普通轮询更节省资源。
-
实时性更好。
-
-
缺点:
-
服务器需要保持大量连接,对服务器资源要求较高。
-
如果消息间隔过长,可能导致连接超时。
-
-
适用场景:对实时性有一定要求但不希望使用复杂技术的场景。

3. 服务器发送事件(Server-Sent Events, SSE)
原理:
-
服务器通过 HTTP 连接向客户端推送数据,客户端通过
EventSource
接收消息。 -
SSE在服务器和客户端之间打开一个单向通道
-
服务端响应的不再是一次性的数据包,而是text/event-stream类型的数据流信息
-
服务器有数据变更时将数据流式传输到客户端
优点:
-
实现简单,基于 HTTP,兼容性较好。
-
只支持单向通信(服务器到客户端),适合通知类应用。
缺点:
-
不支持双向通信。
-
不支持跨域,需要服务器和客户端在同一个域下。
适用场景:股票行情、新闻推送等单向通知场景。

4. WebSocket
-
原理:通过一次握手建立持久的双向通信连接,客户端和服务器可以随时发送和接收消息。
-
优点:
-
全双工通信,实时性高。
-
数据传输效率高,减少了 HTTP 请求的开销。
-
-
缺点:
-
实现复杂,需要服务器和客户端都支持 WebSocket 协议。
-
需要额外处理连接的建立和关闭。
-
-
适用场景:在线聊天、实时游戏、实时协作工具等。
不同消息推送方式各有优缺点,选择时需要根据具体需求和场景进行权衡。例如:
-
如果对实时性要求不高,可以选择轮询或长轮询。
-
如果需要高效实时的双向通信,WebSocket 是更好的选择。
服务端WebSoket
基本介绍


引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
核心API示例

聊天消息demo流程分析

配置类
webSocketConfig(注入ServerEndpoint注解)
java
package com.angindem.server.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
// 注入ServerEndpointExporter,自动注册使用@ServerEndpoint注解的
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
GetHttpSessionConfig(存储Session会话对象)
java
package com.angindem.server.config;
import com.angindem.common.utils.CommonUtils;
import com.angindem.common.utils.IpUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.websocket.HandshakeResponse;
import jakarta.websocket.server.HandshakeRequest;
import jakarta.websocket.server.ServerEndpointConfig;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
// 获取 HttpSession 对象
HttpSession httpSession = (HttpSession) request.getHttpSession();
// 获取当前请求的HttpServletRequest
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest req = attributes.getRequest();
if (httpSession == null) httpSession = req.getSession();
String ip = IpUtils.getIpAddress(req);
httpSession.setAttribute("user", CommonUtils.getRandomString(8));
// 将 HttpSession 对象存储到 ServerEndpointConfig 中
sec.getUserProperties().put(HttpSession.class.getName(), httpSession);// 将HttpServletRequest存储到EndpointConfig的用户属性中
sec.getUserProperties().put(String.class.getName(), ip);
}
}
服务类
Server端Socket
java
package com.angindem.server.wx;
import com.alibaba.fastjson2.JSON;
import com.angindem.common.utils.MessageUtils;
import com.angindem.server.config.GetHttpSessionConfig;
import com.angindem.server.wx.pojo.Message;
import jakarta.servlet.http.HttpSession;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfig.class)
public class ChatEndPoint {
// ConcurrentHashMap 高并发的多线程访问的哈希Map
public static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();
private HttpSession httpSession;
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
log.info("连接建立成功");
// 1、将 Session 放入到集合中,方便后续使用
this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
String reqIp = (String) config.getUserProperties().get(String.class.getName());
String user = MessageUtils.getNowSessionUserName(this.httpSession);
onlineUsers.put(user, session);
// 2、广播消息。需要将登录的所有用户推送给所有用户
log.info("当前在线人数:{}", onlineUsers.size());
log.info("当前用户:{},来自:{}", user, reqIp);
String message = MessageUtils.getDataMessage("system", user, MessageUtils.getFrinds());
MessageUtils.broadcastAllUsers(message);
}
@OnMessage
public void onMessage(String message) {
log.info("收到消息:{}", message);
Message msg = JSON.parseObject(message, Message.class);
// 获取 消息接收方的用户名
String toName = msg.getToName();
String mess = msg.getMessage();
String user = MessageUtils.getNowSessionUserName(this.httpSession);
String sendMessage = MessageUtils.getMessage("user", user, mess);
// 获取消息接收方用户对象的 session 对象
// Session session = onlineUsers.get(toName);
// session.getBasicRemote().sendText(sendMessage);
MessageUtils.broadcastAllUsers(sendMessage);
}
@OnClose
public void onClose(Session session) {
log.info("连接关闭");
// 1、从 onlineUsers 中剔除当前的 session 对象
String user = MessageUtils.getNowSessionUserName(this.httpSession);
onlineUsers.remove(user);
// 2、通知其它所有的用户,当前用户已经下线
log.info("当前在线人数:{}", onlineUsers.size());
String message = MessageUtils.getDataMessage("system", user, MessageUtils.getFrinds());
MessageUtils.broadcastAllUsers(message);
}
}
客户端
(VueUse的 webSocket 客户端)
javascript
/**
* status 是连接的状态值
* data 是 socket 推送过来的消息
* send 是一个方法, 用来发送消息给后台
* close 是一个方法,关闭 socket 连接
* open 如果当前的websocket是活跃的,将会关闭它重新打开一个新的
*/
const { status, data, close, open, send } = useWebSocket(`ws://localhost:9898/chat`, {
onConnected: function (ws) {
console.log('websocket 连接成功!', ws)
},
onDisconnected: function (ws, event) {
console.log('onDisconnected')
},
onError: function (ws, event) {
console.log('onError', event)
},
onMessage: function (ws, event) {
console.log('event.data', event.data)
if (event.data) {
// messageInfo.value = []
const info = JSON.parse(event.data)
console.log(info) // 这里就是websocket每次发送的消息
if (info.data) updateUsers(info.data);
if (info.message) messageInfo.value.push(info)
}
},
heartbeat: false,
autoClose: false, // 自动关闭连接
});
扩展:
同时可以通过 vue 中的 watch 监听发送过来的数据
javascript
import { watch } from 'vue';
//获取数据时必须要监视,此处的data就是上面结构出的data
watch(data, () => {
//获取到的数据为data.value
console.log(data.value)
})
发送消息到服务端
javascript
//需要发送给服务器的数据
const info = "你好吗?"
//send方法,此处的send为上面解构出的send
send(info)