websocket
- 1.简介
- 2.常见的消息推送方式
- 2.1轮询方式
- 2.1.1短轮询
- 2.1.2长轮询
- 2.2 SSE(server-sent event):服务器发送事件
- 2.3 websocket
- 3.原理解析
- 4.websocket API
- 4.1客户端(浏览器)API
- 4.2服务端API
- 5.实现
- 1.流程分析
- 2.消息格式
- 3.代码实现
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 {@Beanpublic ServerEndpointExporter endpointExporter(){return new ServerEndpointExporter();}
}
ServerEndpointExporter是一个Spring Boot提供的用于自动注册和管理WebSocket端点的类。通过将ServerEndpointExporter作为Bean定义在配置类中,Spring Boot会自动扫描并注册所有带有@ServerEndpoint注解的WebSocket端点。
(3)编写配置类,用于获取 HttpSession 对象
@Configuration
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {@Overridepublic 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集合来存sessionprivate static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();private HttpSession httpSession;/*** 建立连接后时被调用* @param session* @param config*/@OnOpenpublic 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*/@OnMessagepublic void onMessage(String message){try {//将消息的json字符串转换为消息的对象Message msg = JSON.parseObject(message, Message.class);//获取消息接收方的用户名String toName = msg.getToName();String msgString = msg.getMessage();//获取消息接收方的sessionSession 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*/@OnClosepublic void onClose(Session session){//1.从onlineUsers中剔除该用户的sessionString 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ㄒ)/~~