websocket

1. WebSocket介绍

  • WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。

  • WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

  • HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。

  • 这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

  • 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步 AJAX 请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

2. websocket协议

  • 本协议有两部分:握手和数据传输。

  • 握手是基于http协议的。

  1. 客户端(浏览器)实现

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>
Message
@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;
Result
@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);
   }

热门相关:最强狂兵   朕是红颜祸水   梦回大明春   修仙界最后的单纯   法医王妃不好当!