websocket
1. WebSocket介绍
-
WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。
-
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
-
HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
-
这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。
-
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 AJAX 请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。
2. websocket协议#
-
本协议有两部分:握手和数据传输。
-
握手是基于http协议的。
- 客户端(浏览器)实现
3.1 websocket对象
实现 WebSockets 的 Web 浏览器将通过 WebSocket 对象公开所有必需的客户端功能(主要指支持 Html5 的浏览器)。
以下 API 用于创建 WebSocket 对象:
var ws = new WebSocket(url);
参数url格式说明: ws://ip地址:端口号/资源名称
3.2 websocket事件#
WebSocket 对象的相关事件
事件 | 事件处理程序 | 描述 |
---|---|---|
onopen | websocket对象.onopen | 连接建立时触发 |
onmessage | websocket对象.onmessage | 客户端接收服务端数据时触发 |
onerror | websocket对象.onerror | 通信发生错误时触发 |
onclose | websocket对象.onclose | 连接关闭时触发 |
3.3 WebSocket方法#
WebSocket 对象的相关方法:
方法 | 描述 |
---|---|
send | 使用连接时发送消息 |
服务器实现#
Java WebSocket应用由一系列的WebSocketEndpoint组成。Endpoint 是一个java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口, 就像Servlet之与http请求一样。
我们可以通过两种方式定义Endpoint:
· 第一种是编程式, 即继承类 javax.websocket.Endpoint并实现其方法。
· 第二种是注解式, 即定义一个POJO, 并添加 @ServerEndpoint相关注解。
实现流程#
服务端如何接收数据#
通过为 Session 添加 MessageHandler 消息处理器来接收消息,当采用注解方式定义Endpoint时,我们还可以通过 @OnMessage 注解指定接收消息的方法。
服务端如何推送数据#
发送消息则由RemoteEndpoint完成,其实例由Session维护,根据使用情况,我们可以通过
Session.getBasicRemote获取同步消息发送的实例,然后调用其sendXxx()方法就可以发送消息,可以通过
Session.getAsyncRemote获取异步消息发送实例。
实现一个简单的聊天室功能#
步骤:#
1.首先导入依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
@Data @AllArgsConstructor @NoArgsConstructor public class Message { private Long id; @TableField(value = "from_user_id") private User fromUser; @TableField(value = "to_user_id") private User toUser; private String content; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT) private LocalDateTime updateTime; private int messageType; @TableField(exist = false) private String toName; @TableField(exist = false) private String fromName; @TableField(exist = false) private String message; //添加好友码 public static final int ADD_FRIEND = 2; //好友列表消息码 public static final int FRIEND_LIST_TYPE = 3;
@Data @AllArgsConstructor @NoArgsConstructor public class Result<T>{ /** * 状态码 */ private Integer code; /** * 提示信息,如果有错误时,前端可以获取该字段进行提示 */ private boolean flag; private String message; private T data; //数据 public static <T> Result<T> success(T object) { Result<T> r = new Result<T>(); r.data = object; r.code = 1; r.flag = true; return r; } public static <T> Result<T> error(String message) { Result r = new Result(); r.message = message; r.code = 0; r.flag = false; return r; } }
@PostMapping("/login") public Result<User> login(@RequestBody User user ,HttpSession session){ String password = user.getPassword(); String username = user.getUsername(); log.info("用户登录操作"); //MD5加密 password = DigestUtils.md5DigestAsHex(password.getBytes()); //根据用户名查找数据库 LambdaQueryWrapper<User> wrapper =new LambdaQueryWrapper<>(); wrapper.eq(User::getUsername,username); User one = userService.getOne(wrapper); if (one == null){ return Result.error("用户不存在,请先注册"); }if (! one.getPassword().equals(password)){ return Result.error("用户名或者密码有误"); } // 登录成功,将用户的ID存储到WebSocket连接的Session中 session.setAttribute("userId", one.getId()); // 假设用户ID为one.getId() String sessionId = session.getId(); return Result.success(one); }
@GetMapping("/getUsername") private String getUsername(HttpSession session) { // 从 HttpSession 中获取用户信息 User user = (User) session.getAttribute("user"); if (user != null) { return user.getUsername(); } return null; }
@Configuration @EnableWebSocketMessageBroker public class WebsocketConfig implements WebSocketMessageBrokerConfigurer { @Bean //注入ServerEndpointExporter bean.对象,自动注册使用了@ServerEndpoint public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 定义一个 WebSocket 入口,客户端需要连接到它才能接收推送消息 registry.addEndpoint("/websocket").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry config) { // 启用推送的消息代理(即使用 STOMP 实现 WebSocket 的代理) config.enableSimpleBroker("/topic"); // 开启基于用户的 WebSocket 会话 config.setUserDestinationPrefix("/user"); } } public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator { /** * 获取session对象 * @param sec * @param request * @param response */ @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { //获取HttpSession.对象 HttpSession httpsession = (HttpSession) request.getHttpSession(); //将httpSession存储到配置对象 sec.getUserProperties().put(HttpSession.class.getName(), httpsession); } }
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfig.class) @Component @Slf4j public class ChatEndpoint { private Session session; private static HttpSession httpSession; //用来存储每一个客户端对象对应的ChatEndpoint对象 private static final Map<String,Session> onlineUsers = new ConcurrentHashMap<>(); @OnOpen public void onopen(Session session, EndpointConfig config) { //将局部的session对象赋值给成员session this.session = session; //获取Httpsession对象 ,键值对集合,得到键获取值 this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName()); String user = (String) this.httpSession.getAttribute("user"); onlineUsers.put(user,session); //广播消息,获取在线的所有好友 String message = MessageUtils.getMessage(true, null, getFriendsName()); broadcastAllUsers(message); } /** * 获取所有在线的好友信息,名称 * @return */ public Set getFriendsName(){ Set<String> set = onlineUsers.keySet(); return set; } /** * 发给所有人的广播 * @param message */ private void broadcastAllUsers(String message){ // 拿到所有的用户的chatEndpoint对象 //存储用户的session信息 Set<Map.Entry<String,Session>> entries = onlineUsers.entrySet(); //遍历map集合 for (Map.Entry<String, Session> entry : entries) { //获取所有用户对应的session对象 //拥有getBasicRemote发送消息的方法 Session session =entry.getValue(); //发送消息 try { session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } } @OnMessage public void onMessage(String message, @PathParam("username") String username){ try { log.info("服务端收到用户username={}的消息:{}", username, message); //将消息转换成message对象 Message msg = JSON.parseObject(message,Message.class); //获取接收方的用户名 String toName = msg.getToName(); //获取消息数据 String message1 = msg.getMessage(); //获取接收方的用户的session对象 Session session = onlineUsers.get(toName); if (session != null) { // 获取当前登录的用户 从session中获取 String user = (String) httpSession.getAttribute("user"); //user代表的是发送方 String message2 = MessageUtils.getMessage(false, user, message1); session.getBasicRemote().sendText(message2); }else { log.info("未找到用户username{}的session",toName); } if (msg.ADD_FRIEND ==2 && session != null){ //获取发送发 String user = (String) httpSession.getAttribute("user"); //获取消息 String message2 = MessageUtils.getMessage(false, user, message1); //发送 session.getBasicRemote().sendText(message2); //将好友添加到相应的列表中,例如用Map存储好友列表 Map<String, List<String>> friendLists = (Map<String, List<String>>) httpSession.getAttribute("friendLists"); List<String> friendList=friendLists.get(user); friendList.add(toName); friendLists.put(user,friendList); httpSession.setAttribute("friendLists", friendLists); //发送好友列表 Message friendListMessage = new Message(); friendListMessage.setMessageType(3); friendListMessage.setFromName("System"); friendListMessage.setToName(user); friendListMessage.setMessage(JSON.toJSONString(friendList)); ChatEndpoint.send(friendListMessage,getFriendsName()); } else { log.info("未找到用户username{}的session",toName); } } catch (IOException e) { throw new RuntimeException(e); } } private static void send(Message friendListMessage, Set friendsName) { try { String toName = friendListMessage.getToName(); String message = friendListMessage.getMessage(); Session session = onlineUsers.get(toName); if (session != null) { String json = JSON.toJSONString(message); session.getBasicRemote().sendText(json); } else { log.info("未找到用户{}的session", toName); } } catch (IOException e) { throw new RuntimeException(e); } } @OnClose public void onclose(Session session) { //提出session中的记录 String user = (String) this.httpSession.getAttribute("user"); onlineUsers.remove(user); //通知所有用户,此账号下线。 String message = MessageUtils.getMessage(true, null, getFriendsName()); broadcastAllUsers(message); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?