shithub: choc

Download patch

ref: bfbffcf197a5d78e90d463945d12c84d0f7deadf
parent: f9ab444cc614db3b2380d02750d142f9b17a8b90
author: Simon Howard <fraggle@gmail.com>
date: Thu Dec 2 14:26:05 EST 2010

Refactor query code and add a -masterquery command line parameter to
query the master server.

Subversion-branch: /trunk/chocolate-doom
Subversion-revision: 2182

--- a/src/d_main.c
+++ b/src/d_main.c
@@ -844,6 +844,18 @@
     }
 
     //!
+    // @category net
+    //
+    // Query the Internet master server for a global list of active
+    // servers.
+    //
+
+    if (M_CheckParm("-masterquery"))
+    {
+        NET_MasterQuery();
+    }
+
+    //!
     // @arg <address>
     // @category net
     //
--- a/src/net_query.c
+++ b/src/net_query.c
@@ -38,18 +38,41 @@
 
 #define MASTER_SERVER_ADDRESS "master.chocolate-doom.org"
 
-typedef struct 
+typedef enum
 {
+    QUERY_TARGET_SERVER,       // Normal server target.
+    QUERY_TARGET_MASTER,       // The master server.
+    QUERY_TARGET_BROADCAST     // Send a broadcast query
+} query_target_type_t;
+
+typedef enum
+{
+    QUERY_TARGET_QUEUED,       // Query not yet sent
+    QUERY_TARGET_QUERIED,      // Query sent, waiting response
+    QUERY_TARGET_RESPONDED,    // Response received
+    QUERY_TARGET_TIMED_OUT
+} query_target_state_t;
+
+typedef struct
+{
+    query_target_type_t type;
+    query_target_state_t state;
     net_addr_t *addr;
     net_querydata_t data;
-} queryresponse_t;
+    unsigned int query_time;
+    boolean printed;
+} query_target_t;
 
+// Transmit a query packet
+
 static boolean registered_with_master = false;
 
 static net_context_t *query_context;
-static queryresponse_t *responders;
-static int num_responses;
+static query_target_t *targets;
+static int num_targets;
 
+static boolean printed_header = false;
+
 // Resolve the master server address.
 
 net_addr_t *NET_Query_ResolveMaster(net_context_t *context)
@@ -116,40 +139,49 @@
     }
 }
 
-// Add a new address to the list of hosts that has responded
+// Send a query to the master server.
 
-static queryresponse_t *AddResponder(net_addr_t *addr, 
-                                     net_querydata_t *data)
+static void NET_Query_SendMasterQuery(net_addr_t *addr)
 {
-    queryresponse_t *response;
+    net_packet_t *packet;
 
-    responders = realloc(responders, 
-                         sizeof(queryresponse_t) * (num_responses + 1));
-
-    response = &responders[num_responses];
-    response->addr = addr;
-    response->data = *data;
-    ++num_responses;
-
-    return response;
+    packet = NET_NewPacket(10);
+    NET_WriteInt16(packet, NET_MASTER_PACKET_TYPE_QUERY);
+    NET_SendPacket(addr, packet);
+    NET_FreePacket(packet);
 }
 
-// Returns true if the reply is from a host that has not previously
-// responded.
+// Given the specified address, find the target associated.  If no
+// target is found, and 'create' is true, a new target is created.
 
-static boolean CheckResponder(net_addr_t *addr)
+static query_target_t *GetTargetForAddr(net_addr_t *addr, boolean create)
 {
+    query_target_t *target;
     int i;
 
-    for (i=0; i<num_responses; ++i)
+    for (i=0; i<num_targets; ++i)
     {
-        if (responders[i].addr == addr)
+        if (targets[i].addr == addr)
         {
-            return false;
+            return &targets[i];
         }
     }
 
-    return true;
+    if (!create)
+    {
+        return NULL;
+    }
+
+    targets = realloc(targets, sizeof(query_target_t) * (num_targets + 1));
+
+    target = &targets[num_targets];
+    target->type = QUERY_TARGET_SERVER;
+    target->state = QUERY_TARGET_QUEUED;
+    target->printed = false;
+    target->addr = addr;
+    ++num_targets;
+
+    return target;
 }
 
 // Transmit a query packet
@@ -173,20 +205,225 @@
     NET_FreePacket(request);
 }
 
+static void NET_Query_ParseResponse(net_addr_t *addr, net_packet_t *packet)
+{
+    unsigned int packet_type;
+    net_querydata_t querydata;
+    query_target_t *target;
+
+    // Read the header
+
+    if (!NET_ReadInt16(packet, &packet_type)
+     || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE)
+    {
+        return;
+    }
+
+    // Read query data
+
+    if (!NET_ReadQueryData(packet, &querydata))
+    {
+        return;
+    }
+
+    // Find the target that responded, or potentially add a new target
+    // if it was not already known (for LAN broadcast search)
+
+    target = GetTargetForAddr(addr, true);
+
+    target->state = QUERY_TARGET_RESPONDED;
+    memcpy(&target->data, &querydata, sizeof(net_querydata_t));
+}
+
+// Parse a response packet from the master server.
+
+static void NET_Query_ParseMasterResponse(net_addr_t *master_addr,
+                                          net_packet_t *packet)
+{
+    unsigned int packet_type;
+    query_target_t *target;
+    char *addr_str;
+    net_addr_t *addr;
+
+    // Read the header.  We are only interested in query responses.
+
+    if (!NET_ReadInt16(packet, &packet_type)
+     || packet_type != NET_MASTER_PACKET_TYPE_QUERY_RESPONSE)
+    {
+        return;
+    }
+
+    // Read a list of strings containing the addresses of servers
+    // that the master knows about.
+
+    for (;;)
+    {
+        addr_str = NET_ReadString(packet);
+
+        if (addr_str == NULL)
+        {
+            break;
+        }
+
+        // Resolve address and add to targets list if it is not already
+        // there.
+
+        addr = NET_ResolveAddress(query_context, addr_str);
+
+        if (addr != NULL)
+        {
+            GetTargetForAddr(addr, true);
+        }
+    }
+
+    // Mark the master as having responded.
+
+    target = GetTargetForAddr(master_addr, true);
+    target->state = QUERY_TARGET_RESPONDED;
+}
+
+static void NET_Query_ParsePacket(net_addr_t *addr, net_packet_t *packet)
+{
+    query_target_t *target;
+
+    // This might be the master server responding.
+
+    target = GetTargetForAddr(addr, false);
+
+    if (target != NULL && target->type == QUERY_TARGET_MASTER)
+    {
+        NET_Query_ParseMasterResponse(addr, packet);
+    }
+    else
+    {
+        NET_Query_ParseResponse(addr, packet);
+    }
+}
+
+static void NET_Query_GetResponse(void)
+{
+    net_addr_t *addr;
+    net_packet_t *packet;
+
+    if (NET_RecvPacket(query_context, &addr, &packet))
+    {
+        NET_Query_ParsePacket(addr, packet);
+        NET_FreePacket(packet);
+    }
+}
+
+// Find a target we have not yet queried and send a query.
+
+static void SendOneQuery(void)
+{
+    unsigned int i;
+
+    for (i = 0; i < num_targets; ++i)
+    {
+        if (targets[i].state == QUERY_TARGET_QUEUED)
+        {
+            break;
+        }
+    }
+
+    if (i >= num_targets)
+    {
+        return;
+    }
+
+    // Found a target to query.  Send a query; how to do this depends on
+    // the target type.
+
+    switch (targets[i].type)
+    {
+        case QUERY_TARGET_SERVER:
+            NET_Query_SendQuery(targets[i].addr);
+            break;
+
+        case QUERY_TARGET_BROADCAST:
+            NET_Query_SendQuery(NULL);
+            break;
+
+        case QUERY_TARGET_MASTER:
+            NET_Query_SendMasterQuery(targets[i].addr);
+            break;
+    }
+
+    //printf("Queried %s\n", NET_AddrToString(targets[i].addr));
+    targets[i].state = QUERY_TARGET_QUERIED;
+    targets[i].query_time = I_GetTimeMS();
+}
+
+// Search the targets list and find a target that has responded.
+// If none have responded yet, returns NULL.
+
+static query_target_t *FindFirstResponder(void)
+{
+    unsigned int i;
+
+    for (i = 0; i < num_targets; ++i)
+    {
+        if (targets[i].type == QUERY_TARGET_SERVER
+         && targets[i].state == QUERY_TARGET_RESPONDED)
+        {
+            return &targets[i];
+        }
+    }
+
+    return NULL;
+}
+
+// Time out servers that have been queried and not responded.
+
+static void CheckTargetTimeouts(void)
+{
+    unsigned int i;
+    unsigned int now;
+
+    now = I_GetTimeMS();
+
+    for (i = 0; i < num_targets; ++i)
+    {
+        if (targets[i].state == QUERY_TARGET_QUERIED
+         && now - targets[i].query_time > 5000)
+        {
+            targets[i].state = QUERY_TARGET_TIMED_OUT;
+        }
+    }
+}
+
+// If all targets have responded or timed out, returns true.
+
+static boolean AllTargetsDone(void)
+{
+    unsigned int i;
+
+    for (i = 0; i < num_targets; ++i)
+    {
+        if (targets[i].state != QUERY_TARGET_RESPONDED
+         && targets[i].state != QUERY_TARGET_TIMED_OUT)
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
 static void formatted_printf(int wide, char *s, ...)
 {
     va_list args;
     int i;
-    
+
     va_start(args, s);
     i = vprintf(s, args);
     va_end(args);
 
-    while (i < wide) 
+    while (i < wide)
     {
         putchar(' ');
         ++i;
-    } 
+    }
 }
 
 static char *GameDescription(GameMode_t mode, GameMission_t mission)
@@ -224,81 +461,56 @@
     putchar('\n');
 }
 
-static void PrintResponse(queryresponse_t *response)
+static void PrintResponse(query_target_t *target)
 {
-    formatted_printf(18, "%s: ", NET_AddrToString(response->addr));
-    formatted_printf(8, "%i/%i", response->data.num_players, 
-                                 response->data.max_players);
+    formatted_printf(18, "%s: ", NET_AddrToString(target->addr));
+    formatted_printf(8, "%i/%i", target->data.num_players, 
+                                 target->data.max_players);
 
-    if (response->data.gamemode != indetermined)
+    if (target->data.gamemode != indetermined)
     {
-        printf("(%s) ", GameDescription(response->data.gamemode, 
-                                        response->data.gamemission));
+        printf("(%s) ", GameDescription(target->data.gamemode, 
+                                        target->data.gamemission));
     }
 
-    if (response->data.server_state)
+    if (target->data.server_state)
     {
         printf("(game running) ");
     }
 
-    NET_SafePuts(response->data.description);
+    NET_SafePuts(target->data.description);
 }
 
-static void NET_Query_ParsePacket(net_addr_t *addr, net_packet_t *packet)
+// Check for printing information about servers that have responded.
+
+static void CheckPrintOutput(void)
 {
-    unsigned int packet_type;
-    net_querydata_t querydata;
-    queryresponse_t *response;
+    unsigned int i;
 
-    // Have we already received a packet from this host?
-
-    if (!CheckResponder(addr))
+    for (i = 0; i < num_targets; ++i)
     {
-        return;
-    }
+        if (targets[i].type == QUERY_TARGET_SERVER
+         && targets[i].state == QUERY_TARGET_RESPONDED
+         && !targets[i].printed)
+        {
+            if (!printed_header)
+            {
+                PrintHeader();
+                printed_header = true;
+            }
 
-    // Read the header
-
-    if (!NET_ReadInt16(packet, &packet_type)
-     || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE)
-    {
-        return;
+            PrintResponse(&targets[i]);
+            targets[i].printed = true;
+        }
     }
-
-    // Read query data
-
-    if (!NET_ReadQueryData(packet, &querydata))
-    {
-        return;
-    }
-
-    if (num_responses <= 0)
-    {
-        // If this is the first response, print the table header
-
-        PrintHeader();
-    }
-
-    response = AddResponder(addr, &querydata);
-
-    PrintResponse(response);
 }
 
-static void NET_Query_GetResponse(void)
-{
-    net_addr_t *addr;
-    net_packet_t *packet;
+// Loop waiting for responses.
 
-    if (NET_RecvPacket(query_context, &addr, &packet))
-    {
-        NET_Query_ParsePacket(addr, packet);
-        NET_FreePacket(packet);
-    }
-}
-
-static net_addr_t *NET_Query_QueryLoop(net_addr_t *addr, 
-                                       boolean find_one)
+static net_addr_t *NET_Query_QueryLoop(boolean find_first,
+                                       boolean silent)
 {
+    query_target_t *responder;
     int start_time;
     int last_send_time;
 
@@ -305,34 +517,48 @@
     last_send_time = -1;
     start_time = I_GetTimeMS();
 
-    while (I_GetTimeMS() < start_time + 5000)
+    while (!AllTargetsDone())
     {
-        // Send a query once every second
+        // Send a query.  This will only send a single query.
+        // Because of the delay below, this is therefore rate limited.
 
-        if (last_send_time < 0 || I_GetTimeMS() > last_send_time + 1000)
-        {
-            NET_Query_SendQuery(addr);
-            last_send_time = I_GetTimeMS();
-        }
+        SendOneQuery();
 
         // Check for a response
 
         NET_Query_GetResponse();
 
+        // Output the responses
+
+        if (!silent)
+        {
+            CheckPrintOutput();
+        }
+
         // Found a response?
 
-        if (find_one && num_responses > 0)
+        if (find_first && FindFirstResponder())
+        {
             break;
-        
+        }
+
         // Don't thrash the CPU
-        
+
         I_Sleep(100);
+
+        CheckTargetTimeouts();
     }
 
-    if (num_responses > 0)
-        return responders[0].addr;
+    responder = FindFirstResponder();
+
+    if (responder != NULL)
+    {
+        return responder->addr;
+    }
     else
+    {
         return NULL;
+    }
 }
 
 void NET_Query_Init(void)
@@ -341,14 +567,16 @@
     NET_AddModule(query_context, &net_sdl_module);
     net_sdl_module.InitClient();
 
-    responders = NULL;
-    num_responses = 0;
+    targets = NULL;
+    num_targets = 0;
+
+    printed_header = false;
 }
 
 void NET_QueryAddress(char *addr)
 {
     net_addr_t *net_addr;
-    
+
     NET_Query_Init();
 
     net_addr = NET_ResolveAddress(query_context, addr);
@@ -358,9 +586,13 @@
         I_Error("NET_QueryAddress: Host '%s' not found!", addr);
     }
 
+    // Add the address to the list of targets.
+
+    GetTargetForAddr(net_addr, true);
+
     printf("\nQuerying '%s'...\n\n", addr);
 
-    if (!NET_Query_QueryLoop(net_addr, true))
+    if (!NET_Query_QueryLoop(true, false))
     {
         I_Error("No response from '%s'", addr);
     }
@@ -370,18 +602,32 @@
 
 net_addr_t *NET_FindLANServer(void)
 {
+    query_target_t *target;
+
     NET_Query_Init();
 
-    return NET_Query_QueryLoop(NULL, true);
+    // Add a broadcast target to the list.
+
+    target = GetTargetForAddr(NULL, true);
+    target->type = QUERY_TARGET_BROADCAST;
+
+    return NET_Query_QueryLoop(true, true);
 }
 
 void NET_LANQuery(void)
 {
+    query_target_t *target;
+
     NET_Query_Init();
 
     printf("\nSearching for servers on local LAN ...\n\n");
 
-    if (!NET_Query_QueryLoop(NULL, false))
+    // Add a broadcast target to the list.
+
+    target = GetTargetForAddr(NULL, true);
+    target->type = QUERY_TARGET_BROADCAST;
+
+    if (!NET_Query_QueryLoop(false, false))
     {
         I_Error("No servers found");
     }
@@ -389,4 +635,30 @@
     exit(0);
 }
 
+void NET_MasterQuery(void)
+{
+    net_addr_t *master;
+    query_target_t *target;
+
+    NET_Query_Init();
+
+    printf("\nSearching for servers on Internet ...\n\n");
+
+    // Resolve master address and add to targets list.
+
+    master = NET_Query_ResolveMaster(query_context);
+
+    if (master == NULL)
+    {
+        I_Error("Failed to resolve master server address");
+    }
+
+    target = GetTargetForAddr(master, true);
+    target->type = QUERY_TARGET_MASTER;
+
+    if (!NET_Query_QueryLoop(false, false))
+    {
+        I_Error("No servers found");
+    }
+}
 
--- a/src/net_query.h
+++ b/src/net_query.h
@@ -30,10 +30,11 @@
 extern void NET_QueryAddress(char *addr);
 extern void NET_LANQuery(void);
 extern net_addr_t *NET_FindLANServer(void);
+extern void NET_MasterQuery(void);
 
-net_addr_t *NET_Query_ResolveMaster(net_context_t *context);
-void NET_Query_AddToMaster(net_addr_t *master_addr);
-void NET_Query_MasterResponse(net_packet_t *packet);
+extern net_addr_t *NET_Query_ResolveMaster(net_context_t *context);
+extern void NET_Query_AddToMaster(net_addr_t *master_addr);
+extern void NET_Query_MasterResponse(net_packet_t *packet);
 
 #endif /* #ifndef NET_QUERY_H */