diff options
author | Simon Howard | 2011-02-12 18:42:10 +0000 |
---|---|---|
committer | Simon Howard | 2011-02-12 18:42:10 +0000 |
commit | 516a7028994df6718289a7e3db4d07a45c95466b (patch) | |
tree | cf974cb4bd38b8e9d0101a41263b42b8c87e96c7 /src/net_query.c | |
parent | a15ba75736d15409876c1f0a44fffc99adf1c192 (diff) | |
parent | a9996b41e954d85fde5ec5188bbf6a7f4df88011 (diff) | |
download | chocolate-doom-516a7028994df6718289a7e3db4d07a45c95466b.tar.gz chocolate-doom-516a7028994df6718289a7e3db4d07a45c95466b.tar.bz2 chocolate-doom-516a7028994df6718289a7e3db4d07a45c95466b.zip |
Merge from raven-branch. FEATURE_MULTIPLAYER has been disabled
temporarily until the netgame changes on raven-branch are finished.
Subversion-branch: /branches/strife-branch
Subversion-revision: 2259
Diffstat (limited to 'src/net_query.c')
-rw-r--r-- | src/net_query.c | 691 |
1 files changed, 550 insertions, 141 deletions
diff --git a/src/net_query.c b/src/net_query.c index b50b4292..ae56dea6 100644 --- a/src/net_query.c +++ b/src/net_query.c @@ -37,50 +37,166 @@ #include "net_structrw.h" #include "net_sdl.h" -typedef struct +// DNS address of the Internet master server. + +#define MASTER_SERVER_ADDRESS "master.chocolate-doom.org:2342" + +// Time to wait for a response before declaring a timeout. + +#define QUERY_TIMEOUT_SECS 2 + +// Number of query attempts to make before giving up on a server. + +#define QUERY_MAX_ATTEMPTS 3 + +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_NO_RESPONSE +} 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 ping_time; + unsigned int query_time; + unsigned int query_attempts; + 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 query_loop_running = false; +static boolean printed_header = false; + +// Resolve the master server address. + +net_addr_t *NET_Query_ResolveMaster(net_context_t *context) +{ + net_addr_t *addr; + + addr = NET_ResolveAddress(context, MASTER_SERVER_ADDRESS); + + if (addr == NULL) + { + fprintf(stderr, "Warning: Failed to resolve address " + "for master server: %s\n", MASTER_SERVER_ADDRESS); + } + + return addr; +} -// Add a new address to the list of hosts that has responded +// Send a registration packet to the master server to register +// ourselves with the global list. -static queryresponse_t *AddResponder(net_addr_t *addr, - net_querydata_t *data) +void NET_Query_AddToMaster(net_addr_t *master_addr) { - queryresponse_t *response; + net_packet_t *packet; + + packet = NET_NewPacket(10); + NET_WriteInt16(packet, NET_MASTER_PACKET_TYPE_ADD); + NET_SendPacket(master_addr, packet); + NET_FreePacket(packet); +} + +// Process a packet received from the master server. + +void NET_Query_MasterResponse(net_packet_t *packet) +{ + unsigned int packet_type; + unsigned int result; + + if (!NET_ReadInt16(packet, &packet_type) + || !NET_ReadInt16(packet, &result)) + { + return; + } + + if (packet_type == NET_MASTER_PACKET_TYPE_ADD_RESPONSE) + { + if (result != 0) + { + // Only show the message once. + + if (!registered_with_master) + { + printf("Registered with master server at %s\n", + MASTER_SERVER_ADDRESS); + registered_with_master = true; + } + } + else + { + // Always show rejections. - responders = realloc(responders, - sizeof(queryresponse_t) * (num_responses + 1)); + printf("Failed to register with master server at %s\n", + MASTER_SERVER_ADDRESS); + } + } +} - response = &responders[num_responses]; - response->addr = addr; - response->data = *data; - ++num_responses; +// Send a query to the master server. + +static void NET_Query_SendMasterQuery(net_addr_t *addr) +{ + net_packet_t *packet; - 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->query_attempts = 0; + target->addr = addr; + ++num_targets; + + return target; } // Transmit a query packet @@ -104,166 +220,254 @@ static void NET_Query_SendQuery(net_addr_t *addr) NET_FreePacket(request); } -static void formatted_printf(int wide, char *s, ...) +static void NET_Query_ParseResponse(net_addr_t *addr, net_packet_t *packet, + net_query_callback_t callback, + void *user_data) { - va_list args; - int i; - - va_start(args, s); - i = vprintf(s, args); - va_end(args); + unsigned int packet_type; + net_querydata_t querydata; + query_target_t *target; + + // Read the header - while (i < wide) + if (!NET_ReadInt16(packet, &packet_type) + || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE) { - putchar(' '); - ++i; - } -} + return; + } -static char *GameDescription(GameMode_t mode, GameMission_t mission) -{ - switch (mode) + // Read query data + + if (!NET_ReadQueryData(packet, &querydata)) { - case shareware: - return "shareware"; - case registered: - return "registered"; - case retail: - return "ultimate"; - case commercial: - if (mission == doom2) - return "doom2"; - else if (mission == pack_tnt) - return "tnt"; - else if (mission == pack_plut) - return "plutonia"; - default: - return "unknown"; + return; } -} -static void PrintHeader(void) -{ - int i; + // Find the target that responded, or potentially add a new target + // if it was not already known (for LAN broadcast search) - formatted_printf(18, "Address"); - formatted_printf(8, "Players"); - puts("Description"); + target = GetTargetForAddr(addr, true); - for (i=0; i<70; ++i) - putchar('='); - putchar('\n'); + if (target->state != QUERY_TARGET_RESPONDED) + { + target->state = QUERY_TARGET_RESPONDED; + memcpy(&target->data, &querydata, sizeof(net_querydata_t)); + + // Calculate RTT. + + target->ping_time = I_GetTimeMS() - target->query_time; + + // Invoke callback to signal that we have a new address. + + callback(addr, &target->data, target->ping_time, user_data); + } } -static void PrintResponse(queryresponse_t *response) +// Parse a response packet from the master server. + +static void NET_Query_ParseMasterResponse(net_addr_t *master_addr, + net_packet_t *packet) { - formatted_printf(18, "%s: ", NET_AddrToString(response->addr)); - formatted_printf(8, "%i/%i", response->data.num_players, - response->data.max_players); + unsigned int packet_type; + query_target_t *target; + char *addr_str; + net_addr_t *addr; - if (response->data.gamemode != indetermined) + // Read the header. We are only interested in query responses. + + if (!NET_ReadInt16(packet, &packet_type) + || packet_type != NET_MASTER_PACKET_TYPE_QUERY_RESPONSE) { - printf("(%s) ", GameDescription(response->data.gamemode, - response->data.gamemission)); + return; } - if (response->data.server_state) + // Read a list of strings containing the addresses of servers + // that the master knows about. + + for (;;) { - printf("(game running) "); + 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); + } } - NET_SafePuts(response->data.description); + // 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) +static void NET_Query_ParsePacket(net_addr_t *addr, net_packet_t *packet, + net_query_callback_t callback, + void *user_data) { - unsigned int packet_type; - net_querydata_t querydata; - queryresponse_t *response; + query_target_t *target; + + // This might be the master server responding. - // Have we already received a packet from this host? + target = GetTargetForAddr(addr, false); - if (!CheckResponder(addr)) + if (target != NULL && target->type == QUERY_TARGET_MASTER) { - return; + NET_Query_ParseMasterResponse(addr, packet); } + else + { + NET_Query_ParseResponse(addr, packet, callback, user_data); + } +} - // Read the header +static void NET_Query_GetResponse(net_query_callback_t callback, + void *user_data) +{ + net_addr_t *addr; + net_packet_t *packet; - if (!NET_ReadInt16(packet, &packet_type) - || packet_type != NET_PACKET_TYPE_QUERY_RESPONSE) + if (NET_RecvPacket(query_context, &addr, &packet)) { - return; + NET_Query_ParsePacket(addr, packet, callback, user_data); + NET_FreePacket(packet); } +} - // Read query data +// Find a target we have not yet queried and send a query. - if (!NET_ReadQueryData(packet, &querydata)) +static void SendOneQuery(void) +{ + unsigned int now; + unsigned int i; + + now = I_GetTimeMS(); + + for (i = 0; i < num_targets; ++i) + { + // Not queried yet? + // Or last query timed out without a response? + + if (targets[i].state == QUERY_TARGET_QUEUED + || (targets[i].state == QUERY_TARGET_QUERIED + && now - targets[i].query_time > QUERY_TIMEOUT_SECS * 1000)) + { + break; + } + } + + if (i >= num_targets) { return; } - if (num_responses <= 0) + // Found a target to query. Send a query; how to do this depends on + // the target type. + + switch (targets[i].type) { - // If this is the first response, print the table header + case QUERY_TARGET_SERVER: + NET_Query_SendQuery(targets[i].addr); + break; - PrintHeader(); + case QUERY_TARGET_BROADCAST: + NET_Query_SendQuery(NULL); + break; + + case QUERY_TARGET_MASTER: + NET_Query_SendMasterQuery(targets[i].addr); + break; } - response = AddResponder(addr, &querydata); + //printf("Queried %s\n", NET_AddrToString(targets[i].addr)); + targets[i].state = QUERY_TARGET_QUERIED; + targets[i].query_time = I_GetTimeMS(); + ++targets[i].query_attempts; +} + +// Time out servers that have been queried and not responded. - PrintResponse(response); +static void CheckTargetTimeouts(void) +{ + unsigned int i; + unsigned int now; + + now = I_GetTimeMS(); + + for (i = 0; i < num_targets; ++i) + { + // We declare a target to be "no response" when we've sent + // multiple query packets to it (QUERY_MAX_ATTEMPTS) and + // received no response to any of them. + + if (targets[i].state == QUERY_TARGET_QUERIED + && targets[i].query_attempts >= QUERY_MAX_ATTEMPTS + && now - targets[i].query_time > QUERY_TIMEOUT_SECS * 1000) + { + targets[i].state = QUERY_TARGET_NO_RESPONSE; + } + } } -static void NET_Query_GetResponse(void) +// If all targets have responded or timed out, returns true. + +static boolean AllTargetsDone(void) { - net_addr_t *addr; - net_packet_t *packet; + unsigned int i; - if (NET_RecvPacket(query_context, &addr, &packet)) + for (i = 0; i < num_targets; ++i) { - NET_Query_ParsePacket(addr, packet); - NET_FreePacket(packet); + if (targets[i].state != QUERY_TARGET_RESPONDED + && targets[i].state != QUERY_TARGET_NO_RESPONSE) + { + return false; + } } + + return true; } -static net_addr_t *NET_Query_QueryLoop(net_addr_t *addr, - boolean find_one) +// Stop the query loop + +static void NET_Query_ExitLoop(void) { - int start_time; - int last_send_time; + query_loop_running = false; +} + +// Loop waiting for responses. +// The specified callback is invoked when a new server responds. - last_send_time = -1; - start_time = I_GetTimeMS(); +static void NET_Query_QueryLoop(net_query_callback_t callback, + void *user_data) +{ + query_loop_running = true; - while (I_GetTimeMS() < start_time + 5000) + while (query_loop_running && !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(); + NET_Query_GetResponse(callback, user_data); - // Found a response? - - if (find_one && num_responses > 0) - break; - // Don't thrash the CPU - - I_Sleep(100); - } - if (num_responses > 0) - return responders[0].addr; - else - return NULL; + I_Sleep(50); + + CheckTargetTimeouts(); + } } void NET_Query_Init(void) @@ -272,51 +476,256 @@ void NET_Query_Init(void) 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) +// Callback that exits the query loop when the first server is found. + +static void NET_Query_ExitCallback(net_addr_t *addr, net_querydata_t *data, + unsigned int ping_time, void *user_data) { - net_addr_t *net_addr; - - NET_Query_Init(); + NET_Query_ExitLoop(); +} - net_addr = NET_ResolveAddress(query_context, addr); +// Search the targets list and find a target that has responded. +// If none have responded, returns NULL. + +static query_target_t *FindFirstResponder(void) +{ + unsigned int i; - if (net_addr == NULL) + for (i = 0; i < num_targets; ++i) { - I_Error("NET_QueryAddress: Host '%s' not found!", addr); + if (targets[i].type == QUERY_TARGET_SERVER + && targets[i].state == QUERY_TARGET_RESPONDED) + { + return &targets[i]; + } } - printf("\nQuerying '%s'...\n\n", addr); + return NULL; +} - if (!NET_Query_QueryLoop(net_addr, true)) +// Return a count of the number of responses. + +static int GetNumResponses(void) +{ + unsigned int i; + int result; + + result = 0; + + for (i = 0; i < num_targets; ++i) { - I_Error("No response from '%s'", addr); + if (targets[i].type == QUERY_TARGET_SERVER + && targets[i].state == QUERY_TARGET_RESPONDED) + { + ++result; + } } - exit(0); + return result; +} + +void NET_QueryAddress(char *addr_str) +{ + net_addr_t *addr; + query_target_t *target; + + NET_Query_Init(); + + addr = NET_ResolveAddress(query_context, addr_str); + + if (addr == NULL) + { + I_Error("NET_QueryAddress: Host '%s' not found!", addr_str); + } + + // Add the address to the list of targets. + + target = GetTargetForAddr(addr, true); + + printf("\nQuerying '%s'...\n", addr_str); + + // Run query loop. + + NET_Query_QueryLoop(NET_Query_ExitCallback, NULL); + + // Check if the target responded. + + if (target->state == QUERY_TARGET_RESPONDED) + { + NET_QueryPrintCallback(addr, &target->data, target->ping_time, NULL); + } + else + { + I_Error("No response from '%s'", addr_str); + } } net_addr_t *NET_FindLANServer(void) { + query_target_t *target; + query_target_t *responder; + + NET_Query_Init(); + + // Add a broadcast target to the list. + + target = GetTargetForAddr(NULL, true); + target->type = QUERY_TARGET_BROADCAST; + + // Run the query loop, and stop at the first target found. + + NET_Query_QueryLoop(NET_Query_ExitCallback, NULL); + + responder = FindFirstResponder(); + + if (responder != NULL) + { + return responder->addr; + } + else + { + return NULL; + } +} + +int NET_LANQuery(net_query_callback_t callback, void *user_data) +{ + 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; + + NET_Query_QueryLoop(callback, user_data); + + return GetNumResponses(); } -void NET_LANQuery(void) +int NET_MasterQuery(net_query_callback_t callback, void *user_data) { + net_addr_t *master; + query_target_t *target; + NET_Query_Init(); - printf("\nSearching for servers on local LAN ...\n\n"); + // Resolve master address and add to targets list. + + master = NET_Query_ResolveMaster(query_context); + + if (master == NULL) + { + return 0; + } + + target = GetTargetForAddr(master, true); + target->type = QUERY_TARGET_MASTER; + + NET_Query_QueryLoop(callback, user_data); + + // Check that we got a response from the master, and display + // a warning if we didn't. + + if (target->state == QUERY_TARGET_NO_RESPONSE) + { + fprintf(stderr, "NET_MasterQuery: no response from master server.\n"); + } + + return GetNumResponses(); +} + +static void formatted_printf(int wide, char *s, ...) +{ + va_list args; + int i; - if (!NET_Query_QueryLoop(NULL, false)) + va_start(args, s); + i = vprintf(s, args); + va_end(args); + + while (i < wide) { - I_Error("No servers found"); + putchar(' '); + ++i; + } +} + +static char *GameDescription(GameMode_t mode, GameMission_t mission) +{ + switch (mode) + { + case shareware: + return "shareware"; + case registered: + return "registered"; + case retail: + return "ultimate"; + case commercial: + if (mission == doom2) + return "doom2"; + else if (mission == pack_tnt) + return "tnt"; + else if (mission == pack_plut) + return "plutonia"; + default: + return "unknown"; + } +} + +static void PrintHeader(void) +{ + int i; + + putchar('\n'); + formatted_printf(5, "Ping"); + formatted_printf(18, "Address"); + formatted_printf(8, "Players"); + puts("Description"); + + for (i=0; i<70; ++i) + putchar('='); + putchar('\n'); +} + +// Callback function that just prints information in a table. + +void NET_QueryPrintCallback(net_addr_t *addr, + net_querydata_t *data, + unsigned int ping_time, + void *user_data) +{ + // If this is the first server, print the header. + + if (!printed_header) + { + PrintHeader(); + printed_header = true; + } + + formatted_printf(5, "%4i", ping_time); + formatted_printf(18, "%s: ", NET_AddrToString(addr)); + formatted_printf(8, "%i/%i", data->num_players, + data->max_players); + + if (data->gamemode != indetermined) + { + printf("(%s) ", GameDescription(data->gamemode, + data->gamemission)); + } + + if (data->server_state) + { + printf("(game running) "); } - exit(0); + NET_SafePuts(data->description); } |