WebSocket-练习1
聊天室
项目资料以及实现来自黑马教程
基础架构

看到的这些类就是初始项目的结构
UserController
java
@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;
}
}
Result
Controller响应结果
java
@Data
public class Result {
private boolean flag;
private String message;
}
User
Controller中接收用户信息
java
@Data
public class User {
private String userId;
private String username;
private String password;
}
MessageUtils
在系统准备将ResultMessage传给客户端前,将ResultMessage转换为Json字符串格式
因为系统传数据给客户端session.getBasicRemote().sendText(message);只能传字符串
java
public class MessageUtils{
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);
}
}
Message
客户端传给系统的消息格式
java
@Data
public class Message {
private String toName;
private String message;
}
ResultMessage
系统传给客户端的消息格式
java
@Data
public class ResultMessage {
private boolean isSystem;
private String fromName;
private Object message;
}
开始实现
初步构思:
-
从添加好依赖开始
-
先创建一个最基本的配置类确保我们的对话类可以被识别为WebSocket端点
-
然后编写对话类(@ServerEndPoint),包括三个基本方法
-
我们希望在登入后(连接建立后,OnOpen)将我们的用户信息广播给所有用户,让所有人知道当前在线的所有人,因此需要获取HttpSession
因为用户信息等会保存在HttpSession中(在Controller中的login中编写的)
注意WebSocketSession和HttpSession不要搞混了
此时就需要再编写一个配置类,用于获取握手时期Http请求中携带的HttpSession
-
编写新的配置类获取HttpSession并保存到WebSocket配置类对象中
上一步OnOpen中则可以通过WebSocket配置对象获取到HttpSession
再获取到HttpSession中保存的用户信息,将用户信息保存起来,后续广播显示给所有人
-
依旧在OnOpen中,将保存的所有用户名广播给所有用户
遍历每一个会话(WebSocketSession),将所有的用户名依次发送给每个用户
发送数据时就需要注意格式,这里用的就是Json,包括三个字段(参考MessageUtil和ResultMessage):
- 是否是系统消息
- 来自谁(系统消息对应null)
- 消息内容
-
OnClose中删除当前用户的会话,并再次广播给所有人
-
OnMessage中将根据Message(传给谁(假设为A)、消息内容)构建ResultMessage,拿到A的会话后将ResultMessage传给A
代码实现:
-
添加依赖
xml<!-- 添加WebSocket的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> -
写配置类
java@Configuration public class WebsocketConfig { // 注册 ServerEndpoint 到 Spring 容器 @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } -
写对话类(ServerEndPoint)
java/** * Websocket端点 * 指定路径和配置类 */ @ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfig.class) @Component public class ChatEndPoint { //保存所有用户session //通过ConcurrentHashMap确保线程安全,在HashMap基础上,加锁锁住数组节点 private static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>(); private HttpSession httpSession; /** * 建立WebSocket连接后调用 * @param session */ //这里的session是WebSocket的session,WebSocket会话的,对应长连接 //而传递用户信息的是HttpSession,这就需要用到GetHttpSessionConfig类来获取HttpSession对象 @OnOpen public void onOpen(Session session, EndpointConfig config) { System.out.println("连接建立:" + session.getId()); //保存当前用户的session(注意是HttpSession) httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName()); String user = (String) httpSession.getAttribute("user"); onlineUsers.put(user,session); //用户名就需要唯一 //广播消息,将登入的 所有用户 推送给 所有用户(看到现在有哪些人登入了) broadcastAllUsers(MessageUtils.getMessage(true,null,onlineUsers.keySet())); } private void broadcastAllUsers(String message){ //遍历Map集合,给每一个用户都发送消息(系统消息) Set<Map.Entry<String, Session>> entries = onlineUsers.entrySet(); for (Map.Entry<String, Session> entry : entries) { //获取用户session Session session = entry.getValue(); //给用户发送同步消息 try { session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } @OnMessage public void onMessage(String message/*, Session session*/){ System.out.println("收到消息:" + message); //将消息推送给指定的用户 //转换成message对象 Message msg = JSON.parseObject(message, Message.class); String toName = msg.getToName(); Session toSession = onlineUsers.get(toName); if(toSession != null) { try { String user = (String) httpSession.getAttribute("user"); String msg1 = MessageUtils.getMessage(false, user, msg.getMessage()); toSession.getBasicRemote().sendText(msg1); } catch (Exception e) { e.printStackTrace(); } } } @OnClose public void onClose(Session session) { System.out.println("连接关闭:" + session.getId()); //在onlineUsers中剔除当前用户 onlineUsers.remove((String) httpSession.getAttribute("user")); //再次广播 broadcastAllUsers(MessageUtils.getMessage(true,null,onlineUsers.keySet())); } } -
写配置类
java@Configuration public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator { /** * 获取httpSession对象,修改握手方法 * 将httpSession对象保存到ServerEndpointConfig对象中 * @param sec * @param request * @param response */ @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { //三个参数:配置对象、握手请求、握手响应 //获取httpSession对象 HttpSession httpSession = (HttpSession)request.getHttpSession(); //保存HttpSession对象到配置对象,这个Map存放的是我们自定义的数据 sec.getUserProperties().put(HttpSession.class.getName(),httpSession); } }