WebSocket —— 在线聊天室

文章目录

消息推送常用方式介绍

轮询

SSE(server-sent event)

websocket介绍

websocket API

前端API


后端API



实现在线聊天室

需求与最终效果展示


实现流程分析

消息格式

代码实现


HttpSession的保存传递

这二者这里可以视为一个东西!!!HttpSession通过这个来保存传递!

具体代码

依赖

pom 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

pojo

Result
java 复制代码
@Data
public class Result {

    private boolean flag;

    private String message;
}
User
java 复制代码
@Data
public class User {

    private String userId;

    private String username;

    private String password;

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

Config

WebsocketConfig
java 复制代码
@Configuration
public class WebsocketConfig {

    /**
     * ServerEndpointExporter 的作用
     *
     * 自动注册 WebSocket 端点
     * ServerEndpointExporter 会自动扫描 Spring 容器中所有带有 @ServerEndpoint 注解的类,并将它们注册为 WebSocket 端点。
     * 这意味着你不需要手动在 Spring 容器中注册这些端点,ServerEndpointExporter 会自动处理。
     *
     * 支持 Spring 的依赖注入
     * 通过 ServerEndpointExporter,Spring 容器中的 WebSocket 端点可以像其他 Spring Bean 一样使用依赖注入(DI)。
     * 例如,你可以在 WebSocket 端点类中注入其他 Spring Bean,如服务层(Service)或工具类(Utility)。
     *
     * 简化 WebSocket 配置
     * 使用 ServerEndpointExporter 可以简化 WebSocket 的配置,避免手动编写大量的配置代码。
     * 它提供了一种声明式的方式来配置 WebSocket 端点,使得代码更加简洁和易于维护。
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
GetHttpSessionConfig
java 复制代码
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
         //获取HttpSession对象
        HttpSession httpSession = (HttpSession)request.getHttpSession();
        //将httpSession对象保存起来
        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
    }

}

Controller

UserController
java 复制代码
/**
 * @CrossOrigin 是 Spring Framework 提供的一个注解,用于解决跨域资源共享(Cross-Origin Resource Sharing, CORS)问题。
 * 可以应用于 Spring MVC 控制器类或方法上,用于配置允许跨域请求的规则。
 * 它支持多种配置选项,包括允许的域、HTTP 方法、请求头等。
 */
@CrossOrigin
@RestController
@RequestMapping("/user")
public class UserController {

    /**
     *
     * @param user
     * @param session 在 Spring MVC 中,HttpSession 是通过 Spring 的 @Controller 方法参数自动注入的。当你在方法签名中声明一个 HttpSession 参数时,
     *                Spring 会自动从当前的 HTTP 请求中获取对应的 HttpSession 对象,并将其传递给方法。
     *
     * HttpSession 的生命周期
     * 创建:当第一个请求到达服务器时,如果请求中没有 JSESSIONID(表示没有已存在的会话),Spring MVC 会创建一个新的 HttpSession。
     * 存储:HttpSession 对象存储在服务器端,与客户端通过 JSESSIONID 进行关联。
     * 访问:在后续的请求中,客户端会通过 JSESSIONID 来标识当前会话,服务器会根据 JSESSIONID 找到对应的 HttpSession。
     * 销毁:当用户关闭浏览器或会话超时后,HttpSession 会被销毁。
     * @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;
    }

}

Websocket

ChatEndpoint
java 复制代码
@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfig.class)
@Component
public class ChatEndpoint {

    private static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();


    private HttpSession httpSession;
    /**
     * 建立websocket连接后,被调用
     * @param session
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        //将session进行保存
        this.httpSession = (HttpSession)config.getUserProperties().get(HttpSession.class.getName());
        String username = (String) this.httpSession.getAttribute("user");
        onlineUsers.put(username, session);

        //广播消息,需要将登录的所有用户推送给所有的用户
        String message = MessageUtils.getMessage(true, null, getFriends());
        broadcastAllUsers(message);
    }

    public Set getFriends() {
        return onlineUsers.keySet();
    }

    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 (Exception e) {
            throw new RuntimeException(e);
        }
    }

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

    }


    /**
     * 断开websocket连接时被调用
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        //从onlineUsers中剔除当前用户的session对象
        String username = (String) this.httpSession.getAttribute("user");
        onlineUsers.remove(username);

        //通知其他用户,当前用户下线了
        broadcastAllUsers(MessageUtils.getMessage(true, null, getFriends()));

    }
}

utils

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

测试

注意事项



用户登录与连接Websocket

测试




过程分析



广播时沟通传递JSON格式数据,因此message记得从实体类转为JSON

获取HttpSession对象后要保存起来成为成员变量,方便后续使用

以及要将其中username信息和对应的Session映射保存起来,方便后续使用!!!

响应



用户私聊

测试
过程分析
响应



用户下线,断开Websocket

测试
过程分析



响应


相关推荐
爱和冰阔落11 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
严肃小白兔12 小时前
Cloudflare 更新 API Token 管理:从最小权限到自动化凭据安全
网络·安全·自动化
Linux-lucky1 天前
45-Linux学习之旅之防火墙与WEB加固
linux·前端·网络·学习·ubuntu
博客zhu虎康1 天前
实现局域网P2P通信:内网发送消息
网络·网络协议·p2p
吴声子夜歌1 天前
Kubernetes权威指南——网络原理(一)
网络·容器·kubernetes
陈希1231 天前
Linux 服务器性能排查手册(2026):CPU、内存、磁盘IO、网络四大瓶颈定位
linux·服务器·网络
国科安芯1 天前
从遥控到自治:ASP4644S2B支撑星载电源健康管理与容错配电
网络·单片机·嵌入式硬件·安全·dcdc·低轨卫星·商业航天
|_⊙1 天前
网络层协议 IP
服务器·网络·tcp/ip
无言听风雨丶1 天前
Kubernetes Service Mesh 与 mTLS 实战:从网络可达走向零信任服务通信
网络·kubernetes·service_mesh
叼烟扛炮1 天前
Linux 学习笔记(六十):单线程的逆袭——epoll 高并发服务器实战
linux·服务器·网络·笔记·学习·算法