websoket 学习笔记

目录

基本概念

工作原理

优势

应用场景

[HTTP协议与 webSoket协议之间的对比](#HTTP协议与 webSoket协议之间的对比)

消息推送场景

[1. 轮询(Polling)](#1. 轮询(Polling))

[2. 长轮询(Long Polling)](#2. 长轮询(Long Polling))

[3. 服务器发送事件(Server-Sent Events, SSE)](#3. 服务器发送事件(Server-Sent Events, SSE))

[4. WebSocket](#4. WebSocket)

服务端WebSoket

基本介绍

引入依赖

核心API示例

聊天消息demo流程分析

配置类

webSocketConfig(注入ServerEndpoint注解)

GetHttpSessionConfig(存储Session会话对象)

服务类

Server端Socket

客户端


WebSocket 是一种基于 TCP 的网络通信协议,允许在客户端和服务器之间建立持久的双向通信连接。

基本概念

  • 全双工通信(Full Duplex):WebSocket 支持客户端和服务器在同一连接上同时发送和接收数据,允许数据在两个方向上同时传输。

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

  • 持久连接:通过一次握手建立连接后,连接会一直保持,无需每次通信都重新建立

  • 低延迟与高效性:减少了传统 HTTP 请求-响应模式中的频繁连接开销,数据传输更高效

工作原理

  1. 握手阶段:客户端通过 HTTP 请求向服务器发起 WebSocket 升级请求,服务器响应后将连接升级为 WebSocket
  • 数据传输:连接建立后,数据以帧的形式在客户端和服务器之间传输

  • 关闭连接:当一方关闭连接时,WebSocket 会发送关闭帧,通知对方关闭连接

优势

  • 实时性:适合需要快速响应和更新的场景
  • 节省资源:减少了不必要的网络请求和响应,降低了带宽和服务器资源消耗

  • 跨平台支持:主流浏览器和服务器端语言均支持 WebSocket

应用场景

  • 实时通信:如在线聊天、实时游戏
  • 数据推送:服务器主动向客户端推送数据,如股票行情、天气预报

  • 实时监控:如视频监控、设备状态监控

WebSocket 是现代 Web 开发中实现高效实时通信的重要技术,广泛应用于各种需要快速数据交互的场景。

HTTP协议与 webSoket协议之间的对比

用到最多的其中一个场景就是 消息推送。

消息推送场景

消息推送是指服务器主动向客户端发送信息的技术。以下是几种常见的消息推送方式:

1. 轮询(Polling)

  • 原理:客户端每隔固定时间向服务器发送请求,查询是否有新消息。

  • 优点

    • 实现简单,兼容性好,不需要额外的服务器支持。
  • 缺点

    • 频繁请求会增加服务器负担,浪费带宽。

    • 实时性较差,消息延迟取决于轮询间隔。

  • 适用场景:对实时性要求不高的场景,如邮件通知。

2. 长轮询(Long Polling)

  • 原理:客户端向服务器发送请求,服务器保持连接打开,直到有新消息才返回响应。

  • 优点

    • 减少了无效请求,相比普通轮询更节省资源。

    • 实时性更好。

  • 缺点

    • 服务器需要保持大量连接,对服务器资源要求较高。

    • 如果消息间隔过长,可能导致连接超时。

  • 适用场景:对实时性有一定要求但不希望使用复杂技术的场景。

3. 服务器发送事件(Server-Sent Events, SSE)

原理

  • 服务器通过 HTTP 连接向客户端推送数据,客户端通过 EventSource 接收消息。

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

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

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

优点

  • 实现简单,基于 HTTP,兼容性较好。

  • 只支持单向通信(服务器到客户端),适合通知类应用。

缺点

  • 不支持双向通信。

  • 不支持跨域,需要服务器和客户端在同一个域下。

适用场景:股票行情、新闻推送等单向通知场景。

4. WebSocket

  • 原理:通过一次握手建立持久的双向通信连接,客户端和服务器可以随时发送和接收消息。

  • 优点

    • 全双工通信,实时性高。

    • 数据传输效率高,减少了 HTTP 请求的开销。

  • 缺点

    • 实现复杂,需要服务器和客户端都支持 WebSocket 协议。

    • 需要额外处理连接的建立和关闭。

  • 适用场景:在线聊天、实时游戏、实时协作工具等。

不同消息推送方式各有优缺点,选择时需要根据具体需求和场景进行权衡。例如:

  • 如果对实时性要求不高,可以选择轮询或长轮询。

  • 如果需要高效实时的双向通信,WebSocket 是更好的选择。

服务端WebSoket

基本介绍

引入依赖

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

核心API示例

聊天消息demo流程分析

配置类

webSocketConfig(注入ServerEndpoint注解)

java 复制代码
package com.angindem.server.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
    @Bean
//    注入ServerEndpointExporter,自动注册使用@ServerEndpoint注解的
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

GetHttpSessionConfig(存储Session会话对象)

java 复制代码
package com.angindem.server.config;

import com.angindem.common.utils.CommonUtils;
import com.angindem.common.utils.IpUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.websocket.HandshakeResponse;
import jakarta.websocket.server.HandshakeRequest;
import jakarta.websocket.server.ServerEndpointConfig;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
//         获取 HttpSession 对象
        HttpSession httpSession = (HttpSession) request.getHttpSession();

        // 获取当前请求的HttpServletRequest
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest req = attributes.getRequest();
        
        
        if (httpSession == null) httpSession = req.getSession();
        String ip = IpUtils.getIpAddress(req);
        httpSession.setAttribute("user", CommonUtils.getRandomString(8));
        
//        将 HttpSession 对象存储到 ServerEndpointConfig 中
        sec.getUserProperties().put(HttpSession.class.getName(), httpSession);// 将HttpServletRequest存储到EndpointConfig的用户属性中

        sec.getUserProperties().put(String.class.getName(), ip);
    }
}

服务类

Server端Socket

java 复制代码
package com.angindem.server.wx;
import com.alibaba.fastjson2.JSON;
import com.angindem.common.utils.MessageUtils;
import com.angindem.server.config.GetHttpSessionConfig;
import com.angindem.server.wx.pojo.Message;
import jakarta.servlet.http.HttpSession;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfig.class)
public class ChatEndPoint {

    //    ConcurrentHashMap 高并发的多线程访问的哈希Map
    public static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();
    private HttpSession httpSession;

    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        log.info("连接建立成功");
//        1、将 Session 放入到集合中,方便后续使用
        this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        String  reqIp = (String) config.getUserProperties().get(String.class.getName());

        String user =  MessageUtils.getNowSessionUserName(this.httpSession);
        onlineUsers.put(user, session);
//        2、广播消息。需要将登录的所有用户推送给所有用户
        log.info("当前在线人数:{}", onlineUsers.size());
        log.info("当前用户:{},来自:{}", user, reqIp);
        String message = MessageUtils.getDataMessage("system", user, MessageUtils.getFrinds());
        MessageUtils.broadcastAllUsers(message);
    }

    @OnMessage
    public void onMessage(String message) {
        log.info("收到消息:{}", message);
        Message msg = JSON.parseObject(message, Message.class);
        // 获取 消息接收方的用户名
        String toName = msg.getToName();
        String mess = msg.getMessage();
        String user =  MessageUtils.getNowSessionUserName(this.httpSession);
        String sendMessage = MessageUtils.getMessage("user", user, mess);
        // 获取消息接收方用户对象的 session 对象
//            Session session = onlineUsers.get(toName);
//            session.getBasicRemote().sendText(sendMessage);
        MessageUtils.broadcastAllUsers(sendMessage);
    }
    @OnClose
    public void onClose(Session session) {
        log.info("连接关闭");
//        1、从 onlineUsers 中剔除当前的 session 对象
        String user =  MessageUtils.getNowSessionUserName(this.httpSession);
        onlineUsers.remove(user);
//        2、通知其它所有的用户,当前用户已经下线
        log.info("当前在线人数:{}", onlineUsers.size());
        String message = MessageUtils.getDataMessage("system", user, MessageUtils.getFrinds());
        MessageUtils.broadcastAllUsers(message);
    }
}

客户端

(VueUse的 webSocket 客户端)

javascript 复制代码
/**
 * status 是连接的状态值
 * data 是 socket 推送过来的消息
 * send 是一个方法, 用来发送消息给后台
 * close 是一个方法,关闭 socket 连接
 * open 如果当前的websocket是活跃的,将会关闭它重新打开一个新的
 */
const { status, data, close, open, send } = useWebSocket(`ws://localhost:9898/chat`, {
        onConnected: function (ws) {
            console.log('websocket 连接成功!', ws)
        },
        onDisconnected: function (ws, event) {
            console.log('onDisconnected')
        },
        onError: function (ws, event) {
            console.log('onError', event)
        },

        onMessage: function (ws, event) {
            console.log('event.data', event.data)
            if (event.data) {
                // messageInfo.value = []
                const info = JSON.parse(event.data)
                console.log(info) // 这里就是websocket每次发送的消息

                if (info.data) updateUsers(info.data);
                if (info.message) messageInfo.value.push(info)
            }
        },
        heartbeat: false,
        autoClose: false, // 自动关闭连接
    });

扩展:

同时可以通过 vue 中的 watch 监听发送过来的数据

javascript 复制代码
import {  watch } from 'vue';
//获取数据时必须要监视,此处的data就是上面结构出的data
watch(data, () => {
  //获取到的数据为data.value
    console.log(data.value)
})

发送消息到服务端

javascript 复制代码
//需要发送给服务器的数据
const info = "你好吗?"
 
//send方法,此处的send为上面解构出的send
send(info)
相关推荐
虾球xz37 分钟前
游戏引擎学习第225天
学习·游戏引擎
美味的大香蕉5 小时前
Spark SQL
笔记
轻闲一号机7 小时前
【机器学习】机器学习笔记
人工智能·笔记·机器学习
天下琴川7 小时前
Dify智能体平台源码二次开发笔记(5) - 多租户的SAAS版实现(2)
人工智能·笔记
CarnivoreRabbit9 小时前
Beamer-LaTeX学习(教程批注版)【2】
学习·latex·beamer
凡人的AI工具箱9 小时前
PyTorch深度学习框架60天进阶学习计划 - 第41天:生成对抗网络进阶(三)
人工智能·pytorch·python·深度学习·学习·生成对抗网络
workworkwork勤劳又勇敢9 小时前
Adversarial Attack对抗攻击--李宏毅机器学习笔记
人工智能·笔记·深度学习·机器学习
寻丶幽风10 小时前
论文阅读笔记——Generating Long Sequences with Sparse Transformers
论文阅读·笔记·语言模型·transformer·稀疏自注意力
LVerrrr11 小时前
Missashe考研日记-day18
学习·考研