shithub: choc

ref: a78ddf0ecadb3580f5f09b09a931abc5734b7f33
dir: /src/net_server.c/

View raw version
// Emacs style mode select   -*- C++ -*- 
//-----------------------------------------------------------------------------
//
// $Id: net_server.c 251 2006-01-02 21:48:37Z 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.10  2006/01/02 21:48:37  fraggle
// fix client connected function
//
// 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 client->active
        && client->state != CLIENT_STATE_DISCONNECTING
        && client->state != CLIENT_STATE_DISCONNECTED
        && client->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);
    }
}