WebSocket机制
WebSocket 是 HTML5 一种新的协议。它实现了欣赏器与服务器全双工通信,能更好的节省服务器资源和带宽并到达实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大差别是:
WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;
WebSocket 须要雷同 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。
和socket区别
Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。
Socket是应用层与TCP/IP协议族通信的中央软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族潜伏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
当两台主机通信时,必须通过Socket连接,Socket则利用TCP/IP协议建立TCP连接。TCP连接则更依赖于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。
WebSocket则是一个典范的应用层协议。
区别
Socket是传输控制层协议,WebSocket是应用层协议。
前世此生
众所周知,Web 应用的交互过程通常是客户端通过欣赏器发出一个哀求,服务器端接收哀求后举行处理并返回结果给客户端,客户端欣赏器将信息呈现,这种机制对于信息变化不是特别频仍的应用尚可,但对于实时要求高、海量并发的应用来说显得捉襟见肘,尤其在当前业界移动互联网发达发展的趋势下,高并发与用户实时响应是 Web 应用经常面临的问题,好比金融证券的实时信息,Web 导航应用中的地理位置获取,交际网络的实时消息推送等。
消息推送常用方式
轮询方式
1.轮询是一种客户端与服务器之间实时通信的技能本领。客户端定期发送哀求来查询服务器是否有新数据或事件,并将响应返 回给客户端。如果服务器有新的数据或事件,则将其返回给客户端;如果没有,则返回一个空响应。客户端收到响应后,可 以处理数据或事件,并根据须要继续发送下一个哀求。
2.长轮询是一种改进的轮询技能,其主要目的是低落轮询过程中的资源消耗和延迟。长轮询的基本原理是客户端发送一个 HTTP哀求给服务器,并保持连接打开,直到服务器有新的数据或事件时才返回响应给客户端。在这期间,服务器会一直保持连接打开,直到超时或有新数据或事件(HTTP1.1版本就是这个性子长连接)
SSE(服务器发送事件)
SSE在服务器和客户端之间打开一个单向通道
服务端响应的不再是一次性的数据包,而是text/event-stream类型的数据流信息
服务器有数据变动时将数据流式传输到客户端
WebSocket
客户端API
服务端 API
Endpoint示例
- @ServerEndpoint("/chat")
- @Component
- public class ChatEndpoint {
- @OnOpen
- //连接建立时被调用
- public void onOpen(Session session, EndpointConfig config){}
- @OnMessage
- //接收到客户端发送的数据时被调用
- public void onMessage(String message){}
- @OnClose
- //连接关闭时被调用
- public void onClose(Session session){}
- }
复制代码 ChatEndpoint类通过实现WebSocket协议,用于处理客户端的连接、消息通报和关闭事件。
在线聊天室实现
流程分析
消息格式
引入坐标
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-websocket</artifactId>
- </dependency>
复制代码 编写设置类
- @Configuration
- public class WebsocketConfig {
- @Bean
- public ServerEndpointExporter serverEndpointExporter() {
- return new ServerEndpointExporter();
- }
- }
复制代码 通过在设置类中界说一个 ServerEndpointExporter 的 @Bean 方法,Spring 会主动创建一个 ServerEndpointExporter 实例,并将其加入到 Spring 容器中。Spring框架会在启动时通过这个实例主动扫描项目中全部使用@ServerEndpoint注解的类,并将它们注册为WebSocket端点。
编写设置类,用于获取 HttpSession 对象
- 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);
- }
- }
复制代码 GetHttpSessionConfig类用于在WebSocket握手过程中获取HTTP会话(HttpSession)对象,并将其保存到用户属性中,以便在WebSocket会话中使用。
ChatEndpoint类
- package com.itheima.ws;
- import com.alibaba.fastjson.JSON;
- import com.itheima.config.GetHttpSessionConfig;
- import com.itheima.utils.MessageUtils;
- import com.itheima.ws.pojo.Message;
- import org.springframework.stereotype.Component;
- import javax.servlet.http.HttpSession;
- import javax.websocket.*;
- import javax.websocket.server.ServerEndpoint;
- import java.util.Map;
- import java.util.Set;
- import java.util.concurrent.ConcurrentHashMap;
- /**
- * @version v1.0
- * @ClassName: ChatEndpoint
- * @Description: TODO(一句话描述该类的功能)
- * @Author: 黑马程序员
- */
- @ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfig.class)
- @Component
- public class ChatEndpoint {
- private static final Map<String,Session> onlineUsers = new ConcurrentHashMap<>();
- private HttpSession httpSession;
- /**
- * 建立websocket连接后,被调用
- * @param session
- */
- @OnOpen
- public void onOpen(Session session, EndpointConfig config) {
- //1,将session进行保存
- this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
- String user = (String) this.httpSession.getAttribute("user");
- onlineUsers.put(user,session);
- //2,广播消息。需要将登陆的所有的用户推送给所有的用户
- String message = MessageUtils.getMessage(true,null,getFriends());
- broadcastAllUsers(message);
- }
- public 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 (Exception e) {
- //记录日志
- }
- }
- /**
- * 浏览器发送消息到服务端,该方法被调用
- *
- * 张三 --> 李四
- * @param message
- */
- @OnMessage
- public void onMessage(String message) {
- try {
- //将消息推送给指定的用户
- Message msg = JSON.parseObject(message, Message.class);
- //获取 消息接收方的用户名
- String toName = msg.getToName();
- String mess = msg.getMessage();
- //获取消息接收方用户对象的session对象
- Session session = onlineUsers.get(toName);
- String user = (String) this.httpSession.getAttribute("user");
- String msg1 = MessageUtils.getMessage(false, user, mess);
- session.getBasicRemote().sendText(msg1);
- } catch (Exception e) {
- //记录日志
- }
- }
- /**
- * 断开 websocket 连接时被调用
- * @param session
- */
- @OnClose
- public void onClose(Session session) {
- //1,从onlineUsers中剔除当前用户的session对象
- String user = (String) this.httpSession.getAttribute("user");
- onlineUsers.remove(user);
- //2,通知其他所有的用户,当前用户下线了
- String message = MessageUtils.getMessage(true,null,getFriends());
- broadcastAllUsers(message);
- }
- }
复制代码 @ServerEndpoint(value = “/chat”, configurator = GetHttpSessionConfig.class) 这行代码声明了一个 WebSocket 端点,客户端可以通过 /chat 路径与之建立连接,并且在握手阶段使用 GetHttpSessionConfig 类来举行自界说设置。
该类是Spring Boot应用中的一个WebSocket端点,它用于处理聊天功能。主要功能包罗:
- 在用户建立WebSocket连接时保存用户信息和会话对象。
- 当用户发送消息时,将消息转发给指定的接收用户。
- 当用户断开连接时,从在线用户列表中移除用户并关照其他用户。
- 使用GetHttpSessionConfig设置器来获取HTTP会话中的用户信息。
userController
- package com.itheima.controller;
- import com.itheima.pojo.Result;
- import com.itheima.pojo.User;
- import org.springframework.web.bind.annotation.*;
- import javax.servlet.http.HttpSession;
- @RestController
- @RequestMapping("user")
- public class UserController {
- /**
- * 登陆
- * @param user 提交的用户数据,包含用户名和密码
- * @param session
- * @return
- */
- @PostMapping("/login")
- public Result login(@RequestBody User user, HttpSession session) {
- Result result = new Result();
- if(user != null && "123".equals(user.getPassword())) {
- result.setFlag(true);
- //将数据存储到session对象中
- session.setAttribute("user",user.getUsername());
- } else {
- result.setFlag(false);
- result.setMessage("登陆失败");
- }
- return result;
- }
- /**
- * 获取用户名
- * @param session
- * @return
- */
- @GetMapping("/getUsername")
- public String getUsername(HttpSession session) {
- String username = (String) session.getAttribute("user");
- return username;
- }
- }
复制代码 Result
- package com.itheima.pojo;
- import lombok.Data;
- /**
- * @version v1.0
- * @ClassName: Result
- * @Description: 用来封装http请求的响应数据
- * @Author: 黑马程序员
- */
- @Data
- public class Result {
- private boolean flag;
- private String message;
- }
复制代码 User
- package com.itheima.pojo;
- import lombok.Data;
- /**
- * @version v1.0
- * @ClassName: User
- * @Description: 接收登录请求的数据
- * @Author: 黑马程序员
- */
- @Data
- public class User {
- private String userId;
- private String username;
- private String password;
- }
复制代码 MessageUtils
- package com.itheima.utils;
- import com.alibaba.fastjson.JSON;
- import com.itheima.ws.pojo.ResultMessage;
- /**
- * @version v1.0
- * @ClassName: MessageUtils
- * @Description: 封装json格式消息的工具类
- * @Author: 黑马程序员
- */
- 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);
- }
- }
复制代码 Message
- package com.itheima.ws.pojo;
- import lombok.Data;
- /**
- * @version v1.0
- * @ClassName: Message
- * @Description: 用于封装浏览器发送给服务端的消息数据
- * @Author: 黑马程序员
- */
- @Data
- public class Message {
- private String toName;
- private String message;
- }
复制代码 ResultMessage
- package com.itheima.ws.pojo;
- import lombok.Data;
- /**
- * @version v1.0
- * @ClassName: ResultMessage
- * @Description: 用来封装服务端给浏览器发送的消息数据
- * @Author: 黑马程序员
- */
- @Data
- public class ResultMessage {
- private boolean isSystem;
- private String fromName;
- private Object message;//如果是系统消息是数组
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |