Files
90CNServer/server/internal/services/dnfbridge/party_udp_relay_throttle_test.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

98 lines
3.3 KiB
Go

package dnfbridge
import (
"testing"
"time"
)
// TestCurrentPartyUDPRelayForwardBudgetLeavesNormalTrafficAlone pins the
// "defensive only" half of the forwarding budget: traffic a real party produces
// (here 60 datagrams per second for a minute, and a 2,000-datagram combat burst)
// is never refused, so the guard cannot change normal play.
func TestCurrentPartyUDPRelayForwardBudgetLeavesNormalTrafficAlone(t *testing.T) {
leg := &currentPartyUDPRelayLeg{}
now := time.Unix(1758300000, 0).UTC()
for tick := 0; tick < 60; tick++ {
now = now.Add(time.Second)
for packet := 0; packet < 60; packet++ {
admitted, report, _ := leg.admitForward(now)
if !admitted || report {
t.Fatalf("normal traffic refused at second=%d packet=%d", tick, packet)
}
}
}
burst := &currentPartyUDPRelayLeg{}
for packet := 0; packet < 2000; packet++ {
admitted, _, _ := burst.admitForward(now)
if !admitted {
t.Fatalf("2000-packet combat burst refused at packet=%d", packet)
}
}
}
// TestCurrentPartyUDPRelayForwardBudgetCapsARetransmitStorm is the measured
// incident: a client that stops dispatching retransmits the same 9-byte payload
// at ~63,400 datagrams per second. The leg must forward at its budget and refuse
// the rest, and the refusal must be reported once per interval rather than per
// datagram.
func TestCurrentPartyUDPRelayForwardBudgetCapsARetransmitStorm(t *testing.T) {
leg := &currentPartyUDPRelayLeg{}
start := time.Unix(1758300000, 0).UTC()
admitted := 0
reports := 0
simulated := 0
for second := 0; second < 5; second++ {
now := start.Add(time.Duration(second) * time.Second)
for packet := 0; packet < 63400; packet++ {
simulated++
ok, report, _ := leg.admitForward(now)
if ok {
admitted++
}
if report {
reports++
}
}
}
if simulated != 5*63400 {
t.Fatalf("simulated=%d", simulated)
}
// One full burst plus five seconds of refill, with a little slack for the
// bucket's per-datagram accounting.
upper := currentPartyUDPRelayForwardBurstPackets +
int(5*currentPartyUDPRelayForwardRatePerSecond) + 100
if admitted > upper {
t.Fatalf("admitted=%d exceeds budget ceiling=%d", admitted, upper)
}
if admitted > simulated/8 {
t.Fatalf("admitted=%d of %d: a 63k/s storm was not cut down", admitted, simulated)
}
if reports == 0 || reports > 6 {
t.Fatalf("throttle reports=%d, want at least one and never one per datagram", reports)
}
leg.mu.Lock()
refused := leg.throttledPackets
leg.mu.Unlock()
if refused != uint64(simulated-admitted) {
t.Fatalf("refused=%d admitted=%d simulated=%d", refused, admitted, simulated)
}
}
// TestCurrentPartyUDPRelayForwardBudgetRefillsAfterTheStorm pins recovery: once
// the client behaves again the same leg forwards normally, so a single storm does
// not permanently degrade the party transport.
func TestCurrentPartyUDPRelayForwardBudgetRefillsAfterTheStorm(t *testing.T) {
leg := &currentPartyUDPRelayLeg{}
now := time.Unix(1758300000, 0).UTC()
for packet := 0; packet < 10000; packet++ {
leg.admitForward(now)
}
// One second of quiet, then a normal second of play.
now = now.Add(2 * time.Second)
for packet := 0; packet < 60; packet++ {
if admitted, _, _ := leg.admitForward(now); !admitted {
t.Fatalf("leg did not recover after the storm at packet=%d", packet)
}
}
}