ref: bfbffcf197a5d78e90d463945d12c84d0f7deadf
parent: f9ab444cc614db3b2380d02750d142f9b17a8b90
author: Simon Howard <[email protected]>
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 */