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 }