diff --git a/goonserver b/goonserver index f9ec811..c720b66 160000 --- a/goonserver +++ b/goonserver @@ -1 +1 @@ -Subproject commit f9ec811b10bbab54e843199eb68156e9e7c143cc +Subproject commit c720b668180d46b8eb37747f9c24d2fa1f49de72 diff --git a/network/network.go b/network/network.go index 587b5d4..de3b361 100644 --- a/network/network.go +++ b/network/network.go @@ -18,7 +18,8 @@ import ( const protoVersion = 1 -var serverAddr = "boner.be:6969" // Default server address +var serverAddr = "boner.be:6969" // Default server address +var lastSeenMessageTimestamp int64 = 0 // Track the last message timestamp seen by this client func SetServerAddr(addr string) { serverAddr = addr @@ -26,10 +27,32 @@ func SetServerAddr(addr string) { } func ConnectToServer(username, password string, isRegistering bool) (net.Conn, int32, error) { - conn, err := net.Dial("tcp", serverAddr) - if err != nil { - log.Printf("Failed to dial server: %v", err) - return nil, 0, err + log.Printf("Connecting to server at %s...", serverAddr) + + var err error + var conn net.Conn + + // Try connecting with a timeout + connChan := make(chan net.Conn, 1) + errChan := make(chan error, 1) + + go func() { + c, e := net.Dial("tcp", serverAddr) + if e != nil { + errChan <- e + return + } + connChan <- c + }() + + // Wait for connection with timeout + select { + case conn = <-connChan: + // Connection successful, continue + case err = <-errChan: + return nil, 0, fmt.Errorf("failed to dial server: %v", err) + case <-time.After(5 * time.Second): + return nil, 0, fmt.Errorf("connection timeout after 5 seconds") } log.Println("Connected to server. Authenticating...") @@ -54,8 +77,12 @@ func ConnectToServer(username, password string, isRegistering bool) (net.Conn, i return nil, 0, fmt.Errorf("failed to send auth: %v", err) } - // Read server response + // Read server response with timeout reader := bufio.NewReader(conn) + + // Set a read deadline for authentication + conn.SetReadDeadline(time.Now().Add(10 * time.Second)) + lengthBuf := make([]byte, 4) if _, err := io.ReadFull(reader, lengthBuf); err != nil { conn.Close() @@ -63,12 +90,21 @@ func ConnectToServer(username, password string, isRegistering bool) (net.Conn, i } messageLength := binary.BigEndian.Uint32(lengthBuf) + // Sanity check message size + if messageLength > 1024*1024 { // 1MB max message size + conn.Close() + return nil, 0, fmt.Errorf("authentication response too large: %d bytes", messageLength) + } + messageBuf := make([]byte, messageLength) if _, err := io.ReadFull(reader, messageBuf); err != nil { conn.Close() return nil, 0, fmt.Errorf("failed to read auth response body: %v", err) } + // Clear read deadline after authentication + conn.SetReadDeadline(time.Time{}) + var response pb.ServerMessage if err := proto.Unmarshal(messageBuf, &response); err != nil { conn.Close() @@ -88,6 +124,10 @@ func ConnectToServer(username, password string, isRegistering bool) (net.Conn, i playerID := response.GetPlayerId() log.Printf("Successfully authenticated with player ID: %d", playerID) + + // Reset the lastSeenMessageTimestamp when reconnecting + lastSeenMessageTimestamp = 0 + return conn, playerID, nil } @@ -110,6 +150,12 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play errChan := make(chan error, 1) done := make(chan struct{}) + // Add a heartbeat ticker to detect connection issues + heartbeatTicker := time.NewTicker(5 * time.Second) + defer heartbeatTicker.Stop() + + lastMessageTime := time.Now() + // Start message sending goroutine go func() { defer func() { @@ -135,15 +181,32 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play return case <-done: return + case <-heartbeatTicker.C: + // If no message has been sent for a while, send a heartbeat + timeSinceLastMessage := time.Since(lastMessageTime) + if timeSinceLastMessage > 5*time.Second { + // Send an empty batch as a heartbeat + emptyBatch := &pb.ActionBatch{ + PlayerId: playerID, + LastSeenMessageTimestamp: lastSeenMessageTimestamp, + } + if err := writeMessage(conn, emptyBatch); err != nil { + log.Printf("Failed to send heartbeat: %v", err) + errChan <- err + return + } + lastMessageTime = time.Now() + } case <-actionTicker.C: player.Lock() if len(player.ActionQueue) > 0 { actions := make([]*pb.Action, len(player.ActionQueue)) copy(actions, player.ActionQueue) batch := &pb.ActionBatch{ - PlayerId: playerID, - Actions: actions, - Tick: player.CurrentTick, + PlayerId: playerID, + Actions: actions, + Tick: player.CurrentTick, + LastSeenMessageTimestamp: lastSeenMessageTimestamp, } player.ActionQueue = player.ActionQueue[:0] player.Unlock() @@ -152,6 +215,7 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play errChan <- err return } + lastMessageTime = time.Now() } else { player.Unlock() } @@ -260,6 +324,13 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 { log.Printf("Received %d chat messages from server", len(serverMessage.ChatMessages)) handler.HandleServerMessages(serverMessage.ChatMessages) + + // Update the last seen message timestamp to the most recent message + if len(serverMessage.ChatMessages) > 0 { + lastMsg := serverMessage.ChatMessages[len(serverMessage.ChatMessages)-1] + lastSeenMessageTimestamp = lastMsg.Timestamp + log.Printf("Updated last seen message timestamp to %d", lastSeenMessageTimestamp) + } } } } @@ -268,6 +339,7 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play // Wait for error or quit signal select { case <-quitChan: + log.Printf("Received quit signal, sending disconnect message") // Send disconnect message disconnectMsg := &pb.ActionBatch{ PlayerId: playerID, @@ -277,8 +349,10 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play }}, } writeMessage(conn, disconnectMsg) + close(done) case err := <-errChan: log.Printf("Network error: %v", err) + close(done) } }