eq2go/internal/login/server.go

279 lines
6.3 KiB
Go

package login
import (
"context"
"fmt"
"log"
"net/http"
"sync"
"time"
"eq2emu/internal/udp"
)
// Server represents the login server instance
type Server struct {
config *ServerConfig
database *LoginDB
udpServer *udp.Server
webServer *http.Server
clientList *ClientList
worldList *WorldList
running bool
startTime time.Time
// Synchronization
mu sync.RWMutex
stopChan chan struct{}
wg sync.WaitGroup
}
// NewServer creates a new login server instance
func NewServer(config *ServerConfig) (*Server, error) {
if config == nil {
return nil, fmt.Errorf("configuration cannot be nil")
}
// Validate configuration
if err := config.Validate(); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}
// Create database connection
db, err := NewLoginDB(config.DatabaseDSN)
if err != nil {
return nil, fmt.Errorf("failed to initialize database: %w", err)
}
// Create login server instance first
server := &Server{
config: config,
database: db,
clientList: NewClientList(),
worldList: NewWorldList(),
running: false,
stopChan: make(chan struct{}),
}
// Create UDP server for EverQuest II protocol
addr := fmt.Sprintf("%s:%d", config.ListenAddr, config.ListenPort)
udpConfig := udp.Config{
MaxConnections: config.MaxClients,
BufferSize: 1024 * 64, // 64KB buffer
EnableCompression: true,
EnableEncryption: true,
}
udpServer, err := udp.NewServer(addr, server.handleUDPPacket, udpConfig)
if err != nil {
return nil, fmt.Errorf("failed to create UDP server: %w", err)
}
server.udpServer = udpServer
// Initialize web server if enabled
if config.WebPort > 0 {
server.initWebServer()
}
return server, nil
}
// Start starts the login server
func (s *Server) Start() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.running {
return fmt.Errorf("server is already running")
}
log.Printf("Starting login server on %s:%d", s.config.ListenAddr, s.config.ListenPort)
// Start UDP server (it doesn't return an error in this implementation)
go s.udpServer.Start()
// Start web server if configured
if s.webServer != nil {
s.wg.Add(1)
go func() {
defer s.wg.Done()
addr := fmt.Sprintf("%s:%d", s.config.WebAddr, s.config.WebPort)
log.Printf("Starting web server on %s", addr)
if err := s.webServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("Web server error: %v", err)
}
}()
}
// Initialize world servers
for _, worldInfo := range s.config.WorldServers {
world := NewWorldServer(worldInfo)
s.worldList.Add(world)
}
s.running = true
s.startTime = time.Now()
log.Println("Login server started successfully")
return nil
}
// Stop stops the login server gracefully
func (s *Server) Stop() error {
s.mu.Lock()
defer s.mu.Unlock()
if !s.running {
return nil
}
log.Println("Stopping login server...")
// Signal shutdown
close(s.stopChan)
// Stop UDP server
s.udpServer.Stop()
// Stop web server
if s.webServer != nil {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := s.webServer.Shutdown(ctx); err != nil {
log.Printf("Error stopping web server: %v", err)
}
}
// Disconnect all clients
s.clientList.DisconnectAll("Server shutdown")
// Shutdown world servers
s.worldList.Shutdown()
// Close database
if err := s.database.Close(); err != nil {
log.Printf("Error closing database: %v", err)
}
// Wait for all goroutines to finish
s.wg.Wait()
s.running = false
log.Println("Login server stopped")
return nil
}
// Process runs the main server processing loop
func (s *Server) Process() {
ticker := time.NewTicker(time.Millisecond * 50) // 20 FPS processing
defer ticker.Stop()
statsTicker := time.NewTicker(time.Minute) // Statistics every minute
defer statsTicker.Stop()
for {
select {
case <-s.stopChan:
return
case <-ticker.C:
// Process clients
s.clientList.Process()
// Process world servers
s.worldList.Process()
case <-statsTicker.C:
// Update statistics
s.updateStatistics()
}
}
}
// handleUDPPacket handles incoming UDP packets from clients
func (s *Server) handleUDPPacket(conn *udp.Connection, packet *udp.ApplicationPacket) {
// Find or create client for this connection
client := s.clientList.GetByConnection(conn)
if client == nil {
client = NewClient(conn, s.database)
s.clientList.Add(client)
log.Printf("New client connected from %s", conn.GetClientAddr())
}
// Process packet
if err := client.HandlePacket(packet.Data); err != nil {
log.Printf("Error handling packet from %s: %v", conn.GetClientAddr(), err)
}
}
// updateStatistics updates server statistics
func (s *Server) updateStatistics() {
s.mu.RLock()
defer s.mu.RUnlock()
if !s.running {
return
}
clientCount := s.clientList.Count()
worldCount := s.worldList.Count()
uptime := time.Since(s.startTime)
log.Printf("Stats - Clients: %d, Worlds: %d, Uptime: %v",
clientCount, worldCount, uptime.Truncate(time.Second))
// Update database statistics
if err := s.database.UpdateServerStats("login", clientCount, worldCount); err != nil {
log.Printf("Failed to update server stats: %v", err)
}
}
// initWebServer initializes the web interface server
func (s *Server) initWebServer() {
mux := http.NewServeMux()
// Register web routes
mux.HandleFunc("/", s.handleWebRoot)
mux.HandleFunc("/api/status", s.handleAPIStatus)
mux.HandleFunc("/api/clients", s.handleAPIClients)
mux.HandleFunc("/api/worlds", s.handleAPIWorlds)
s.webServer = &http.Server{
Addr: fmt.Sprintf("%s:%d", s.config.WebAddr, s.config.WebPort),
Handler: mux,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
}
// IsRunning returns whether the server is currently running
func (s *Server) IsRunning() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.running
}
// GetUptime returns how long the server has been running
func (s *Server) GetUptime() time.Duration {
s.mu.RLock()
defer s.mu.RUnlock()
if !s.running {
return 0
}
return time.Since(s.startTime)
}
// GetClientCount returns the current number of connected clients
func (s *Server) GetClientCount() int {
return s.clientList.Count()
}
// GetWorldCount returns the current number of registered world servers
func (s *Server) GetWorldCount() int {
return s.worldList.Count()
}