Web 高级技术 - SSE、WebSocket、WebTransport
Web 高级技术
新:
new WebSocketStream(WSS_URL) 比 new WebSocket(WSS_URL) 多个控制读速的能力。
Jetty 13+ 可能实现 WebSocket HTTP/3 CONNECT(RFC 9220) - https://github.com/jetty/jetty.project/issues/14294
Chrome 148+ 执行 window.crypto.subtle.importKey(... ChaCha20-Poly1305 ...) 报 Unsupported import key format for algorithm. 暂用 libsodium.js/sodium.js 替代。
常用、易忘
TCP 连接会在两个钟头(Linux tcp_keepalive_time/Windows KeepAliveTime)无通讯时,发送探活包,通畅则再延俩钟头;但路由器等中间节点可能会主动断开,故假定超时重连总会发生,云端资源充分就每分钟在业务层主动探活一次,不够充分就宽松至最大十分钟。
首选 - 用心跳(10分钟间隔)和增加超时(暂定60分钟)来抑制断开次数,来进行保活。
SSE
超时:
若本地上了代理或前端转发,可能有自己的断连规则,故需要捕获错误并处理: sse.onerror = (e) => { console.error(e); };
[亲测 v2rayN + Nginx + Quarkus]约 15 分钟无数据传输则会报“net::ERR_CONNECTION_CLOSED”并重连一次,之后无传输则再次重连下,若再超时将触发“net::ERR_HTTP2_PING_FAILED”;经测,不用代理连上后,30分钟后才报 net::ERR_HTTP2_PROTOCOL_ERROR。
每隔10分钟由服务端发送一次心跳:
// 如果非 quarkus-resteasy-reactive 库,则只能用 Executors 起线程遍历探活了。 sse.newEventBuilder().comment("ping").build() 等同下行拼接字符。
var sseHeartbeat = Multi.createFrom().ticks().every(Duration.ZERO).onItem().transform((_) -> ": ping\n\n").onFailure().recoverWithCompletion();
return io.smallrye.mutiny.Multi.createBy().merging().streams(Multi.createFrom().items("Hi", "Friend!"), sseHeartbeat);
Nginx配置参考 - https://eamonyin.blog.csdn.net/article/details/161930326
WebTransport
场景:
游戏玩家坐标、对内语音等 - 通过数据报(Datagrams)无连接(Connectionless)的覆盖数据;有时候为了防止作弊,只发送按键状态,让服务端来计算最终位置。
概念:
字节流(Streams)负责可靠传输、数据报(Datagrams)负责非连接型传输,前者需要像 TCP 那样处理粘包、拆包,后者则天生有边界,无需魔数(Magic Number)和长度前缀,前者检查数据头是否存在魔数,但不进行帧同步,直接重连。
帧同步搜寻(Frame Sync Hunting) - 由于串口信号会被干扰,且不便于断开重连,故需要魔数定界,抛弃魔数前数据,同时尾部还应添加 CRC / Checksum 校验,毕竟没有 TCP 内置的完整性校验。
单片机因计算资源紧张,故用 CRC 校验,而 PC 可通过 ChaCha20-Poly1305 加解密原文来保证完整性。
Netty 对于魔数错位的标准做法,是用 LengthFieldBasedFrameDecoder 进行切包,一旦发现魔数对不上,就直接 ctx.close() 重连;发送时添加长度至包头用 LengthFieldPrepender。
流程:
首先进行 QUIC TLS 握手(Handshake),校验后 Client 侧会请求一条可靠的 QUIC“单向控制流”(Control Streams),交换 HTTP/3 SETTINGS 帧(SETTINGS_ENABLE_WEBTRANSPORT = 1、SETTINGS_H3_DATAGRAM = 1),之后客户端请求一条新的 QUIC 双向流,发送 :protocol: webtransport 头的 HTTP/3 CONNECT 请求,服务端响应 200 OK 即连接成功。
Client:
JS:
获取 MTU 最大字节 - _WebTransport.datagrams.maxDatagramSize 或写死安全范围内的 1200 字节。
用 https://github.com/msgpack/msgpack-javascript 库序列化 JS 对象,通过可靠字节流方式传递给 WebTransport Server 端。
C#:
序列化库
Server:
Java - org.msgpack:msgpack-core 序列化库。
其他
IPv6 规定中间路由器不再负责分片,如果一个大 UDP 包遇到了一个 MTU 较小的路由器,这个包会被直接丢弃,并返回一个 ICMPv6 "Packet Too Big" 消息。