黑马在线聊天室(websocket)

黑马在线聊天室(websocket)

消息推送常见方式

1. 轮询方式

  • 轮询是一种客户端与服务器之间实时通信的技术手段。客户端定期发送请求来查询服务器是否有新数据或事件,并将响应返 回给客户端。如果服务器有新的数据或事件,则将其返回给客户端;如果没有,则返回一个空响应。客户端收到响应后,可 以处理数据或事件,并根据需要继续发送下一个请求。
  • 长轮询是一种改进的轮询技术,其主要目的是降低轮询过程中的资源消耗和延迟。长轮询的基本原理是客户端发送一个 HTTP请求给服务器,并保持连接打开,直到服务器有新的数据或事件时才返回响应给客户端。在这期间,服务器会一直保持连接打开,直到超时或有新数据或事件

在这里插入图片描述

2. SSE(server-sent event):服务器发送事件

  • SSE在服务器和客户端之间打开一个单向通道

  • 服务端响应的不再是一次性的数据包,而是text/event-stream类型的数据流信息

  • 服务器有数据变更时将数据流式传输到客户端

在这里插入图片描述

3. websocket

WebSocket是一种网络通信协议,提供了一种在单个连接上进行全双工通信的途径。与HTTP不同,WebSocket在客户端和服务器之间建立一个持久的连接,这个连接在任意一方关闭之前都保持打开状态,允许数据在两个方向上实时流动。

在这里插入图片描述

  • 全双工(Full Duplex):允许数据在两个方向上同时传输。

  • 半双工(Half Duplex):允许数据在两个方向上传输,但是同一个时间段内只允许一个方向上传输。


WebSocket应用场景:
  • 实时游戏

  • 聊天应用

  • 实时通知服务

  • 实时金融市场数据

  • 实时协作工具


WebSocket原理解析:

在这里插入图片描述

在 WebSocket 协议中,建立连接时进行一次“握手”。这个握手过程与HTTP协议类似,但使用的是 WebSocket 协议的特定头部格式。在客户端发送 WebSocket 请求时,其请求头部包括一个特殊的 “Upgrade” 标识,表示希望升级连接为 WebSocket。同时还需要包含一个 “Sec-WebSocket-Key”,它是随机生成的一段字符串,用于在服务器端验证请求的合法性。

当服务器收到这个 WebSocket 请求时,会进行一次握手确认,响应头部包含一个 “Upgrade” 标识,并指明应用的协议是 WebSocket。响应头部还包含一个 “Sec-WebSocket-Accept”,这是一个根据客户端请求头部 的 “Sec-WebSocket-Key” 计算的特殊字符串。如果服务器验证通过,那么连接就被升级为 WebSocket 协议。

在建立连接之后,客户端和服务器之间可以进行任意的双向通信。WebSocket 协议支持文本和二进制数据的传输,因此可以在浏览器端实现实时聊天、游戏、文件传输等各种应用。

客户端【浏览器】API

websocket对象创建

​ let ws = new WebSocket(URL);

URL说明:

  • 格式:协议://ip地址/访问路径
  • 协议:协议名称为 ws

websocket对象相关事件

事件事件处理程序描述
openws.onopen连接建立时触发
messagews.onmessage客户端接收到服务器发送的数据时触发
closews.onclose连接关闭时触发

websocket对象提供的方法

方法名称描述
send()通过websocket对象调用该方法发送数据给服务端

前端发送请求

   async init() {
                await axios.get("user/getUsername").then(res => {
                    this.username = res.data;
                })
 //创建webSocket对象
            ws = new WebSocket("ws://localhost/chat");

            //给ws绑定事件
            ws.onopen = this.onopen;
            //接收到服务端推送的消息后触发
            ws.onmessage = this.onMessage;

            ws.onclose = this.onClose;
        }

服务端 API

Tomcat的7.0.5 版本开始支持WebSocket,并且实现了Java WebSocket规范。

Java WebSocket应用由一系列的Endpoint组成。Endpoint 是一个java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口。


我们可以通过两种方式定义Endpoint:

  • 第一种是编程式, 即继承类 javax.websocket.Endpoint并实现其方法。

  • 第二种是注解式, 即定义一个POJO, 并添加 @ServerEndpoint相关注解。


Endpoint实例在WebSocket握手时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束。在Endpoint接口中明确定义了与其生命周期相关的方法, 规范实现者确保生命周期的各个阶段调用实例的相关方法。生命周期方法如下:

方法描述注解
onOpen()当开启一个新的会话时调用,该方法是客户端与服务端握手成功后调用的方法@OnOpen
onClose()当会话关闭时调用@OnClose
onError()当连接过程异常时调用@OnError

服务端如何接收客户端发送的数据呢?

  • 编程式:通过添加 MessageHandler 消息处理器来接收消息
  • 注解式:在定义Endpoint时,通过@OnMessage注解指定接收消息的方法

服务端如何推送数据给客户端呢?

发送消息则由 RemoteEndpoint 完成, 其实例由 Session 维护。

发送消息有2种方式发送消息

  • 通过session.getBasicRemote 获取同步消息发送的实例 , 然后调用其 sendXxx()方法发送消息
  • 通过session.getAsyncRemote 获取异步消息发送实例,然后调用其 sendXxx() 方法发送消息

Endpoint示例

@ServerEndpoint("/chat")
@Component
public class ChatEndpoint {
    @OnOpen
    //连接建立时被调用
    public void onOpen(Session session, EndpointConfig config){}
    @OnMessage
    //接收到客户端发送的数据时被调用
    public void onMessage(String message){}
    @OnClose
    //连接关闭时被调用
    public void onClose(Session session){}
}

ChatEndpoint类通过实现WebSocket协议,用于处理客户端的连接、消息传递和关闭事件。

在线聊天室实现

流程分析

在这里插入图片描述

消息格式

在这里插入图片描述

代码实现

  1. 引入坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    
  2. 编写配置类,扫描添加有@ServerEndpoint注解的 Bean

@Configuration
public class WebsocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

通过在配置类中定义一个 ServerEndpointExporter 的 @Bean 方法,Spring 会自动创建一个 ServerEndpointExporter 实例,并将其加入到 Spring 容器中。Spring框架会在启动时通过这个实例自动扫描项目中所有使用@ServerEndpoint注解的类,并将它们注册为WebSocket端点

  1. 编写配置类,用于获取 HttpSession 对象
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);
    }
}

GetHttpSessionConfig类用于在WebSocket握手过程中获取HTTP会话(HttpSession)对象,并将其保存到用户属性中,以便在WebSocket会话中使用。

  1. ChatEndpoint

    package com.itheima.ws;
    
    import com.alibaba.fastjson.JSON;
    import com.itheima.config.GetHttpSessionConfig;
    import com.itheima.utils.MessageUtils;
    import com.itheima.ws.pojo.Message;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpSession;
    import javax.websocket.*;
    import javax.websocket.server.ServerEndpoint;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * @version v1.0
     * @ClassName: ChatEndpoint
     * @Description: TODO(一句话描述该类的功能)
     * @Author: 黑马程序员
     */
    @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) {
            //1,将session进行保存
            this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
            String user = (String) this.httpSession.getAttribute("user");
            onlineUsers.put(user,session);
            //2,广播消息。需要将登陆的所有的用户推送给所有的用户
            String message = MessageUtils.getMessage(true,null,getFriends());
            broadcastAllUsers(message);
        }
    
        public Set getFriends() {
            Set<String> set = onlineUsers.keySet();
            return set;
        }
    
        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) {
                //记录日志
            }
        }
    
        /**
         * 浏览器发送消息到服务端,该方法被调用
         *
         * 张三  -->  李四
         * @param message
         */
        @OnMessage
        public void onMessage(String message) {
            try {
                //将消息推送给指定的用户
                Message msg = JSON.parseObject(message, Message.class);
                //获取 消息接收方的用户名
                String toName = msg.getToName();
                String mess = msg.getMessage();
                //获取消息接收方用户对象的session对象
                Session session = onlineUsers.get(toName);
                String user = (String) this.httpSession.getAttribute("user");
                String msg1 = MessageUtils.getMessage(false, user, mess);
                session.getBasicRemote().sendText(msg1);
            } catch (Exception e) {
                //记录日志
            }
        }
    
        /**
         * 断开 websocket 连接时被调用
         * @param session
         */
        @OnClose
        public void onClose(Session session) {
            //1,从onlineUsers中剔除当前用户的session对象
            String user = (String) this.httpSession.getAttribute("user");
            onlineUsers.remove(user);
            //2,通知其他所有的用户,当前用户下线了
            String message = MessageUtils.getMessage(true,null,getFriends());
            broadcastAllUsers(message);
        }
    }
    

@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfig.class) 这行代码声明了一个 WebSocket 端点,客户端可以通过 /chat 路径与之建立连接,并且在握手阶段使用 GetHttpSessionConfig 类来进行自定义配置。

该类是Spring Boot应用中的一个WebSocket端点,它用于处理聊天功能。主要功能包括:

  • 在用户建立WebSocket连接时保存用户信息和会话对象。
  • 当用户发送消息时,将消息转发给指定的接收用户。
  • 当用户断开连接时,从在线用户列表中移除用户并通知其他用户。
  • 使用GetHttpSessionConfig配置器来获取HTTP会话中的用户信息。

userController

package com.itheima.controller;

import com.itheima.pojo.Result;
import com.itheima.pojo.User;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;


@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

package com.itheima.pojo;

import lombok.Data;

/**
 * @version v1.0
 * @ClassName: Result
 * @Description: 用来封装http请求的响应数据
 * @Author: 黑马程序员
 */
@Data
public class Result {
    private boolean flag;
    private String message;
}

User

package com.itheima.pojo;

import lombok.Data;

/**
 * @version v1.0
 * @ClassName: User
 * @Description: 接收登录请求的数据
 * @Author: 黑马程序员
 */
@Data
public class User {

    private String userId;
    private String username;
    private String password;
}

MessageUtils

package com.itheima.utils;

import com.alibaba.fastjson.JSON;
import com.itheima.ws.pojo.ResultMessage;

/**
 * @version v1.0
 * @ClassName: MessageUtils
 * @Description: 封装json格式消息的工具类
 * @Author: 黑马程序员
 */
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

package com.itheima.ws.pojo;

import lombok.Data;

/**
 * @version v1.0
 * @ClassName: Message
 * @Description: 用于封装浏览器发送给服务端的消息数据
 * @Author: 黑马程序员
 */
@Data
public class Message {
    private String toName;
    private String message;
}

ResultMessage

package com.itheima.ws.pojo;

import lombok.Data;

/**
 * @version v1.0
 * @ClassName: ResultMessage
 * @Description: 用来封装服务端给浏览器发送的消息数据
 * @Author: 黑马程序员
 */
@Data
public class ResultMessage {

    private boolean isSystem;
    private String fromName;
    private Object message;//如果是系统消息是数组
}
  • 20
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值