Feat: UDP 框架
This commit is contained in:
358
cmd/internal/transport/udp_test.go
Normal file
358
cmd/internal/transport/udp_test.go
Normal file
@@ -0,0 +1,358 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"omnisocketgo/cmd/internal/latencylog"
|
||||
"omnisocketgo/cmd/internal/protocol"
|
||||
)
|
||||
|
||||
// TestUDPSendReceiveMessage 验证 UDP transport 可以正常收发 text 和 file 消息。
|
||||
func TestUDPSendReceiveMessage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
msg protocol.Message
|
||||
}{
|
||||
{
|
||||
name: "text",
|
||||
msg: protocol.Message{
|
||||
Type: protocol.MessageTypeText,
|
||||
ID: 1,
|
||||
From: "peer-a",
|
||||
To: "peer-b",
|
||||
Body: []byte("hello udp"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
msg: protocol.Message{
|
||||
Type: protocol.MessageTypeFile,
|
||||
ID: 2,
|
||||
From: "peer-a",
|
||||
To: "peer-b",
|
||||
FileName: "data.bin",
|
||||
Body: []byte{0x00, 0x10, 0xff},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sender, receiver := newUDPConnPair(t, nil, nil)
|
||||
|
||||
sendErr := make(chan error, 1)
|
||||
go func() {
|
||||
sendErr <- sender.Send(tt.msg)
|
||||
}()
|
||||
|
||||
got, _, err := receiver.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("Receive() error = %v", err)
|
||||
}
|
||||
if err := <-sendErr; err != nil {
|
||||
t.Fatalf("Send() error = %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tt.msg) {
|
||||
t.Fatalf("message mismatch: got %+v want %+v", got, tt.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestUDPSendLogsHandoffEvents 验证 UDP Send 会记录 handoff 事件。
|
||||
func TestUDPSendLogsHandoffEvents(t *testing.T) {
|
||||
logger := &recordingLogger{}
|
||||
sender, receiver := newUDPConnPair(
|
||||
t,
|
||||
[]UDPOption{WithUDPLogger(logger, latencylog.NodeRolePeer, "peer-a")},
|
||||
nil,
|
||||
)
|
||||
|
||||
msg := protocol.Message{
|
||||
Type: protocol.MessageTypeText,
|
||||
ID: 7,
|
||||
From: "peer-a",
|
||||
To: "peer-b",
|
||||
Body: []byte("hello"),
|
||||
}
|
||||
|
||||
sendErr := make(chan error, 1)
|
||||
go func() {
|
||||
sendErr <- sender.Send(msg)
|
||||
}()
|
||||
|
||||
got, _, err := receiver.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("Receive() error = %v", err)
|
||||
}
|
||||
if err := <-sendErr; err != nil {
|
||||
t.Fatalf("Send() error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, msg) {
|
||||
t.Fatalf("message mismatch: got %+v want %+v", got, msg)
|
||||
}
|
||||
|
||||
events := logger.Events()
|
||||
if len(events) < 2 {
|
||||
t.Fatalf("event count = %d, want at least 2", len(events))
|
||||
}
|
||||
if events[0].Event != latencylog.EventSendHandoffBegin {
|
||||
t.Fatalf("first event = %q, want %q", events[0].Event, latencylog.EventSendHandoffBegin)
|
||||
}
|
||||
// 最后一个事件应该是 SendHandoffEnd
|
||||
lastEvent := events[len(events)-1]
|
||||
if lastEvent.Event != latencylog.EventSendHandoffEnd {
|
||||
t.Fatalf("last event = %q, want %q", lastEvent.Event, latencylog.EventSendHandoffEnd)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUDPReceiveLoopDeliversMessages 验证 ReceiveLoop 会逐条交付连续到达的消息。
|
||||
func TestUDPReceiveLoopDeliversMessages(t *testing.T) {
|
||||
sender, receiver := newUDPConnPair(t, nil, nil)
|
||||
|
||||
want := []protocol.Message{
|
||||
{
|
||||
Type: protocol.MessageTypeText,
|
||||
ID: 1,
|
||||
From: "peer-a",
|
||||
To: "peer-b",
|
||||
Body: []byte("hello"),
|
||||
},
|
||||
{
|
||||
Type: protocol.MessageTypeFile,
|
||||
ID: 2,
|
||||
From: "peer-a",
|
||||
To: "peer-b",
|
||||
FileName: "payload.bin",
|
||||
Body: []byte{0x01, 0x02, 0x03},
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
got []protocol.Message
|
||||
)
|
||||
loopErr := make(chan error, 1)
|
||||
go func() {
|
||||
loopErr <- receiver.ReceiveLoop(func(msg protocol.Message, _ *net.UDPAddr) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
got = append(got, msg)
|
||||
if len(got) >= len(want) {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
|
||||
for _, msg := range want {
|
||||
if err := sender.Send(msg); err != nil {
|
||||
t.Fatalf("Send() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭发送端,ReceiveLoop 会因读取错误退出
|
||||
if err := sender.Close(); err != nil {
|
||||
t.Fatalf("sender.Close() error = %v", err)
|
||||
}
|
||||
|
||||
err := <-loopErr
|
||||
if err == nil {
|
||||
t.Fatal("ReceiveLoop() error = nil, want non-nil after peer close")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "udp receive loop read") {
|
||||
t.Fatalf("ReceiveLoop() error = %v, want read context", err)
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("received messages mismatch: got %+v want %+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUDPCloseIsIdempotent 验证 Close 可以安全地被重复调用。
|
||||
func TestUDPCloseIsIdempotent(t *testing.T) {
|
||||
conn, peer := newUDPConnPair(t, nil, nil)
|
||||
|
||||
if err := conn.Close(); err != nil {
|
||||
t.Fatalf("Close(first) error = %v", err)
|
||||
}
|
||||
if err := conn.Close(); err != nil {
|
||||
t.Fatalf("Close(second) error = %v, want nil", err)
|
||||
}
|
||||
_ = peer.Close()
|
||||
}
|
||||
|
||||
// TestUDPSendToMessage 验证 SendTo 可以向指定地址发送消息。
|
||||
func TestUDPSendToMessage(t *testing.T) {
|
||||
serverConn := newUDPListener(t)
|
||||
peerConn := newUDPDialed(t, serverConn.conn.LocalAddr().(*net.UDPAddr))
|
||||
|
||||
msg := protocol.Message{
|
||||
Type: protocol.MessageTypeText,
|
||||
ID: 1,
|
||||
From: "peer-a",
|
||||
To: "server",
|
||||
Body: []byte("hello sendto"),
|
||||
}
|
||||
|
||||
// peer 发送消息到 server
|
||||
sendErr := make(chan error, 1)
|
||||
go func() {
|
||||
sendErr <- peerConn.Send(msg)
|
||||
}()
|
||||
|
||||
got, addr, err := serverConn.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("Receive() error = %v", err)
|
||||
}
|
||||
if err := <-sendErr; err != nil {
|
||||
t.Fatalf("Send() error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, msg) {
|
||||
t.Fatalf("message mismatch: got %+v want %+v", got, msg)
|
||||
}
|
||||
|
||||
// server 用 SendTo 回复到 peer 地址
|
||||
reply := protocol.Message{
|
||||
Type: protocol.MessageTypeText,
|
||||
ID: 2,
|
||||
From: "server",
|
||||
To: "peer-a",
|
||||
Body: []byte("reply"),
|
||||
}
|
||||
|
||||
sendErr2 := make(chan error, 1)
|
||||
go func() {
|
||||
sendErr2 <- serverConn.SendTo(reply, addr)
|
||||
}()
|
||||
|
||||
gotReply, _, err := peerConn.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("peer Receive() error = %v", err)
|
||||
}
|
||||
if err := <-sendErr2; err != nil {
|
||||
t.Fatalf("SendTo() error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(gotReply, reply) {
|
||||
t.Fatalf("reply mismatch: got %+v want %+v", gotReply, reply)
|
||||
}
|
||||
}
|
||||
|
||||
// newUDPConnPair 创建一对互相连接的 UDP transport 连接,用于测试。
|
||||
func newUDPConnPair(t *testing.T, senderOpts []UDPOption, receiverOpts []UDPOption) (*UDPConn, *UDPConn) {
|
||||
t.Helper()
|
||||
|
||||
// 创建两个 UDP socket,通过 Dial 互相连接
|
||||
addr1, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveUDPAddr() error = %v", err)
|
||||
}
|
||||
addr2, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveUDPAddr() error = %v", err)
|
||||
}
|
||||
|
||||
conn1, err := net.ListenUDP("udp", addr1)
|
||||
if err != nil {
|
||||
t.Fatalf("ListenUDP(1) error = %v", err)
|
||||
}
|
||||
conn2, err := net.ListenUDP("udp", addr2)
|
||||
if err != nil {
|
||||
_ = conn1.Close()
|
||||
t.Fatalf("ListenUDP(2) error = %v", err)
|
||||
}
|
||||
|
||||
// 用 Dial 模式连接对端
|
||||
senderRaw, err := net.DialUDP("udp", nil, conn2.LocalAddr().(*net.UDPAddr))
|
||||
if err != nil {
|
||||
_ = conn1.Close()
|
||||
_ = conn2.Close()
|
||||
t.Fatalf("DialUDP(sender) error = %v", err)
|
||||
}
|
||||
_ = conn1.Close() // 不再需要 conn1
|
||||
|
||||
receiverRaw, err := net.DialUDP("udp", conn2.LocalAddr().(*net.UDPAddr), senderRaw.LocalAddr().(*net.UDPAddr))
|
||||
if err != nil {
|
||||
_ = senderRaw.Close()
|
||||
_ = conn2.Close()
|
||||
t.Fatalf("DialUDP(receiver) error = %v", err)
|
||||
}
|
||||
_ = conn2.Close() // 不再需要 conn2
|
||||
|
||||
sender, err := NewUDPConn(senderRaw, nil, senderOpts...)
|
||||
if err != nil {
|
||||
_ = senderRaw.Close()
|
||||
_ = receiverRaw.Close()
|
||||
t.Fatalf("NewUDPConn(sender) error = %v", err)
|
||||
}
|
||||
|
||||
receiver, err := NewUDPConn(receiverRaw, nil, receiverOpts...)
|
||||
if err != nil {
|
||||
_ = sender.Close()
|
||||
_ = receiverRaw.Close()
|
||||
t.Fatalf("NewUDPConn(receiver) error = %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = sender.Close()
|
||||
_ = receiver.Close()
|
||||
})
|
||||
|
||||
return sender, receiver
|
||||
}
|
||||
|
||||
// newUDPListener 创建一个监听模式的 UDP 连接,用于测试 server 场景。
|
||||
func newUDPListener(t *testing.T) *UDPConn {
|
||||
t.Helper()
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveUDPAddr() error = %v", err)
|
||||
}
|
||||
|
||||
raw, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("ListenUDP() error = %v", err)
|
||||
}
|
||||
|
||||
conn, err := NewUDPConn(raw, nil)
|
||||
if err != nil {
|
||||
_ = raw.Close()
|
||||
t.Fatalf("NewUDPConn() error = %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = conn.Close()
|
||||
})
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
// newUDPDialed 创建一个已连接到指定地址的 UDP 连接,用于测试 peer 场景。
|
||||
func newUDPDialed(t *testing.T, serverAddr *net.UDPAddr) *UDPConn {
|
||||
t.Helper()
|
||||
|
||||
raw, err := net.DialUDP("udp", nil, serverAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("DialUDP() error = %v", err)
|
||||
}
|
||||
|
||||
conn, err := NewUDPConn(raw, nil)
|
||||
if err != nil {
|
||||
_ = raw.Close()
|
||||
t.Fatalf("NewUDPConn() error = %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = conn.Close()
|
||||
})
|
||||
|
||||
return conn
|
||||
}
|
||||
Reference in New Issue
Block a user