feat:多跳(B->D->C->A)功能

This commit is contained in:
nnbcccscdscdsc
2026-03-27 23:03:00 +08:00
parent 5be3ff670f
commit 8e2bd0ffc6
8 changed files with 455 additions and 51 deletions

View File

@@ -25,6 +25,7 @@ type clientOptions struct {
udpLinuxTimestamping bool
bindIP string
bindDevice string
kcpDialAddress string
}
// Option 用于配置 Client 的可选行为,例如时延日志。
@@ -73,6 +74,13 @@ func WithBindDevice(device string) Option {
}
}
// WithKCPDialAddress 指定 KCP 实际拨号使用的 UDP 地址,可用于通过 relay 连接逻辑上的 server。
func WithKCPDialAddress(addr string) Option {
return func(options *clientOptions) {
options.kcpDialAddress = addr
}
}
// WithUDPLinuxTimestamping controls whether UDP clients enable Linux timestamping.
func WithUDPLinuxTimestamping(enabled bool) Option {
return func(options *clientOptions) {

View File

@@ -32,8 +32,13 @@ func DialKCP(serverAddr, peerID string, opts ...Option) (*KCPClient, error) {
options.logger = latencylog.NoopLogger{}
}
dialAddr := serverAddr
if options.kcpDialAddress != "" {
dialAddr = options.kcpDialAddress
}
session, err := transport.DialKCPSession(
serverAddr,
dialAddr,
options.bindIP,
options.bindDevice,
options.kcpPacketDebugLogger,

View File

@@ -267,6 +267,96 @@ func TestKCPClientsExchangeMessagesAcrossRelayedServers(t *testing.T) {
}
}
func TestKCPClientsExchangeMessagesViaUDPRelayToSingleHub(t *testing.T) {
hub := server.NewKCPHub()
serverAddr, cleanupHub := startRealKCPHubServer(t, hub)
defer cleanupHub()
remoteAddr, err := net.ResolveUDPAddr("udp", serverAddr)
if err != nil {
t.Fatalf("ResolveUDPAddr(server) error = %v", err)
}
baseRelayConn, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatalf("ListenPacket(relay) error = %v", err)
}
relayConn := &countingPacketConn{PacketConn: baseRelayConn}
relay, err := server.NewUDPRelay(relayConn, remoteAddr)
if err != nil {
_ = relayConn.Close()
t.Fatalf("NewUDPRelay() error = %v", err)
}
var relayWG sync.WaitGroup
relayWG.Add(1)
go func() {
defer relayWG.Done()
if serveErr := relay.Serve(); serveErr != nil {
t.Errorf("relay.Serve() error = %v", serveErr)
}
}()
defer func() {
_ = relayConn.Close()
relayWG.Wait()
}()
peerA, err := DialKCP(serverAddr, "peer-a", WithKCPDialAddress(relayConn.LocalAddr().String()))
if err != nil {
t.Fatalf("DialKCP(peer-a via relay) error = %v", err)
}
defer func() { _ = peerA.Close() }()
peerB, err := DialKCP(serverAddr, "peer-b")
if err != nil {
t.Fatalf("DialKCP(peer-b direct) error = %v", err)
}
defer func() { _ = peerB.Close() }()
waitFor(t, func() bool { return hub.HasPeer("peer-a") && hub.HasPeer("peer-b") }, "both peers to be registered on the single hub")
if err := peerB.SendText("peer-a", "hello via udp relay"); err != nil {
t.Fatalf("peerB.SendText() error = %v", err)
}
gotAtA, err := peerA.Receive()
if err != nil {
t.Fatalf("peerA.Receive() error = %v", err)
}
wantAtA := protocol.Message{
Type: protocol.MessageTypeText,
ID: 1,
From: "peer-b",
To: "peer-a",
Body: []byte("hello via udp relay"),
}
if !reflect.DeepEqual(gotAtA, wantAtA) {
t.Fatalf("peerA received %+v, want %+v", gotAtA, wantAtA)
}
if err := peerA.SendText("peer-b", "hello back through relay"); err != nil {
t.Fatalf("peerA.SendText() error = %v", err)
}
gotAtB, err := peerB.Receive()
if err != nil {
t.Fatalf("peerB.Receive() error = %v", err)
}
wantAtB := protocol.Message{
Type: protocol.MessageTypeText,
ID: 1,
From: "peer-a",
To: "peer-b",
Body: []byte("hello back through relay"),
}
if !reflect.DeepEqual(gotAtB, wantAtB) {
t.Fatalf("peerB received %+v, want %+v", gotAtB, wantAtB)
}
if got := relayConn.WriteCount(); got == 0 {
t.Fatal("relay should have forwarded packets for peer-a session")
}
}
func TestKCPHubPrefersLocalPeerBeforeRelay(t *testing.T) {
fixture := startRelayedKCPHubs(t)
defer fixture.cleanup()