实时传输的选择方案

针对聊天、订单状态推送这类实时数据更新等场景,通常有多种解决方案

一.普通短轮询

就是固定时间轮询一次,比如设置为10秒,那就是会攒够10秒的所有信息,再统一发送

缺点:

1.无效请求占比高,资源浪费严重,假如一天就跟别人说了一句话,那么只有那一句话的轮询是有效的,其他时间的轮询就是无效轮询

2.实时性差,存在延长,如果设置时间越短,那么实时性当然就越好,但这样无效请求就更多,消耗资源就越严重,那么出于优化,将时间延长,那么这样实时性就差了

3.需要客户端不断发送请求,导致客户端卡顿,体验差,若是移动端,还会有电量消耗和流量消耗

4.无法应对突发高并发,针对突发流量,短轮询会在这一段所有进行积攒,然后就瞬间压垮服务端

二.长轮询

就是发送一次长轮询,如果服务端没有数据,就一直挂着,保持连接,直到有数据了/超时,此时会立即返回信息(超时就是空响应),并断开连接,客户端接收到响应后,又马上再发送一次长轮询,如此循环

缺点:

1.仍然是HTTP请求,每次发送轮询请求,都会消耗网络开销(三次握手/四次挥手)

2.连接挂在服务器仍然占用资源

3.有空窗期,在断开连接,到再次发送连接这一段时间,就是空窗期,实时性不如无间隙的

三.MQTT(轻量级物联网通信协议)

主要采用发布者、订阅者、代理服务器三个角色,主要用于物联网。

四.WebRTC

主要用于视频流、音频流的推送,一般不用于传输文本数据

五.WebSocket(消息推送机制)

WebSocket是一个应用层协议,与HTTP协议是并列同级的关系,是基于传输层TCP实现的一个协议,一旦连接建立完成,客户端或服务端都可以主动的向对方发送数据。

1.WebSocket的报文格式

其中FIN就是表示是否要关闭WebSocket,然后RSV有三位,作为保留位,就是现在不用,留着以后用
opcode是操作码,描述了当前WebSocket数据帧,是起到什么作用的

比如说0x1这个操作码就表示是一个文本数据,0x2表示是二进制数据

所以WebSocket既可以传输二进制数据,也可以传输文本数据
MASK代表是否开启掩码操作,掩码操作主要是为了避免"缓冲区溢出"的问题
payload length,也就是载荷长度,因为有7个bit位,所以是2^7-1=127,然后单位是字节

127字节会不会太少了呢,的确,所以payload length有三种模式

(1)7bit,当payload length<126时,采用模式1

(2)16bit,当payload length是126时,采用模式2

(3)64bit,当payload length是127时,采用模式3
payload data那就是要传输的数据了

2.WebSocket的握手过程

一开始浏览器和服务器是采用HTTP协议进行发送,浏览器给服务器发送的请求中会有一些特殊的header,分别是Connection:upgrade(升级)和Upgrade:websocket

第一个Connection表示我这次来的目的是想要从HTTP协议进行升级

第二个Upgrade表示我要升级成哪种协议,比如websocket

同理服务器也会在响应报文中,带上这两个特殊的header,表示我也愿意跟你升级一下

其中HTTP的状态码是101,101就表示协议切换

这下就建立了Websocket的连接,就可以使用websocket进行数据传输

3.WebSocket的使用

先导入依赖

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

继承TextWebSocketHandler类并重写方法

java 复制代码
package com.example.java_chatroom.api;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Component
public class TestWebSocketAPI extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 这个方法会在 websocket 连接建立成功后, 被自动调用.
        System.out.println("TestAPI 连接成功!");
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 这个方法是在 websocket 收到消息的时候, 被自动调用的.
        System.out.println("TestAPI 收到消息!" + message.toString());
        // session 是个会话, 里面就记录了通信双方是谁. (session 中就持有了 websocket 的通信连接)
        session.sendMessage(message);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // 这个方法是在连接出现异常的时候, 被自动调用的.
        System.out.println("TestAPI 连接异常!");
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 这个方法是在连接正常关闭后, 被自动调用的
        System.out.println("TestAPI 连接关闭!");
    }
}

然后再创建一个WebSocketAPI类继承TextWebSocketHandler,重写这几个方法,具体代码就根据实际业务来编写

再对websocket进行配置

java 复制代码
package com.example.java_chatroom.config;

import com.example.java_chatroom.api.TestWebSocketAPI;
import com.example.java_chatroom.api.WebSocketAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Autowired
    private TestWebSocketAPI testWebSocketAPI;

    @Autowired
    private WebSocketAPI webSocketAPI;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 通过这个方法, 把刚才创建好的 Handler 类给注册到具体的 路径上.
        // 此时当浏览器, websocket 的请求路径是 "/test" 的时候, 就会调用到 TestWebSocketAPI 这个类里的方法.
        // registry.addHandler(testWebSocketAPI, "/test");
        registry.addHandler(webSocketAPI, "/WebSocketMessage")
                // 通过注册这个特定的 HttpSession 拦截器, 就可以把用户给 HttpSession 中添加的 Attribute 键值对
                // 往我们的 WebSocketSession 里也添加一份.
                .addInterceptors(new HttpSessionHandshakeInterceptor());
    }
}

4.Websocket连接断开的判断和重连机制

Websocket内置了心跳包机制,所以在断开后就能感知到是否连接断开,并且提供了onclose和onerror回调,就能在断开后通过回调通知到业务代码

关于重连的机制有六种

(1)立即重连:优点是能够短时间内快速恢复连接,缺点是当无法重连时,会对服务器造成额外压力,导致资源的无效循环利用

(2)指数退避策略:每次重连失败后会增加延迟,采用的是指数退避策略,也就是说每一次失败等待时间会越来越久,可以减少对服务器的压力,并且最后能够重新建立上连接

(3)设置重试限制:设置最大重试次数,防止客户端不断重试连接到不可用的服务器,从而避免了不必要的资源使用

(4)超时设置:为每次重连设置合理的超时值,避免无限期等待服务器响应

(5)用户反馈:在用户界面有明确清晰的连接状态反馈

(6)错误处理:在重连失败后提供明确的错误信息

六.WebTransport

目的是解决传统协议(如:WebSocket)的一些局限性,但使用的广泛程度不如websocket

而WebTransport是基于UDP协议实现的,可以通过流API可靠地发送数据,以及数据报API不可靠地发送数据

相比于WebSocket的优势:

高级功能:WebSocket很难通过单个连接发送不同类型的数据,因为对数据进行分包的时候,无法区分数据中不同类型的编码,因此就需要建立多个连接,然后不同连接发送不同格式的数据。而WebTransport支持多个流(也就是多路复用),即可以通过一个连接发送不同类型的数据,从而减少建立和维护多个连接的开销。

改进性能:WebTransport是一种基于UDP的传输协议,比基于TCP的协议(如Websocket)性能更高,因为缺乏纠错和重传功能,延迟会更低

可靠性:虽然没有纠错和重传功能,但并非不如WebSocket可靠,在设计上就比WebSocket更可靠,为可靠的单向或双向数据传输提供了流API,由底层QUIC协议实现,通过切换为流API这个模式就包含重传、确认、无乱序等可靠机制

安全性:默认提供端到端加密,确保在传输过程中不会被截获或修改,其中一个安全功能是使用"Origin"标头, 为服务器能够验证请求是否来自可靠信源,有助于防止跨站请求伪造

相关推荐
带娃的IT创业者6 小时前
WeClaw 心跳与重连实战:指数退避算法如何让 WebSocket 在弱网环境下的连接成功率提升 67%?
python·websocket·网络协议·算法·fastapi·实时通信
理性的曜9 小时前
AI语音通话系统设计思路:从语音输入到智能回复
人工智能·python·websocket·fastapi
带娃的IT创业者14 小时前
WeClaw WebSocket 路由实战:BridgeConnectionManager 如何用四层映射在 800 个连接中实现毫秒级消息转发?
网络·python·websocket·网络协议·fastapi·实时通信
遗憾随她而去.14 小时前
前后端通信核心方案:轮询、WebSocket、SSE
网络·websocket·网络协议
yeshihouhou14 小时前
websocket实现进度条功能
网络·websocket·网络协议
xUxIAOrUIII2 天前
【WebSocket】原理介绍
网络·websocket·网络协议
NGC_66112 天前
webSocket和Socket辨析
websocket
Andy工程师2 天前
WebSocket介绍
websocket·网络协议
不会写DN3 天前
如何使用WebSocket实现一个公域聊天室?
websocket·网络协议·iphone