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 {@Beanpublic 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 {@Overridepublic 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.Configurator
:ServerEndpointConfig.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 {//开一个线程安全的Mapprivate static final Map<String,Session> onlineUsers=new ConcurrentHashMap<>();private HttpSession httpSession;/*** 广播系统消息* @param message*/private void broadcastAllUsers(String message){try {//遍历mapSet<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*/@OnOpenpublic 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*/@OnMessagepublic void onMessage(String message) throws IOException {//将消息推送给指定的用户Message msg = JSON.parseObject(message, Message.class);//获取消息接收方的用户名String toName = msg.getToName();String mess=msg.getMessage();//获取消息接收方用户对象的sessionSession session=onlineUsers.get(toName);String user=(String) httpSession.getAttribute("user");String message1 = MessageUtils.getMessage(false, user,mess);session.getBasicRemote().sendText(message1);}/*** 断开WebSocket连接时被调用* @param session*/@OnClosepublic 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端点,监听路径为/chat
。configurator = 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();}
}
- 目的: 向所有在线用户广播一条消息。
- 步骤:
- 获取
onlineUsers
中所有的条目(即用户名和Session的映射)。 - 遍历每个条目,获取对应的Session对象。
- 使用
session.getBasicRemote().sendText(message)
向每个用户发送消息。 - 捕获并打印可能发生的IO异常。
- 获取
获取在线好友列表方法
public Set getFriends() {Set<String> set = onlineUsers.keySet();return set;
}
- 目的: 返回当前在线用户的用户名集合。
- 步骤:
- 调用
onlineUsers.keySet()
获取所有在线用户的用户名集合。 - 返回这个集合。
- 调用
处理连接建立事件方法
@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连接时执行的操作。
- 步骤:
- 从
config.getUserProperties()
中获取HTTP会话对象,并赋值给this.httpSession
。 - 从
httpSession
中获取当前用户的用户名。 - 将用户名和对应的Session对象存入
onlineUsers
中。 - 构造一条包含当前在线用户信息的消息。
- 调用
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);
}
- 目的: 当客户端发送消息到服务器时执行的操作。
- 步骤:
- 解析客户端发送的JSON格式的消息,将其转换为
Message
对象。 - 从
Message
对象中提取接收方的用户名toName
和实际消息内容mess
。 - 根据接收方的用户名从
onlineUsers
中获取对应的Session对象。 - 从
httpSession
中获取当前发送消息的用户的用户名。 - 构造一条包含发送者和消息内容的消息。
- 使用
session.getBasicRemote().sendText(message1)
将消息发送给接收方。
- 解析客户端发送的JSON格式的消息,将其转换为
处理连接关闭事件方法
@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连接时执行的操作。
- 步骤:
- 从
httpSession
中获取当前用户的用户名。 - 从
onlineUsers
中移除该用户的Session对象。 - 构造一条包含当前在线用户信息的消息。
- 调用
broadcastAllUsers(message)
向所有在线用户广播这条消息。
- 从
总结
整个类的核心功能是维护一个在线用户列表,并在用户上线、下线或发送消息时进行相应的处理和通知。通过这些方法,多个客户端之间可以通过服务器转发消息,实现简单的即时通讯功能。