fix(relay): 按首帧分流上游 —— Hash Request 走 c2(大厅),EnterSpace 走 c1(二次进空间)
- 取证:短命连接是「复用首个会话哈希、跳过 Hash Request 直接 EnterSpace」,被服务器 code=1000 关闭(约 1.2 秒);完整握手的连接可稳定存活(2 分半) - wss_server:上游改为「首帧到达后再建」——StreamDecoder 记录 FirstCmdId,首帧 ID=3 走备用上游、其余走主上游;首帧仍走 Pending 原序补发 - 新增 --upstream-alt / -UpstreamAlt(默认 wss://c1.3dtank.com:9090/)
This commit is contained in:
1 parent
529868b194
commit
941dde0913
2 files changed
+44
-22
No files matched your search
@@ -26,7 +26,8 @@
|
||||
param(
|
||||
[int]$MirrorPort = 8080,
|
||||
[int]$RelayPort = 9091,
|
||||
[string]$Upstream = 'wss://c2.3dtank.com:9090/', # 官方客户端实际连 c2(c1/c2 是两个独立服;要切回 c1 用 -Upstream wss://c1.3dtank.com:9090/)
|
||||
[string]$Upstream = 'wss://c2.3dtank.com:9090/', # 主上游(大厅:首帧 = Hash Request)
|
||||
[string]$UpstreamAlt = 'wss://c1.3dtank.com:9090/', # 备用上游(首帧直接 EnterSpace 的「二次进空间」连接)
|
||||
[string]$Label = (Get-Date -Format 'yyyyMMdd-HHmmss'),
|
||||
[switch]$FetchAssets, # 先跑 fetch_assets.js 镜像资源(首次或资源缺失时用)
|
||||
[switch]$KillExisting, # 端口被占用时,先结束占用进程
|
||||
@@ -135,7 +136,7 @@ if (Test-Path $PackScript) {
|
||||
$RelayProc = $null
|
||||
if ($WithRelay) {
|
||||
Write-Host ("[中继] wss_server.js relay ws://localhost:{0}/ -> {1}" -f $RelayPort, $Upstream) -ForegroundColor Green
|
||||
$RelayArgs = @('wss_server.js', '--port', "$RelayPort", '--upstream', $Upstream, '--label', $Label, '--dump-every', "$DumpEvery")
|
||||
$RelayArgs = @('wss_server.js', '--port', "$RelayPort", '--upstream', $Upstream, '--upstream-alt', $UpstreamAlt, '--label', $Label, '--dump-every', "$DumpEvery")
|
||||
if (-not $NoLog) { $RelayArgs += @('--log', '1') }
|
||||
if ($NoHex) { $RelayArgs += @('--no-hex', '1') }
|
||||
if ($Quiet) { $RelayArgs += @('--quiet', '1') }
|
||||
|
||||
+41
-20
@@ -40,7 +40,9 @@ const Arg = (Name, Def) => {
|
||||
};
|
||||
|
||||
const PORT = Number(Arg('port', '9091'));
|
||||
const UPSTREAM = Arg('upstream', 'wss://c2.3dtank.com:9090/'); // 官方客户端实际连 c2(c1/c2 是两个独立服;要切回来用 --upstream wss://c1.3dtank.com:9090/)
|
||||
const UPSTREAM = Arg('upstream', 'wss://c2.3dtank.com:9090/'); // 主上游(大厅:首帧 = Hash Request)
|
||||
// 备用上游:首帧直接是 Auth/EnterSpace 的连接(复用会话哈希的"二次进空间",官方走的是另一台服务器)→ 默认 c1
|
||||
const UPSTREAM_ALT = Arg('upstream-alt', 'wss://c1.3dtank.com:9090/');
|
||||
const LABEL = Arg('label', new Date().toISOString().replace(/[:.]/g, '-'));
|
||||
const PACKET_DIR = path.resolve(Arg('dir', path.join(PROJECT, 'packets', LABEL)));
|
||||
|
||||
@@ -277,6 +279,7 @@ class StreamDecoder {
|
||||
this.Key = null;
|
||||
this.Hint = null; // 对向方向破解出的密钥
|
||||
this.Window = [];
|
||||
this.FirstCmdId = null; // 本方向解出的**第一条**命令 ID(用于按首帧选上游)
|
||||
}
|
||||
|
||||
Feed(Buf) {
|
||||
@@ -372,6 +375,7 @@ class StreamDecoder {
|
||||
continue;
|
||||
}
|
||||
const Id = Body[At];
|
||||
if (this.FirstCmdId === null) this.FirstCmdId = Id; // 首条命令 ID(供上游分流)
|
||||
const Payload = Body.slice(At + 1, At + 41);
|
||||
const Name = (CMD_NAME[this.Direction] && CMD_NAME[this.Direction][Id]) || '未知';
|
||||
const Extra = this.Direction === 'c2s' && Id === 32 ? ' ' + StreamDecoder.DescribeClientLog(Body.slice(At + 1)) : '';
|
||||
@@ -551,31 +555,48 @@ WsServer.on('connection', (Client, Req) => {
|
||||
: undefined;
|
||||
|
||||
// 转发:客户端帧**原样直发**;上游未就绪时暂存,open 后按原序补发(不重放、不改字节)。
|
||||
Upstream = new Ws(UPSTREAM, { headers: UpHeaders, protocols: UpProtocols, rejectUnauthorized: false });
|
||||
Upstream.binaryType = 'nodebuffer';
|
||||
Upstream.on('open', () => {
|
||||
State.Log('上游已连接: ' + UPSTREAM + ',补发暂存 ' + Pending.length + ' 帧');
|
||||
while (Pending.length) Upstream.send(Pending.shift());
|
||||
});
|
||||
Upstream.on('message', (Data) => {
|
||||
const Buf = Buffer.from(Data);
|
||||
SaveFrame(State, 's2c', Buf);
|
||||
Decoders.s2c.Feed(Buf);
|
||||
SyncKeys(Decoders);
|
||||
if (Client.readyState === Ws.OPEN) Client.send(Buf);
|
||||
});
|
||||
Upstream.on('close', (Code) => {
|
||||
State.Log('上游关闭 code=' + Code);
|
||||
if (Client.readyState === Ws.OPEN) Client.close();
|
||||
});
|
||||
Upstream.on('error', (Err) => State.Log('上游错误: ' + Err.message));
|
||||
// ★ 上游不再"连上就建":等客户端首帧到达、解出命令 ID 后再选上游(见下方 Client.on('message'))。
|
||||
const StartUpstream = (Target) => {
|
||||
State.Log('上游选择: ' + Target + (Target === UPSTREAM ? '(主:大厅连接)' : '(备:复用哈希的二次进空间)'));
|
||||
const U = new Ws(Target, { headers: UpHeaders, protocols: UpProtocols, rejectUnauthorized: false });
|
||||
U.binaryType = 'nodebuffer';
|
||||
U.on('open', () => {
|
||||
State.Log('上游已连接: ' + Target + ',补发暂存 ' + Pending.length + ' 帧');
|
||||
while (Pending.length) U.send(Pending.shift());
|
||||
});
|
||||
U.on('message', (Data) => {
|
||||
const Buf = Buffer.from(Data);
|
||||
SaveFrame(State, 's2c', Buf);
|
||||
Decoders.s2c.Feed(Buf);
|
||||
SyncKeys(Decoders);
|
||||
if (Client.readyState === Ws.OPEN) Client.send(Buf);
|
||||
});
|
||||
U.on('close', (Code) => {
|
||||
State.Log('上游关闭 code=' + Code);
|
||||
if (Client.readyState === Ws.OPEN) Client.close();
|
||||
});
|
||||
U.on('error', (Err) => State.Log('上游错误: ' + Err.message));
|
||||
Upstream = U;
|
||||
};
|
||||
|
||||
Client.on('message', (Data) => {
|
||||
const Buf = Buffer.from(Data);
|
||||
SaveFrame(State, 'c2s', Buf);
|
||||
Decoders.c2s.Feed(Buf);
|
||||
SyncKeys(Decoders);
|
||||
if (Upstream && Upstream.readyState === Ws.OPEN) Upstream.send(Buf);
|
||||
if (!Upstream) {
|
||||
// 上游还没定:先暂存,并尝试用首帧的命令 ID 决定去主上游还是备用上游
|
||||
Pending.push(Buf);
|
||||
const Id = Decoders.c2s.FirstCmdId;
|
||||
if (Id === null) {
|
||||
State.Log('等待首帧命令 ID 以决定上游(暂存 ' + Buf.length + ' 字节)');
|
||||
return;
|
||||
}
|
||||
// 1 = Hash Request(大厅)→ 主上游;3 = Auth/EnterSpace(复用会话哈希的二次进空间)→ 备用上游
|
||||
StartUpstream(Id === 3 ? UPSTREAM_ALT : UPSTREAM);
|
||||
return;
|
||||
}
|
||||
if (Upstream.readyState === Ws.OPEN) Upstream.send(Buf);
|
||||
else {
|
||||
State.Log('上游尚未就绪,暂存 ' + Buf.length + ' 字节待补发');
|
||||
Pending.push(Buf);
|
||||
|
||||
Reference in new issue
Block a user