1
0
Protocol/server.go
2025-09-01 13:56:01 -05:00

407 lines
8.9 KiB
Go

package eq2net
import (
"context"
"fmt"
"net"
"sync"
"time"
"github.com/panjf2000/gnet/v2"
)
// ServerConfig contains configuration for the EQ2 server
type ServerConfig struct {
// Network settings
Address string // Listen address (e.g., ":9000")
MaxConnections int // Maximum concurrent connections
ReadBufferSize int // UDP read buffer size
WriteBufferSize int // UDP write buffer size
// Stream settings
StreamConfig *StreamConfig // Default config for new streams
// Performance settings
NumEventLoops int // Number of gnet event loops (0 = NumCPU)
ReusePort bool // Enable SO_REUSEPORT for load balancing
}
// DefaultServerConfig returns a default server configuration
func DefaultServerConfig() *ServerConfig {
return &ServerConfig{
Address: ":9000",
MaxConnections: 10000,
ReadBufferSize: 65536,
WriteBufferSize: 65536,
StreamConfig: DefaultStreamConfig(),
NumEventLoops: 0,
ReusePort: true,
}
}
// EQ2Server implements a gnet-based EverQuest 2 server
type EQ2Server struct {
gnet.BuiltinEventEngine
config *ServerConfig
engine gnet.Engine
engineSet bool // Track if engine has been set
addr net.Addr
// Connection management
streams map[string]*serverStream // Key: remote address string
streamsMu sync.RWMutex
// Callbacks
onNewConnection func(*EQStream)
onConnectionClosed func(*EQStream, string)
// Lifecycle
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
}
// serverStream wraps an EQStream with server-specific data
type serverStream struct {
stream *EQStream
lastActive time.Time
conn gnet.Conn
}
// NewEQ2Server creates a new EQ2 server
func NewEQ2Server(config *ServerConfig) *EQ2Server {
if config == nil {
config = DefaultServerConfig()
}
ctx, cancel := context.WithCancel(context.Background())
return &EQ2Server{
config: config,
streams: make(map[string]*serverStream),
ctx: ctx,
cancel: cancel,
}
}
// Start begins listening for connections
func (s *EQ2Server) Start() error {
// Configure gnet options
opts := []gnet.Option{
gnet.WithMulticore(true),
gnet.WithReusePort(s.config.ReusePort),
gnet.WithSocketRecvBuffer(s.config.ReadBufferSize),
gnet.WithSocketSendBuffer(s.config.WriteBufferSize),
gnet.WithTicker(true),
}
if s.config.NumEventLoops > 0 {
opts = append(opts, gnet.WithNumEventLoop(s.config.NumEventLoops))
}
// Start cleanup worker
s.wg.Add(1)
go s.cleanupWorker()
// Start gnet server
return gnet.Run(s, "udp://"+s.config.Address, opts...)
}
// Stop gracefully shuts down the server
func (s *EQ2Server) Stop() error {
// Signal shutdown
s.cancel()
// Close all streams
s.streamsMu.Lock()
for _, ss := range s.streams {
ss.stream.Close()
}
s.streamsMu.Unlock()
// Wait for cleanup
done := make(chan struct{})
go func() {
s.wg.Wait()
close(done)
}()
select {
case <-done:
case <-time.After(10 * time.Second):
// Force shutdown after timeout
}
// Stop gnet engine
if s.engineSet {
return s.engine.Stop(s.ctx)
}
return nil
}
// gnet event handlers
// OnBoot is called when the server starts
func (s *EQ2Server) OnBoot(eng gnet.Engine) (action gnet.Action) {
s.engine = eng
s.engineSet = true
// Parse and store the address
addr, err := net.ResolveUDPAddr("udp", s.config.Address)
if err == nil {
s.addr = addr
}
fmt.Printf("EQ2 server started on %s\n", s.config.Address)
return gnet.None
}
// OnShutdown is called when the server stops
func (s *EQ2Server) OnShutdown(eng gnet.Engine) {
fmt.Println("EQ2 server shutting down")
}
// OnTraffic handles incoming UDP packets
func (s *EQ2Server) OnTraffic(c gnet.Conn) (action gnet.Action) {
// Read the packet
buf, err := c.Next(-1)
if err != nil {
return gnet.None
}
// Get remote address
remoteAddr := c.RemoteAddr()
if remoteAddr == nil {
return gnet.None
}
addrStr := remoteAddr.String()
// Look up or create stream
s.streamsMu.RLock()
ss, exists := s.streams[addrStr]
s.streamsMu.RUnlock()
if !exists {
// Check for session request
if len(buf) >= 2 {
opcode := uint16(buf[0])<<8 | uint16(buf[1])
if opcode == OPSessionRequest {
// Create new stream
ss = s.createStream(c, remoteAddr)
if ss == nil {
return gnet.None
}
} else {
// Not a session request, send out-of-session
s.sendOutOfSession(c, remoteAddr)
return gnet.None
}
} else {
return gnet.None
}
}
// Update last activity
ss.lastActive = time.Now()
// Process packet in stream
ss.stream.handleIncomingPacket(buf)
return gnet.None
}
// OnTick is called periodically
func (s *EQ2Server) OnTick() (delay time.Duration, action gnet.Action) {
// Tick interval for maintenance tasks
return 100 * time.Millisecond, gnet.None
}
// createStream creates a new stream for a client
func (s *EQ2Server) createStream(c gnet.Conn, remoteAddr net.Addr) *serverStream {
// Check connection limit
s.streamsMu.Lock()
defer s.streamsMu.Unlock()
if len(s.streams) >= s.config.MaxConnections {
return nil
}
// Create stream config (copy from default)
streamConfig := *s.config.StreamConfig
// Create new stream
stream := NewEQStream(&streamConfig)
// Set up callbacks
stream.SetCallbacks(
func() {
// On connect
if s.onNewConnection != nil {
s.onNewConnection(stream)
}
},
func(reason string) {
// On disconnect
s.removeStream(remoteAddr.String())
if s.onConnectionClosed != nil {
s.onConnectionClosed(stream, reason)
}
},
nil, // Error handler
)
// Create server stream wrapper
ss := &serverStream{
stream: stream,
lastActive: time.Now(),
conn: c,
}
// Store in map
s.streams[remoteAddr.String()] = ss
// Create a custom PacketConn wrapper for this stream
packetConn := &gnetPacketConn{
conn: c,
localAddr: s.addr,
remoteAddr: remoteAddr,
server: s,
}
// Connect the stream (in server mode, this just sets up the connection)
go func() {
if err := stream.Connect(packetConn, remoteAddr); err != nil {
s.removeStream(remoteAddr.String())
}
}()
return ss
}
// removeStream removes a stream from the server
func (s *EQ2Server) removeStream(addrStr string) {
s.streamsMu.Lock()
defer s.streamsMu.Unlock()
if ss, exists := s.streams[addrStr]; exists {
ss.stream.Close()
delete(s.streams, addrStr)
}
}
// sendOutOfSession sends an out-of-session packet
func (s *EQ2Server) sendOutOfSession(c gnet.Conn, remoteAddr net.Addr) {
packet := NewEQProtocolPacket(OPOutOfSession, nil)
data := packet.Serialize(0)
c.AsyncWrite(data, nil)
}
// cleanupWorker periodically cleans up inactive connections
func (s *EQ2Server) cleanupWorker() {
defer s.wg.Done()
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-s.ctx.Done():
return
case <-ticker.C:
s.cleanupInactiveStreams()
}
}
}
// cleanupInactiveStreams removes streams that have been inactive too long
func (s *EQ2Server) cleanupInactiveStreams() {
timeout := 5 * time.Minute
now := time.Now()
s.streamsMu.Lock()
defer s.streamsMu.Unlock()
for addr, ss := range s.streams {
if now.Sub(ss.lastActive) > timeout {
ss.stream.Close()
delete(s.streams, addr)
}
}
}
// SetCallbacks sets server event callbacks
func (s *EQ2Server) SetCallbacks(onNew func(*EQStream), onClosed func(*EQStream, string)) {
s.onNewConnection = onNew
s.onConnectionClosed = onClosed
}
// GetStream returns the stream for a given address
func (s *EQ2Server) GetStream(addr string) *EQStream {
s.streamsMu.RLock()
defer s.streamsMu.RUnlock()
if ss, exists := s.streams[addr]; exists {
return ss.stream
}
return nil
}
// GetAllStreams returns all active streams
func (s *EQ2Server) GetAllStreams() []*EQStream {
s.streamsMu.RLock()
defer s.streamsMu.RUnlock()
streams := make([]*EQStream, 0, len(s.streams))
for _, ss := range s.streams {
streams = append(streams, ss.stream)
}
return streams
}
// gnetPacketConn implements net.PacketConn for gnet connections
type gnetPacketConn struct {
conn gnet.Conn
localAddr net.Addr
remoteAddr net.Addr
server *EQ2Server
}
func (g *gnetPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
// This is handled by OnTraffic, not used in server mode
return 0, nil, fmt.Errorf("not implemented for server mode")
}
func (g *gnetPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
// Write to the gnet connection
err = g.conn.AsyncWrite(p, nil)
if err != nil {
return 0, err
}
return len(p), nil
}
func (g *gnetPacketConn) Close() error {
// Connection lifecycle is managed by server
return nil
}
func (g *gnetPacketConn) LocalAddr() net.Addr {
return g.localAddr
}
func (g *gnetPacketConn) SetDeadline(t time.Time) error {
// Not implemented for UDP
return nil
}
func (g *gnetPacketConn) SetReadDeadline(t time.Time) error {
// Not implemented for UDP
return nil
}
func (g *gnetPacketConn) SetWriteDeadline(t time.Time) error {
// Not implemented for UDP
return nil
}