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.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 {
//开一个线程安全的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端点,监听路径为/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)向所有在线用户广播这条消息。
- 从
总结
整个类的核心功能是维护一个在线用户列表,并在用户上线、下线或发送消息时进行相应的处理和通知。通过这些方法,多个客户端之间可以通过服务器转发消息,实现简单的即时通讯功能。


















