自学内容网 自学内容网

WebSocket协议在Java中的整合

1. 常见的消息推送方式

2.WebSocket API

3.基于WebSocket的实战(实时聊天室)

这里以解析后端代码为主,前端不作为重点,若想复现项目,请从作者的仓库中拉取代码

WebSocket-chatRoom: 基于WebSocket协议实现一个简单的聊天室

项目架构如下:

最后一个为@onclose(图上写错了)

3.1 基础环境搭建

 Resut实体类

@Data
public class Result {
    private boolean flag;
    private String message;
}

用户信息实体类

@Data
public class User {
    private String userId;
    private String username;
    private String password;
}

用户登录与获取用户信息的实现

@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;
    }
}

3.2 WebSocket配置

  WebsocketConfig 配置类
@Configuration
public class WebsocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }

}
  • @Configuration 注解:表明这个类是一个配置类,它可以包含一个或多个@Bean方法,这些方法返回的对象会被Spring容器管理。
  • serverEndpointExporter() 方法:该方法被@Bean注释标记,表示它返回的对象(在这个例子中是ServerEndpointExporter实例)将被Spring容器作为bean管理。ServerEndpointExporter的作用是扫描并注册所有使用了@ServerEndpoint注解的类,使得它们可以处理WebSocket连接。

GetHttpSessionConfig 配置类
 /**
 * ServerEndpointConfig.Configurator 是一个抽象类,提供了在WebSocket握手阶段进行自定义配置的机会。通过继承这个类,我们可以在握手过程中执行额外的操作,如获取HTTP会话等。
 */
@Configuration
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {


    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
          //获取HttpSession对象
          HttpSession httpSession= (HttpSession) request.getHttpSession();
          //将httpSession对象保存起来
         //我们将获取到的HttpSession对象存储在UserProperties集合中。这样做的目的是为了让后续的WebSocket消息处理方法能够访问到这个HTTP会话信息,例如用于身份验证或会话跟踪。
          sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
    }
}
  • 继承自 ServerEndpointConfig.ConfiguratorServerEndpointConfig.Configurator 是一个抽象类,提供了在WebSocket握手阶段进行自定义配置的机会。通过继承这个类,我们可以在握手过程中执行额外的操作,如获取HTTP会话等。
  • modifyHandshake 方法:这是Configurator类中的一个重写方法,它允许我们在WebSocket握手阶段对连接进行修改。具体来说,在这里我们做了两件事:
    • 获取 HttpSession 对象:通过调用request.getHttpSession(),我们可以从握手请求中获得当前的HTTP会话。这在需要将WebSocket连接与特定用户的HTTP会话关联起来时非常有用。
    • 将 HttpSession 对象保存到 UserProperties 中:通过sec.getUserProperties().put(HttpSession.class.getName(), httpSession),我们将获取到的HttpSession对象存储在UserProperties集合中。这样做的目的是为了让后续的WebSocket消息处理方法能够访问到这个HTTP会话信息,例如用于身份验证或会话跟踪。
总结

这两段配置共同实现了以下功能:

  • 自动扫描并注册所有使用了@ServerEndpoint注解的类,使其成为WebSocket端点。
  • 在WebSocket握手阶段,获取当前用户的HTTP会话信息,并将其与WebSocket连接关联起来,以便在后续的消息交换中可以利用这些会话数据。

这种方式特别适用于需要在WebSocket通信中保持用户状态的应用场景,比如实时聊天应用、在线游戏等。通过这种方式,开发者可以确保WebSocket连接与用户的HTTP会话紧密关联,从而实现更安全、个性化的服务。

3.3 消息的处理

定义两个消息对象
 /**
 * 用于封装浏览器发送给服务端的消息数据
 */
@Data
public class Message {
    private String toName;
    private String message;
}

 /**
 * 用来封装服务端给浏览器发送的消息数据
 */
@Data
public class ResultMessage {

    private boolean isSystem;
    private String fromName;
    private Object message;//如果是系统消息是数组

}
定义消息的工具类
public class MessageUtils {

     /**
     * @param isSystemMessage 是否是系统消息。只有广播消息才是系统消息。如果是私聊消息的话,就不是系统消息
     * @param fromName 给谁发消息,如果是系统消息的话,这个参数不需要指定
     * @param message 消息的具体内容
     * @return
     */
    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(value="/chat",configurator = GetHttpSessionConfig.class)
@Component
public class ChatEndpoint {

    //开一个线程安全的Map
    private static final Map<String,Session> onlineUsers=new ConcurrentHashMap<>();

    private HttpSession httpSession;


     /**
     * 广播系统消息
     * @param message
     */
    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) {
            e.printStackTrace();
        }

    }


    /**
     * 返回所有在线用户的用户名集合。
     * @return
     */
    public Set getFriends() {
        Set<String> set = onlineUsers.keySet();
        return set;
    }




     /**
     * 建立WebSocket连接后调用
     * @param session
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config){     //config与配置类中的sec是一个对象
        //将session保存
        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,getFriends());
        broadcastAllUsers(message);
    }



     /**
     * 浏览器发送消息到服务端,该方法被调用
     * @param message
     */
    @OnMessage
    public void onMessage(String message) throws IOException {
        //将消息推送给指定的用户
        Message msg = JSON.parseObject(message, Message.class);
        //获取消息接收方的用户名
        String toName = msg.getToName();
        String mess=msg.getMessage();
        //获取消息接收方用户对象的session
        Session session=onlineUsers.get(toName);
        String user=(String) httpSession.getAttribute("user");
        String message1 = MessageUtils.getMessage(false, user,mess);
        session.getBasicRemote().sendText(message1);

    }




     /**
     * 断开WebSocket连接时被调用
     * @param session
     */
    @OnClose
    public void onClose(Session session){
       //从onlineUsers中移除当前用户的session对象(用户退出)
        String user=(String) httpSession.getAttribute("user");
        onlineUsers.remove(user);
       //通知其他所有用户,当前用户下线
        String message = MessageUtils.getMessage(true, null,getFriends());
        broadcastAllUsers(message);
    }



}

下面是对代码的每一步思路进行详细解释:

类定义与注解

@ServerEndpoint(value="/chat", configurator = GetHttpSessionConfig.class)
@Component
public class ChatEndpoint {
  • @ServerEndpoint(value="/chat", configurator = GetHttpSessionConfig.class): 这个注解表明ChatEndpoint类是一个WebSocket端点,监听路径为/chatconfigurator = GetHttpSessionConfig.class指定了一个配置器,用于获取HTTP会话信息。
  • @Component: 这个注解将ChatEndpoint类声明为Spring的一个组件,这样Spring容器可以自动发现并管理它。
成员变量
private static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();
private HttpSession httpSession;
  • onlineUsers: 一个线程安全的Map,键是用户名,值是对应的WebSocket Session对象。使用ConcurrentHashMap确保多线程环境下的安全性。
  • httpSession: 存储当前用户的HTTP会话对象,用于获取用户的相关信息(如用户名)。
广播系统消息方法
private void broadcastAllUsers(String message) {
    try {
        Set<Map.Entry<String, Session>> entries = onlineUsers.entrySet();
        for (Map.Entry<String, Session> entry : entries) {
            Session session = entry.getValue();
            session.getBasicRemote().sendText(message);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  • 目的: 向所有在线用户广播一条消息。
  • 步骤:
    1. 获取onlineUsers中所有的条目(即用户名和Session的映射)。
    2. 遍历每个条目,获取对应的Session对象。
    3. 使用session.getBasicRemote().sendText(message)向每个用户发送消息。
    4. 捕获并打印可能发生的IO异常。
获取在线好友列表方法
public Set getFriends() {
    Set<String> set = onlineUsers.keySet();
    return set;
}
  • 目的: 返回当前在线用户的用户名集合。
  • 步骤:
    1. 调用onlineUsers.keySet()获取所有在线用户的用户名集合。
    2. 返回这个集合。
处理连接建立事件方法
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
    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, getFriends());
    broadcastAllUsers(message);
}
  • 目的: 当客户端与服务器建立WebSocket连接时执行的操作。
  • 步骤:
    1. config.getUserProperties()中获取HTTP会话对象,并赋值给this.httpSession
    2. httpSession中获取当前用户的用户名。
    3. 将用户名和对应的Session对象存入onlineUsers中。
    4. 构造一条包含当前在线用户信息的消息。
    5. 调用broadcastAllUsers(message)向所有在线用户广播这条消息。
处理接收到的消息方法
@OnMessage
public void onMessage(String message) throws IOException {
    Message msg = JSON.parseObject(message, Message.class);
    String toName = msg.getToName();
    String mess = msg.getMessage();
    Session session = onlineUsers.get(toName);
    String user = (String) httpSession.getAttribute("user");
    String message1 = MessageUtils.getMessage(false, user, mess);
    session.getBasicRemote().sendText(message1);
}
  • 目的: 当客户端发送消息到服务器时执行的操作。
  • 步骤:
    1. 解析客户端发送的JSON格式的消息,将其转换为Message对象。
    2. Message对象中提取接收方的用户名toName和实际消息内容mess
    3. 根据接收方的用户名从onlineUsers中获取对应的Session对象。
    4. httpSession中获取当前发送消息的用户的用户名。
    5. 构造一条包含发送者和消息内容的消息。
    6. 使用session.getBasicRemote().sendText(message1)将消息发送给接收方。
处理连接关闭事件方法
@OnClose
public void onClose(Session session) {
    String user = (String) httpSession.getAttribute("user");
    onlineUsers.remove(user);
    String message = MessageUtils.getMessage(true, null, getFriends());
    broadcastAllUsers(message);
}
  • 目的: 当客户端断开WebSocket连接时执行的操作。
  • 步骤:
    1. httpSession中获取当前用户的用户名。
    2. onlineUsers中移除该用户的Session对象。
    3. 构造一条包含当前在线用户信息的消息。
    4. 调用broadcastAllUsers(message)向所有在线用户广播这条消息。
总结

整个类的核心功能是维护一个在线用户列表,并在用户上线、下线或发送消息时进行相应的处理和通知。通过这些方法,多个客户端之间可以通过服务器转发消息,实现简单的即时通讯功能。


原文地址:https://blog.csdn.net/2303_80556719/article/details/143821808

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!