ref: 58656316f7e115a321a155f905b2daaefcde537d
dir: /src/net_server.c/
// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id: net_server.c 250 2006-01-02 21:04:10Z fraggle $
//
// Copyright(C) 2005 Simon Howard
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//
// $Log$
// Revision 1.9 2006/01/02 21:04:10 fraggle
// Create NET_SV_Shutdown function to shut down the server. Call it
// when quitting the game. Print the IP of the server correctly when
// connecting.
//
// Revision 1.8 2006/01/02 20:13:06 fraggle
// Refer to connected clients by their AddrToString() output rather than just
// the pointer to their struct. Listen for IP connections as well as
// loopback connections.
//
// Revision 1.7 2006/01/02 17:24:40 fraggle
// Remove test code
//
// Revision 1.6 2006/01/02 00:54:17 fraggle
// Fix packet not freed back after being sent.
// Code to disconnect clients from the server side.
//
// Revision 1.5 2006/01/02 00:00:08 fraggle
// Neater prefixes: NET_Client -> NET_CL_. NET_Server -> NET_SV_.
//
// Revision 1.4 2006/01/01 23:54:31 fraggle
// Client disconnect code
//
// Revision 1.3 2005/12/30 18:58:22 fraggle
// Fix client code to correctly send reply to server on connection.
// Add "waiting screen" while waiting for the game to start.
// Hook in the new networking code into the main game code.
//
// Revision 1.2 2005/12/29 21:29:55 fraggle
// Working client connect code
//
// Revision 1.1 2005/12/29 17:48:25 fraggle
// Add initial client/server connect code. Reorganise sources list in
// Makefile.am.
//
//
// Network server code
//
#include "doomdef.h"
#include "doomstat.h"
#include "i_system.h"
#include "net_client.h"
#include "net_defs.h"
#include "net_io.h"
#include "net_loop.h"
#include "net_packet.h"
#include "net_server.h"
#include "net_sdl.h"
typedef enum
{
// received a syn, sent an ack, waiting for an ack reply
CLIENT_STATE_WAITING_ACK,
// waiting for a game to start
CLIENT_STATE_WAITING_START,
// in game
CLIENT_STATE_IN_GAME,
// sent a DISCONNECT packet, waiting for a DISCONNECT_ACK reply
CLIENT_STATE_DISCONNECTING,
// client successfully disconnected
CLIENT_STATE_DISCONNECTED,
} net_clientstate_t;
#define MAX_RETRIES 5
typedef struct
{
boolean active;
net_clientstate_t state;
net_addr_t *addr;
int last_send_time;
int num_retries;
} net_client_t;
static boolean server_initialised = false;
static net_client_t clients[MAXNETNODES];
static net_context_t *server_context;
static void NET_SV_DisconnectClient(net_client_t *client)
{
if (client->active
&& client->state != CLIENT_STATE_DISCONNECTING
&& client->state != CLIENT_STATE_DISCONNECTED)
{
client->state = CLIENT_STATE_DISCONNECTING;
client->num_retries = 0;
client->last_send_time = -1;
}
}
static boolean ClientConnected(net_client_t *client)
{
// Check that the client is properly connected: ie. not in the
// process of connecting or disconnecting
return clients->active
&& clients->state != CLIENT_STATE_DISCONNECTING
&& clients->state != CLIENT_STATE_DISCONNECTED
&& clients->state != CLIENT_STATE_WAITING_ACK;
}
// returns the number of clients connected
static int NET_SV_NumClients(void)
{
int count;
int i;
count = 0;
for (i=0; i<MAXNETNODES; ++i)
{
if (ClientConnected(&clients[i]))
{
++count;
}
}
return count;
}
// returns a pointer to the client which controls the server
static net_client_t *NET_SV_Controller(void)
{
int i;
// first client in the list is the controller
for (i=0; i<MAXNETNODES; ++i)
{
if (ClientConnected(&clients[i]))
{
return &clients[i];
}
}
return NULL;
}
// Given an address, find the corresponding client
static net_client_t *NET_SV_FindClient(net_addr_t *addr)
{
int i;
for (i=0; i<MAXNETNODES; ++i)
{
if (clients[i].active && clients[i].addr == addr)
{
// found the client
return &clients[i];
}
}
return NULL;
}
// parse a SYN from a client(initiating a connection)
static void NET_SV_ParseSYN(net_packet_t *packet,
net_client_t *client,
net_addr_t *addr)
{
unsigned int magic;
int i;
// read the magic number
if (!NET_ReadInt32(packet, &magic))
{
return;
}
if (magic != NET_MAGIC_NUMBER)
{
// invalid magic number
return;
}
// received a valid SYN
// allocate a client slot if there isn't one already
if (client == NULL)
{
// find a slot, or return if none found
for (i=0; i<MAXNETNODES; ++i)
{
if (!clients[i].active)
{
client = &clients[i];
client->active = true;
client->addr = addr;
client->state = CLIENT_STATE_DISCONNECTED;
break;
}
}
if (client == NULL)
{
return;
}
}
// Set into the correct state if necessary
// Allow immediate reconnects from clients which just disconnected.
if (client->state == CLIENT_STATE_DISCONNECTED)
{
client->state = CLIENT_STATE_WAITING_ACK;
client->num_retries = 0;
}
if (client->state == CLIENT_STATE_WAITING_ACK)
{
// force an acknowledgement
client->last_send_time = -1;
}
}
// parse an ACK packet from a client
static void NET_SV_ParseACK(net_packet_t *packet, net_client_t *client)
{
if (client == NULL)
{
return;
}
if (client->state == CLIENT_STATE_WAITING_ACK)
{
// now waiting for the game to start
client->state = CLIENT_STATE_WAITING_START;
// force a waiting data packet to be sent immediately
client->last_send_time = -1;
}
}
static void NET_SV_ParseDisconnect(net_packet_t *packet, net_client_t *client)
{
net_packet_t *reply;
// sanity check
if (client == NULL)
{
return;
}
// This client wants to disconnect from the server.
// Send a DISCONNECT_ACK reply.
reply = NET_NewPacket(10);
NET_WriteInt16(reply, NET_PACKET_TYPE_DISCONNECT_ACK);
NET_SendPacket(client->addr, reply);
NET_FreePacket(reply);
client->last_send_time = I_GetTimeMS();
// Do not set to inactive immediately. Instead, set to the
// DISCONNECTED state. This is in case our acknowledgement is
// not received and another must be sent.
//
// After a few seconds, the client will get properly removed
// and cleaned up from the clients list.
client->state = CLIENT_STATE_DISCONNECTED;
//printf("SV: %s: client disconnected\n", NET_AddrToString(client->addr));
}
// Parse a DISCONNECT_ACK packet
static void NET_SV_ParseDisconnectACK(net_packet_t *packet,
net_client_t *client)
{
// Sanity check
if (client == NULL)
{
return;
}
if (client->state == CLIENT_STATE_DISCONNECTING)
{
// We have received an acknowledgement to our disconnect
// request. Client has been disconnected successfully.
// Place into the DISCONNECTED state to allow for cleanup.
client->state = CLIENT_STATE_DISCONNECTED;
client->last_send_time = -1;
}
}
// Process a packet received by the server
static void NET_SV_Packet(net_packet_t *packet, net_addr_t *addr)
{
net_client_t *client;
unsigned int packet_type;
// Find which client this packet came from
client = NET_SV_FindClient(addr);
// Read the packet type
if (!NET_ReadInt16(packet, &packet_type))
{
// no packet type
return;
}
//printf("SV: %s: %i\n", NET_AddrToString(addr), packet_type);
switch (packet_type)
{
case NET_PACKET_TYPE_SYN:
NET_SV_ParseSYN(packet, client, addr);
break;
case NET_PACKET_TYPE_ACK:
NET_SV_ParseACK(packet, client);
break;
case NET_PACKET_TYPE_GAMESTART:
break;
case NET_PACKET_TYPE_GAMEDATA:
break;
case NET_PACKET_TYPE_DISCONNECT:
NET_SV_ParseDisconnect(packet, client);
break;
case NET_PACKET_TYPE_DISCONNECT_ACK:
NET_SV_ParseDisconnectACK(packet, client);
break;
default:
// unknown packet type
break;
}
// If this address is not in the list of clients, be sure to
// free it back.
if (NET_SV_FindClient(addr) == NULL)
{
NET_FreeAddress(addr);
}
}
static void NET_SV_SendWaitingData(net_client_t *client)
{
net_packet_t *packet;
// time to send the client another status packet
packet = NET_NewPacket(10);
NET_WriteInt16(packet, NET_PACKET_TYPE_WAITING_DATA);
// include the number of clients waiting
NET_WriteInt8(packet, NET_SV_NumClients());
// indicate whether the client is the controller
NET_WriteInt8(packet, NET_SV_Controller() == client);
// send packet to client and free
NET_SendPacket(client->addr, packet);
NET_FreePacket(packet);
// update time
client->last_send_time = I_GetTimeMS();
}
// Perform any needed action on a client
static void NET_SV_RunClient(net_client_t *client)
{
net_packet_t *packet;
if (client->state == CLIENT_STATE_WAITING_ACK)
{
if (client->last_send_time < 0
|| I_GetTimeMS() - client->last_send_time > 1000)
{
// it has been a second since the last ACK was sent, and
// still no reply.
if (client->num_retries < MAX_RETRIES)
{
// send another ACK
packet = NET_NewPacket(10);
NET_WriteInt16(packet, NET_PACKET_TYPE_ACK);
NET_SendPacket(client->addr, packet);
NET_FreePacket(packet);
client->last_send_time = I_GetTimeMS();
++client->num_retries;
}
else
{
// no more retries allowed.
client->active = false;
NET_FreeAddress(client->addr);
}
}
}
else if (client->state == CLIENT_STATE_WAITING_START)
{
// Waiting for the game to start
// Send information once every second
if (client->last_send_time < 0
|| I_GetTimeMS() - client->last_send_time > 1000)
{
NET_SV_SendWaitingData(client);
}
}
else if (client->state == CLIENT_STATE_DISCONNECTING)
{
// Waiting for a reply to our DISCONNECT request.
if (client->last_send_time < 0
|| I_GetTimeMS() - client->last_send_time > 1000)
{
// it has been a second since the last disconnect packet
// was sent, and still no reply.
if (client->num_retries < MAX_RETRIES)
{
// send another disconnect
packet = NET_NewPacket(10);
NET_WriteInt16(packet, NET_PACKET_TYPE_DISCONNECT);
NET_SendPacket(client->addr, packet);
NET_FreePacket(packet);
client->last_send_time = I_GetTimeMS();
++client->num_retries;
}
else
{
// No more retries allowed.
// Force disconnect.
client->active = false;
NET_FreeAddress(client->addr);
}
}
}
else if (client->state == CLIENT_STATE_DISCONNECTED)
{
// Client has disconnected.
//
// See NET_SV_ParseDisconnect() above.
// Remove from the list after five seconds
if (client->last_send_time < 0
|| I_GetTimeMS() - client->last_send_time > 5000)
{
//printf("SV: %s: deactivated\n", NET_AddrToString(client->addr));
client->active = false;
NET_FreeAddress(client->addr);
}
}
}
// Initialise server and wait for connections
void NET_SV_Init(void)
{
int i;
// initialise send/receive context, with loopback send/recv
server_context = NET_NewContext();
NET_AddModule(server_context, &net_loop_server_module);
net_loop_server_module.InitServer();
NET_AddModule(server_context, &net_sdl_module);
net_sdl_module.InitServer();
// no clients yet
for (i=0; i<MAXNETNODES; ++i)
{
clients[i].active = false;
}
server_initialised = true;
}
// Run server code to check for new packets/send packets as the server
// requires
void NET_SV_Run(void)
{
net_addr_t *addr;
net_packet_t *packet;
int i;
if (!server_initialised)
{
return;
}
while (NET_RecvPacket(server_context, &addr, &packet))
{
NET_SV_Packet(packet, addr);
}
// "Run" any clients that may have things to do, independent of responses
// to received packets
for (i=0; i<MAXNETNODES; ++i)
{
if (clients[i].active)
{
NET_SV_RunClient(&clients[i]);
}
}
}
void NET_SV_Shutdown(void)
{
int i;
boolean running;
int start_time;
if (!server_initialised)
{
return;
}
fprintf(stderr, "SV: Shutting down server...\n");
// Disconnect all clients
for (i=0; i<MAXNETNODES; ++i)
{
if (clients[i].active)
{
NET_SV_DisconnectClient(&clients[i]);
}
}
// Wait for all clients to finish disconnecting
start_time = I_GetTimeMS();
running = true;
while (running)
{
// Check if any clients are still not finished
running = false;
for (i=0; i<MAXNETNODES; ++i)
{
if (clients[i].active)
{
running = true;
}
}
// Timed out?
if (I_GetTimeMS() - start_time > 5000)
{
running = false;
fprintf(stderr, "SV: Timed out waiting for clients to disconnect.\n");
}
// Run the client code in case this is a loopback client.
NET_CL_Run();
NET_SV_Run();
// Don't hog the CPU
I_Sleep(10);
}
}