279 lines
6.3 KiB
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()
|
|
} |