WebSocket-练习1

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

开始实现

初步构思:

  1. 从添加好依赖开始

  2. 先创建一个最基本的配置类确保我们的对话类可以被识别为WebSocket端点

  3. 然后编写对话类(@ServerEndPoint),包括三个基本方法

  4. 我们希望在登入后(连接建立后,OnOpen)将我们的用户信息广播给所有用户,让所有人知道当前在线的所有人,因此需要获取HttpSession

    因为用户信息等会保存在HttpSession中(在Controller中的login中编写的)

    注意WebSocketSession和HttpSession不要搞混了

    此时就需要再编写一个配置类,用于获取握手时期Http请求中携带的HttpSession

  5. 编写新的配置类获取HttpSession并保存到WebSocket配置类对象中

    上一步OnOpen中则可以通过WebSocket配置对象获取到HttpSession

    再获取到HttpSession中保存的用户信息,将用户信息保存起来,后续广播显示给所有人

  6. 依旧在OnOpen中,将保存的所有用户名广播给所有用户

    遍历每一个会话(WebSocketSession),将所有的用户名依次发送给每个用户

    发送数据时就需要注意格式,这里用的就是Json,包括三个字段(参考MessageUtil和ResultMessage):

    • 是否是系统消息
    • 来自谁(系统消息对应null)
    • 消息内容
  7. OnClose中删除当前用户的会话,并再次广播给所有人

  8. OnMessage中将根据Message(传给谁(假设为A)、消息内容)构建ResultMessage,拿到A的会话后将ResultMessage传给A

代码实现:

  1. 添加依赖

    xml 复制代码
    <!--        添加WebSocket的依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
  2. 写配置类

    java 复制代码
    @Configuration
    public class WebsocketConfig {
        // 注册 ServerEndpoint 到 Spring 容器
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    }
  3. 写对话类(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()));
        }
    }
  4. 写配置类

    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);
        }
    }
相关推荐
狂奔的sherry2 小时前
Socket vs WebSocket
网络·websocket·网络协议
sadandbad3 小时前
[vulhub靶机通关]DC-2(rbash绕过_git提权)
网络·sql·web安全·网络安全
2501_915106323 小时前
App HTTPS 抓包 工程化排查与工具组合实战
网络协议·ios·小程序·https·uni-app·php·iphone
GTgiantech4 小时前
科普SFP 封装光模块教程
服务器·网络·数据库
0和1的舞者5 小时前
网络通信的奥秘:HTTP详解 (七)
服务器·网络·网络协议·http·okhttp·软件工程·1024程序员节
Ashlee_code5 小时前
BSS供应商:电信与金融领域的幕后支撑者
大数据·网络·金融·系统架构·跨境·金融机构·场外期权
节点小宝5 小时前
节点小宝免费版流量机制解析:点对点直连技术与备用流量设计
网络·网络协议·p2p
创业之路&下一个五年7 小时前
按照ip的转换为二进制的方式理解a\b\c类地址的边界
服务器·网络·tcp/ip
陌路207 小时前
Linux29初识网络:核心概念与分层逻辑
网络