(websocket)
1.简介
websocket是一种基于TCP连接上进行全双工通信的协议,设计用于提供低延迟、全双工和长期运行的连接,可以说websocket的出现就是解决实时通信的问题
全双工:通信的双方可以同时发送和接受数据,不需要等对方的响应或传输完成 半双方:允许数据在两个方向上传输,但是在同一个时间段只允许在一个方向上运输
实时通信:即时消息传递、音视频通话、在线会议和实时数据传输等,可以实现即时的数据传输和交流,不需要用户主动请求或刷新来获取更新数据
2.常见的消息推送方式
2.1轮询方式
2.1.1短轮询
浏览器以指定的时间间隔向服务器发出http请求,服务器实时返回数据给浏览器
数据有延迟并且对服务器的压力较大
2.1.2长轮询
浏览器发出ajax请求,服务器端接收到请求后,会阻塞请求直到有数据或者超时才会返回
相对于短轮询而言,对服务器的压力会小一点
2.2 SSE(server-sent event):服务器发送事件
- SSE在服务器和客户端之间打开一个单向通道
- 服务端响应的不再是一次性的数据包,而是text/event-stream类型的数据流信息
- 服务器有数据变更时将数据流式传输到客户端
2.3 websocket
看本篇
3.原理解析
- 浏览器发送请求,请求头中有UPgrade:websocket,请求将http协议升级为websocket协议
- 服务器响应,响应状态码为101,表示将将http协议升级为websocket协议
- 握手后就可以双向数据传输
4.websocket API
4.1客户端(浏览器)API
(1)websocket对象创建
let ws = new WebSocket(URL);
URL说明:
- 格式:
协议://ip地址/访问路径
,默认端口为80 - 协议:协议名称为ws
(2)websocket对象相关事件
(3)websocket对象提供的方法
总体结构
4.2服务端API
Tomcat的7.0.5 版本开始支持WebSocket,并且实现了Java WebSocket规范 Java WebSocket应用由一系列的Endpoint组成,Endpoint 是一个java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口。 可以通过两种方式定义Endpoint:
- 第一种是编程式,即继承类javax.websocket.Endpoint并实现其方式
- 第二种是注解式,即定义一个POJO,并添加@ServerEndpoint相关注解
Endpoint实例在WebSocket握手时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束。在Endpoint接口中明确定义了与其生命周期相关的方法,规范实现者确保生命周期的各个阶段调用实例的相关方法。生命周期方法如下:
左边是编程式,右边是注解式
服务端如何接收客户端发送的数据呢?
- 编程式:通过添加MessageHandler消息处理器来接收消息
- 注解式:在定义Endpoint时,通过@OnMessage竹节指定接收消息的方法
服务端如何推送数据给客户端呢?
发送消息则由RemoteEndpoint完成,它的实例由Session(会话)维护,websocket连接成功后就建立了会话。
发送消息有2种方式发送消息:
- 通过session.getBasicRemote获取同步消息发送的实例,然后调用其sendXxx()发送消息,如sendText()方法向客户端发送文本消息
- 通过session.getAsyncRemote获取异步消息发送的实例,然后调用其sendXxx()发送消息....
5.实现
1.流程分析
- 登录完成后发送请求转为websocket协议,记录session,并广播消息,向所有客户端响应用户列表消息,显示用户在线列表
- 客户端发送消息后服务端解析消息,并判断收消息的人,将消息推送给指定的客户端
- 关闭连接,向所有客户端响应用户列表消息,显示用户在线列表
2.消息格式
客户端--->服务端
toName:谁接收消息
服务端--->客户端
system:判断是否为系统消息 fromName:谁发送的消息 message:消息内容
系统消息中的李四王五是在线用户列表
3.代码实现
(1)引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
(2)编写配置类,扫描添加有@ServerEndpoint注解的 Bean
@Configuration
public class WebsocketConfig {
@Bean
public ServerEndpointExporter endpointExporter(){
return new ServerEndpointExporter();
}
}
ServerEndpointExporter是一个Spring Boot提供的用于自动注册和管理WebSocket端点的类。通过将ServerEndpointExporter作为Bean定义在配置类中,Spring Boot会自动扫描并注册所有带有@ServerEndpoint注解的WebSocket端点。
(3)编写配置类,用于获取 HttpSession 对象
@Configuration
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
//获取httpSession对象
HttpSession httpSession = (HttpSession) request.getHttpSession();
//将httpsession对象保存起来
sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
}
}
修改握手请求,在这个方法中,我们可以获取到HTTP请求的会话对象(HttpSession),并将其保存起来,以便后续在WebSocket连接中使用。
ServerEndpointConfig是Java WebSocket API中的一个接口,用于配置WebSocket端点的相关参数和属性,通过getUserProperties方法获取到用户属性,并使用put方法将HttpSession对象存储在其中。
(4)编写Endpoint类
//定义的endpoint类
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfig.class)//前端中写的访问websocket的路径
@Component
public class ChatEndpoint {
//每个用户都会创建一个Endpoint对象,创建一个公用的map集合来存session
private static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();
private HttpSession httpSession;
/**
* 建立连接后时被调用
* @param session
* @param config
*/
@OnOpen
public void onOpen(Session session, EndpointConfig config){
//1.将session进行保存
httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
String username = (String) httpSession.getAttribute("user");
onlineUsers.put(username,session);
//2.广播消息,需要将登录的所有用户推送给所有用户
String message = MessageUtils.getMessage(true, null, getFriends());//使用MessageUtils中的方法
broadcastAllUsers(message);
}
//获取onlineUsers的map集合中的键
private Set getFriends() {
Set<String> set = onlineUsers.keySet();
return set;
}
//广播给所有用户
private void broadcastAllUsers(String message){
try {
//遍历map集合
Set<Map.Entry<String, Session>> entries = onlineUsers.entrySet();
for (Map.Entry<String, Session> entry : entries) {
//获取所有用户对应的session对象
Session session = entry.getValue();
//发送同步消息
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
//记录日志
}
}
/**
* 浏览器发送消息到服务端时被调用
* 将消息推送给指定的用户
* @param message
*/
@OnMessage
public void onMessage(String message){
try {
//将消息的json字符串转换为消息的对象
Message msg = JSON.parseObject(message, Message.class);
//获取消息接收方的用户名
String toName = msg.getToName();
String msgString = msg.getMessage();
//获取消息接收方的session
Session session = onlineUsers.get(toName);
//获取消息发送方的用户名
String fromName = (String) httpSession.getAttribute("user");
//将消息字符串转换为指定的json格式
String msgJson = MessageUtils.getMessage(false, fromName, msgString);
//发送消息给指定的用户
session.getBasicRemote().sendText(msgJson);
} catch (IOException e) {
//记录日志
}
}
/**
* 关闭连接时被调用
* @param session
*/
@OnClose
public void onClose(Session session){
//1.从onlineUsers中剔除该用户的session
String username = (String) httpSession.getAttribute("user");
onlineUsers.remove(username);
//2.广播给所有用户,该用户下线了
//将消息字符串转换为指定的json格式
String message = MessageUtils.getMessage(true, null, getFriends());
broadcastAllUsers(message);
}
}
消息工具类
public class MessageUtils {
public static String getMessage(boolean isSystemMessage,String fromName, Object message) {
ResultMessage result = new ResultMessage();
result.setSystem(isSystemMessage);
result.setMessage(message);
if(fromName != null) {
result.setFromName(fromName);
}
return JSON.toJSONString(result);
}
}
- @ServerEndpoint注解被标注在ChatEndpoint类上,表示该类是一个WebSocket端点。value = "/chat"指定了WebSocket端点的URL路径为/chat,即客户端可以通过ws://localhost:8080/chat来连接到该WebSocket端点;configurator = GetHttpSessionConfig.class指定了一个配置器类GetHttpSessionConfig,用于在WebSocket握手过程中获取和保存HttpSession对象
- value = "/chat"指定了WebSocket端点的URL路径为/chat,即客户端可以通过ws://localhost:8080/chat来连接到该WebSocket端点;configurator = GetHttpSessionConfig.class指定了一个配置器类GetHttpSessionConfig,用于在WebSocket握手过程中获取和保存HttpSession对象
- onlineUsers用来创建一个公用的map集合来存所有已登录用户的session,因为每个用户登录后都会创建一个Endpoint对象。
- 在session.getBasicRemote().sendText()发送消息之前需要将消息字符串转化为指定的json格式
/(ㄒoㄒ)/~~