feat: server 包新增机器人,可通过 server.NewBot 函数进行创建,机器人将模拟普通连接行为,适用于测试等场景

This commit is contained in:
kercylan98 2023-11-13 16:05:32 +08:00
parent e2b7887b14
commit 4c092c04d2
6 changed files with 236 additions and 23 deletions

69
server/bot.go Normal file
View File

@ -0,0 +1,69 @@
package server
import (
"fmt"
"io"
"sync/atomic"
"time"
)
// NewBot 创建一个机器人,目前仅支持 Socket 服务器
func NewBot(srv *Server, options ...BotOption) *Bot {
if !srv.IsSocket() {
panic(fmt.Errorf("server type[%s] is not socket", srv.network))
}
bot := &Bot{
conn: newBotConn(srv),
}
for _, option := range options {
option(bot)
}
return bot
}
type Bot struct {
conn *Conn
joined atomic.Bool
}
// JoinServer 加入服务器
func (slf *Bot) JoinServer() {
if slf.joined.Swap(true) {
slf.conn.server.OnConnectionClosedEvent(slf.conn, nil)
}
slf.conn.server.OnConnectionOpenedEvent(slf.conn)
}
// LeaveServer 离开服务器
func (slf *Bot) LeaveServer() {
if slf.joined.Swap(false) {
slf.conn.server.OnConnectionClosedEvent(slf.conn, nil)
}
}
// SetNetworkDelay 设置网络延迟和波动范围
// - delay 延迟
// - fluctuation 波动范围
func (slf *Bot) SetNetworkDelay(delay, fluctuation time.Duration) {
slf.conn.delay = delay
slf.conn.fluctuation = fluctuation
}
// SetWriter 设置写入器
func (slf *Bot) SetWriter(writer io.Writer) {
slf.conn.botWriter.Store(&writer)
}
// SendPacket 发送数据包到服务器
func (slf *Bot) SendPacket(packet []byte) {
if slf.conn.server.IsOnline(slf.conn.GetID()) {
slf.conn.server.PushPacketMessage(slf.conn, 0, packet)
}
}
// SendWSPacket 发送 WebSocket 数据包到服务器
func (slf *Bot) SendWSPacket(wst int, packet []byte) {
if slf.conn.server.IsOnline(slf.conn.GetID()) {
slf.conn.server.PushPacketMessage(slf.conn, wst, packet)
}
}

26
server/bot_options.go Normal file
View File

@ -0,0 +1,26 @@
package server
import (
"io"
"time"
)
type BotOption func(bot *Bot)
// WithBotNetworkDelay 设置机器人网络延迟及波动范围
// - delay 延迟
// - fluctuation 波动范围
func WithBotNetworkDelay(delay, fluctuation time.Duration) BotOption {
return func(bot *Bot) {
bot.conn.delay = delay
bot.conn.fluctuation = fluctuation
}
}
// WithBotWriter 设置机器人写入器,默认为 os.Stdout
func WithBotWriter(construction func(bot *Bot) io.Writer) BotOption {
return func(bot *Bot) {
writer := construction(bot)
bot.conn.botWriter.Store(&writer)
}
}

49
server/bot_test.go Normal file
View File

@ -0,0 +1,49 @@
package server_test
import (
"github.com/kercylan98/minotaur/server"
"io"
"testing"
"time"
)
type Writer struct {
t *testing.T
bot *server.Bot
}
func (slf *Writer) Write(p []byte) (n int, err error) {
slf.t.Log(string(p))
switch string(p) {
case "hello":
slf.bot.SendPacket([]byte("world"))
}
return 0, nil
}
func TestNewBot(t *testing.T) {
srv := server.New(server.NetworkWebsocket)
srv.RegConnectionOpenedEvent(func(srv *server.Server, conn *server.Conn) {
t.Logf("connection opened: %s", conn.GetID())
conn.Close()
conn.Write([]byte("hello"))
})
srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) {
t.Logf("connection closed: %s", conn.GetID())
})
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
t.Logf("connection %s receive packet: %s", conn.GetID(), string(packet))
conn.Write([]byte("world"))
})
srv.RegStartFinishEvent(func(srv *server.Server) {
bot := server.NewBot(srv, server.WithBotNetworkDelay(100, 20), server.WithBotWriter(func(bot *server.Bot) io.Writer {
return &Writer{t: t, bot: bot}
}))
bot.JoinServer()
time.Sleep(time.Second)
bot.SendPacket([]byte("hello"))
})
srv.Run(":9600")
}

View File

@ -13,11 +13,14 @@ import (
"github.com/kercylan98/minotaur/utils/timer"
"github.com/panjf2000/gnet"
"github.com/xtaci/kcp-go/v5"
"io"
"net"
"net/http"
"os"
"runtime/debug"
"strings"
"sync"
"sync/atomic"
"time"
)
@ -80,18 +83,25 @@ func newWebsocketConn(server *Server, ws *websocket.Conn, ip string) *Conn {
return c
}
// NewEmptyConn 创建一个适用于测试的空连接
func NewEmptyConn(server *Server) *Conn {
// newBotConn 创建一个适用于测试等情况的机器人连接
func newBotConn(server *Server) *Conn {
ip, port := random.NetIP(), random.Port()
var writer io.Writer = os.Stdout
c := &Conn{
ctx: server.ctx,
connection: &connection{
server: server,
remoteAddr: &net.TCPAddr{},
ip: "0.0.0.0:0",
data: map[any]any{},
openTime: time.Now(),
server: server,
remoteAddr: &net.TCPAddr{
IP: ip,
Port: port,
Zone: "",
},
ip: fmt.Sprintf("BOT:%s:%d", ip.String(), port),
data: map[any]any{},
openTime: time.Now(),
},
}
c.botWriter.Store(&writer)
c.init()
return c
}
@ -104,20 +114,23 @@ type Conn struct {
// connection 长久保持的连接
type connection struct {
server *Server
ticker *timer.Ticker
remoteAddr net.Addr
ip string
ws *websocket.Conn
gn gnet.Conn
kcp *kcp.UDPSession
gw func(packet []byte)
data map[any]any
closed bool
pool *concurrent.Pool[*connPacket]
loop *writeloop.WriteLoop[*connPacket]
mu sync.Mutex
openTime time.Time
server *Server
ticker *timer.Ticker
remoteAddr net.Addr
ip string
ws *websocket.Conn
gn gnet.Conn
kcp *kcp.UDPSession
gw func(packet []byte)
data map[any]any
closed bool
pool *concurrent.Pool[*connPacket]
loop *writeloop.WriteLoop[*connPacket]
mu sync.Mutex
openTime time.Time
delay time.Duration
fluctuation time.Duration
botWriter atomic.Pointer[io.Writer]
}
// Ticker 获取定时器
@ -145,8 +158,8 @@ func (slf *Conn) GetWebsocketRequest() *http.Request {
return slf.GetData(wsRequestKey).(*http.Request)
}
// IsEmpty 是否是空连接
func (slf *Conn) IsEmpty() bool {
// IsBot 是否是机器人连接
func (slf *Conn) IsBot() bool {
return slf.ws == nil && slf.gn == nil && slf.kcp == nil && slf.gw == nil
}
@ -158,6 +171,9 @@ func (slf *Conn) RemoteAddr() net.Addr {
// GetID 获取连接ID
// - 为远程地址的字符串形式
func (slf *Conn) GetID() string {
if slf.IsBot() {
return slf.ip
}
return slf.remoteAddr.String()
}
@ -281,6 +297,14 @@ func (slf *Conn) init() {
)
slf.loop = writeloop.NewWriteLoop[*connPacket](slf.pool, func(data *connPacket) error {
var err error
if slf.delay > 0 || slf.fluctuation > 0 {
time.Sleep(random.Duration(int64(slf.delay-slf.fluctuation), int64(slf.delay+slf.fluctuation)))
_, err = (*slf.botWriter.Load()).Write(data.packet)
if data.callback != nil {
data.callback(err)
}
return err
}
if slf.IsWebsocket() {
err = slf.ws.WriteMessage(data.wst, data.packet)
} else {

View File

@ -387,6 +387,13 @@ func (slf *Server) Run(addr string) error {
return nil
}
// IsSocket 是否是 Socket 模式
func (slf *Server) IsSocket() bool {
return slf.network == NetworkTcp || slf.network == NetworkTcp4 || slf.network == NetworkTcp6 ||
slf.network == NetworkUdp || slf.network == NetworkUdp4 || slf.network == NetworkUdp6 ||
slf.network == NetworkUnix || slf.network == NetworkKcp || slf.network == NetworkWebsocket
}
// RunNone 是 Run("") 的简写,仅适用于运行 NetworkNone 服务器
func (slf *Server) RunNone() error {
return slf.Run(str.None)
@ -407,6 +414,18 @@ func (slf *Server) GetOnlineCount() int {
return slf.online.Size()
}
// GetOnlineBotCount 获取在线机器人数量
func (slf *Server) GetOnlineBotCount() int {
var count int
slf.online.Range(func(id string, conn *Conn) bool {
if conn.IsBot() {
count++
}
return true
})
return count
}
// GetOnline 获取在线连接
func (slf *Server) GetOnline(id string) *Conn {
return slf.online.Get(id)

26
utils/random/ip.go Normal file
View File

@ -0,0 +1,26 @@
package random
import (
"fmt"
"net"
)
// NetIP 返回一个随机的IP地址
func NetIP() net.IP {
return net.IPv4(byte(Int64(0, 255)), byte(Int64(0, 255)), byte(Int64(0, 255)), byte(Int64(0, 255)))
}
// Port 返回一个随机的端口号
func Port() int {
return Int(1, 65535)
}
// IPv4 返回一个随机产生的IPv4地址。
func IPv4() string {
return fmt.Sprintf("%d.%d.%d.%d", Int(1, 255), Int(0, 255), Int(0, 255), Int(0, 255))
}
// IPv4Port 返回一个随机产生的IPv4地址和端口。
func IPv4Port() string {
return fmt.Sprintf("%d.%d.%d.%d:%d", Int(1, 255), Int(0, 255), Int(0, 255), Int(0, 255), Int(1, 65535))
}