websocket

打印 上一主题 下一主题

主题 806|帖子 806|积分 2418

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 对象的相关事件
事件事件处理程序描述onopenwebsocket对象.onopen连接建立时触发onmessagewebsocket对象.onmessage客户端接收服务端数据时触发onerrorwebsocket对象.onerror通信发生错误时触发onclosewebsocket对象.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.首先导入依赖
  1. <dependencies>
  2.     <dependency>
  3.         <groupId>org.springframework.boot</groupId>
  4.         <artifactId>spring-boot-starter-websocket</artifactId>
  5.     </dependency>
复制代码
Message
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class Message {
  5.     private Long id;
  6.     @TableField(value = "from_user_id")
  7.     private User fromUser;
  8.     @TableField(value = "to_user_id")
  9.     private User toUser;
  10.      private String content;
  11.     @TableField(fill = FieldFill.INSERT)
  12.     private LocalDateTime createTime;
  13.     @TableField(fill = FieldFill.INSERT)
  14.     private LocalDateTime updateTime;
  15.     private int messageType;
  16.     @TableField(exist = false)
  17.     private String toName;
  18.     @TableField(exist = false)
  19.     private String fromName;
  20.     @TableField(exist = false)
  21.     private String message;
  22.     //添加好友码
  23.     public static final int ADD_FRIEND = 2;
  24.     //好友列表消息码
  25.     public static final int FRIEND_LIST_TYPE = 3;
复制代码
Result
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class Result<T>{
  5.     /**
  6.      * 状态码
  7.      */
  8.     private Integer code;
  9.     /**
  10.      * 提示信息,如果有错误时,前端可以获取该字段进行提示
  11.      */
  12.     private boolean flag;
  13.     private String message;
  14.     private T data; //数据
  15.     public static <T> Result<T> success(T object) {
  16.         Result<T> r = new Result<T>();
  17.         r.data = object;
  18.         r.code = 1;
  19.         r.flag = true;
  20.         return r;
  21.     }
  22.     public static <T> Result<T> error(String message) {
  23.         Result r = new Result();
  24.         r.message = message;
  25.         r.code = 0;
  26.         r.flag = false;
  27.         return r;
  28.     }
  29. }
复制代码
登录
  1.     @PostMapping("/login")
  2.     public Result<User> login(@RequestBody User user ,HttpSession session){
  3.         String password = user.getPassword();
  4.         String username = user.getUsername();
  5.         log.info("用户登录操作");
  6.          //MD5加密
  7.         password = DigestUtils.md5DigestAsHex(password.getBytes());
  8.         //根据用户名查找数据库
  9.         LambdaQueryWrapper<User> wrapper =new LambdaQueryWrapper<>();
  10.         wrapper.eq(User::getUsername,username);
  11.         User one = userService.getOne(wrapper);
  12.         if (one == null){
  13.             return Result.error("用户不存在,请先注册");
  14.         }if (! one.getPassword().equals(password)){
  15.             return Result.error("用户名或者密码有误");
  16.         }
  17.         // 登录成功,将用户的ID存储到WebSocket连接的Session中
  18.         session.setAttribute("userId", one.getId()); // 假设用户ID为one.getId()
  19.         String sessionId = session.getId();
  20.         return Result.success(one);
  21.     }
复制代码
获取用户名
  1.     @GetMapping("/getUsername")
  2.     private String getUsername(HttpSession session) {
  3.         // 从 HttpSession 中获取用户信息
  4.         User user = (User) session.getAttribute("user");
  5.         if (user != null) {
  6.             return user.getUsername();
  7.         }
  8.         return null;
  9.     }
复制代码
配置
  1. @Configuration
  2. @EnableWebSocketMessageBroker
  3. public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
  4.     @Bean
  5.     //注入ServerEndpointExporter bean.对象,自动注册使用了@ServerEndpoint
  6.     public ServerEndpointExporter serverEndpointExporter(){
  7.         return  new ServerEndpointExporter();
  8.     }
  9.     @Override
  10.     public void registerStompEndpoints(StompEndpointRegistry registry) {
  11.         // 定义一个 WebSocket 入口,客户端需要连接到它才能接收推送消息
  12.         registry.addEndpoint("/websocket").withSockJS();
  13.     }
  14.     @Override
  15.     public void configureMessageBroker(MessageBrokerRegistry config) {
  16.         // 启用推送的消息代理(即使用 STOMP 实现 WebSocket 的代理)
  17.         config.enableSimpleBroker("/topic");
  18.         // 开启基于用户的 WebSocket 会话
  19.         config.setUserDestinationPrefix("/user");
  20.     }
  21. }
  22. public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {
  23.     /**
  24.      * 获取session对象
  25.      * @param sec
  26.      * @param request
  27.      * @param response
  28.      */
  29.     @Override
  30.     public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
  31.          //获取HttpSession.对象
  32.         HttpSession httpsession = (HttpSession) request.getHttpSession();
  33.         //将httpSession存储到配置对象
  34.         sec.getUserProperties().put(HttpSession.class.getName(), httpsession);
  35.     }
  36. }
复制代码
聊天室功能
  1. @ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfig.class)
  2. @Component
  3. @Slf4j
  4. public class ChatEndpoint {
  5.     private Session session;
  6.     private static HttpSession httpSession;
  7.     //用来存储每一个客户端对象对应的ChatEndpoint对象
  8.     private static final Map<String,Session> onlineUsers = new ConcurrentHashMap<>();
  9.     @OnOpen
  10.     public void onopen(Session session, EndpointConfig config) {
  11.         //将局部的session对象赋值给成员session
  12.         this.session = session;
  13.         //获取Httpsession对象 ,键值对集合,得到键获取值
  14.         this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
  15.         String user = (String) this.httpSession.getAttribute("user");
  16.         onlineUsers.put(user,session);
  17.         //广播消息,获取在线的所有好友
  18.         String message = MessageUtils.getMessage(true, null, getFriendsName());
  19.         broadcastAllUsers(message);
  20.     }
  21.     /**
  22.      * 获取所有在线的好友信息,名称
  23.      * @return
  24.      */
  25.     public Set getFriendsName(){
  26.         Set<String> set = onlineUsers.keySet();
  27.         return set;
  28.     }
  29.     /**
  30.      * 发给所有人的广播
  31.      * @param message
  32.      */
  33.     private void broadcastAllUsers(String message){
  34. //        拿到所有的用户的chatEndpoint对象  //存储用户的session信息
  35.         Set<Map.Entry<String,Session>> entries = onlineUsers.entrySet();
  36.         //遍历map集合
  37.         for (Map.Entry<String, Session> entry : entries) {
  38.             //获取所有用户对应的session对象  //拥有getBasicRemote发送消息的方法
  39.             Session session =entry.getValue();
  40.             //发送消息
  41.             try {
  42.                 session.getBasicRemote().sendText(message);
  43.             } catch (IOException e) {
  44.                 e.printStackTrace();
  45.             }
  46.         }
  47.     }
  48.     @OnMessage
  49.     public void onMessage(String message, @PathParam("username") String username){
  50.         try {
  51.             log.info("服务端收到用户username={}的消息:{}", username, message);
  52.             //将消息转换成message对象
  53.             Message msg = JSON.parseObject(message,Message.class);
  54.             //获取接收方的用户名
  55.             String toName = msg.getToName();
  56.             //获取消息数据
  57.             String message1 = msg.getMessage();
  58.             //获取接收方的用户的session对象
  59.             Session session = onlineUsers.get(toName);
  60.             if (session != null) {
  61.     //            获取当前登录的用户  从session中获取
  62.                 String user = (String) httpSession.getAttribute("user");  //user代表的是发送方
  63.                 String message2 = MessageUtils.getMessage(false, user, message1);
  64.                     session.getBasicRemote().sendText(message2);
  65.             }else {
  66.                 log.info("未找到用户username{}的session",toName);
  67.             }
  68.             if (msg.ADD_FRIEND ==2 && session != null){
  69.                 //获取发送发
  70.                 String user = (String) httpSession.getAttribute("user");
  71.                 //获取消息
  72.                 String message2 = MessageUtils.getMessage(false, user, message1);
  73.                 //发送
  74.                 session.getBasicRemote().sendText(message2);
  75.                 //将好友添加到相应的列表中,例如用Map存储好友列表
  76.                 Map<String, List<String>> friendLists = (Map<String, List<String>>) httpSession.getAttribute("friendLists");
  77.                 List<String> friendList=friendLists.get(user);
  78.                 friendList.add(toName);
  79.                 friendLists.put(user,friendList);
  80.                 httpSession.setAttribute("friendLists", friendLists);
  81.                 //发送好友列表
  82.                 Message friendListMessage = new Message();
  83.                 friendListMessage.setMessageType(3);
  84.                 friendListMessage.setFromName("System");
  85.                 friendListMessage.setToName(user);
  86.                 friendListMessage.setMessage(JSON.toJSONString(friendList));
  87.                 ChatEndpoint.send(friendListMessage,getFriendsName());
  88.             }
  89.             else {
  90.                 log.info("未找到用户username{}的session",toName);
  91.             }
  92.         } catch (IOException e) {
  93.             throw new RuntimeException(e);
  94.         }
  95.     }
  96.     private static void send(Message friendListMessage, Set friendsName) {
  97.         try {
  98.             String toName = friendListMessage.getToName();
  99.             String message = friendListMessage.getMessage();
  100.             Session session = onlineUsers.get(toName);
  101.             if (session != null) {
  102.                 String json = JSON.toJSONString(message);
  103.                 session.getBasicRemote().sendText(json);
  104.             } else {
  105.                 log.info("未找到用户{}的session", toName);
  106.             }
  107.         } catch (IOException e) {
  108.             throw new RuntimeException(e);
  109.         }
  110.     }
  111.     @OnClose
  112.    public void onclose(Session session) {
  113.         //提出session中的记录
  114.        String user = (String) this.httpSession.getAttribute("user");
  115.        onlineUsers.remove(user);
  116.        //通知所有用户,此账号下线。
  117.        String message = MessageUtils.getMessage(true, null, getFriendsName());
  118.        broadcastAllUsers(message);
  119.    }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农民

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表