Gobligine/metaserver/netdriver.cpp

350 lines
8.1 KiB
C++

// _________ __ __
// / _____// |_____________ _/ |______ ____ __ __ ______
// \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
// / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
// /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
// \/ \/ \//_____/ \/
// ______________________ ______________________
// T H E W A R B E G I N S
// Stratagus - A free fantasy real time strategy game engine
//
/**@name netdriver.cpp - Session mangement (SDL_net Socket Implementation). */
//
// (c) Copyright 2005 by Edward Haase and Jimmy Salmon
//
// 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; only version 2 of the License.
//
// 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.
//
//@{
/*----------------------------------------------------------------------------
-- Includes
----------------------------------------------------------------------------*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#ifndef _MSC_VER
#include <errno.h>
#endif
#include "stratagus.h"
#include "netdriver.h"
#include "net_lowlevel.h"
/*----------------------------------------------------------------------------
-- Defines
----------------------------------------------------------------------------*/
/**
** LINK
**
** Adds an item to a linked list.
*/
#define LINK(first, item, last, count) { \
if (!first) \
first = item; \
if (!last) { \
last = item; \
} else { \
item->Next = last->Next; \
last->Next = item; \
item->Prev = last; \
last = item; \
if (!item->Prev->Next) \
item->Prev->Next = item;\
} \
++count; \
}
/**
** UNLINK
**
** Removes an item from the linked list.
*/
#define UNLINK(first, item, last, count) { \
if (item->Prev) \
item->Prev->Next = item->Next;\
if (item->Next) \
item->Next->Prev = item->Prev;\
if (item == last) \
last = item->Prev; \
if (item == first) \
first = item->Next; \
--count; \
}
/*----------------------------------------------------------------------------
-- Variables
----------------------------------------------------------------------------*/
static Socket MasterSocket;
static Socket HolePunchSocket;
SessionPool *Pool;
ServerStruct Server;
char UDPBuffer[16 /* GameData->IP */ + 6 /* GameData->Port */ + 1] = {'\0'};
unsigned long UDPHost = 0;
int UDPPort = 0;
/*----------------------------------------------------------------------------
-- Functions
----------------------------------------------------------------------------*/
/**
** Send a message to a session
**
** @param session Session to send the message to
** @param msg Message to send
*/
void Send(Session *session, const char *msg)
{
NetSendTCP(session->Sock, msg, strlen(msg));
}
/**
** Initialize the server
**
** @param port Defines the port to which the server will bind.
**
** @return 0 for success, non-zero for failure
*/
int ServerInit(int port)
{
int code = 0;
Pool = NULL;
if (NetInit() == -1) {
return -1;
}
if ((MasterSocket = NetOpenTCP(NULL, port)) == (Socket)-1) {
fprintf(stderr, "NetOpenTCP failed\n");
return -2;
}
if ((HolePunchSocket = NetOpenUDP(INADDR_ANY, port)) == (Socket)-1) {
fprintf(stderr, "NetOpenUDP failed\n");
return -2;
}
if (NetSetNonBlocking(MasterSocket) == -1) {
fprintf(stderr, "NetSetNonBlocking TCP failed\n");
code = -3;
goto error;
}
if (NetSetNonBlocking(HolePunchSocket) == -1) {
fprintf(stderr, "NetSetNonBlocking UDP failed\n");
code = -3;
goto error;
}
if (NetListenTCP(MasterSocket) == -1) {
fprintf(stderr, "NetListenTCP failed\n");
code = -4;
goto error;
}
if (!(Pool = new SessionPool)) {
fprintf(stderr, "Out of memory\n");
code = -5;
goto error;
}
if (!(Pool->Sockets = new SocketSet)) {
code = -6;
goto error;
}
Pool->First = NULL;
Pool->Last = NULL;
Pool->Count = 0;
return 0;
error:
NetCloseTCP(MasterSocket);
NetCloseUDP(HolePunchSocket);
NetExit();
return code;
}
/**
** ServerQuit: Releases the server socket.
*/
void ServerQuit(void)
{
NetCloseTCP(MasterSocket);
// begin clean up of any remaining sockets
if (Pool) {
Session *ptr;
while (Pool->First) {
ptr = Pool->First;
UNLINK(Pool->First, Pool->First, Pool->Last, Pool->Count);
NetCloseTCP(ptr->Sock);
delete ptr;
}
delete Pool->Sockets;
delete Pool;
}
NetExit();
}
/**
** Returns time (in seconds) that a session has been idle.
**
** @param session This is the session we are checking.
*/
static int IdleSeconds(Session *session)
{
return (int)(time(0) - session->Idle);
}
/**
** Destroys and cleans up session data.
**
** @param session Reference to the session to be killed.
*/
static int KillSession(Session *session)
{
DebugPrint("Closing connection from '%s'\n" _C_ session->AddrData.IPStr);
NetCloseTCP(session->Sock);
Pool->Sockets->DelSocket(session->Sock);
UNLINK(Pool->First, session, Pool->Last, Pool->Count);
delete session;
return 0;
}
/**
** Accept new connections
*/
static void AcceptConnections()
{
Session *new_session;
Socket new_socket;
unsigned long host;
int port;
while ((new_socket = NetAcceptTCP(MasterSocket, &host, &port)) != (Socket)-1) {
// Check if we're at MaxConnections
if (Pool->Count == Server.MaxConnections) {
NetSendTCP(new_socket, "Server Full\n", 12);
NetCloseTCP(new_socket);
break;
}
new_session = new Session;
if (!new_session) {
fprintf(stderr, "ERROR: %s\n", strerror(errno));
break;
}
new_session->Sock = new_socket;
new_session->Idle = time(0);
new_session->AddrData.Host = host;
sprintf(new_session->AddrData.IPStr, "%d.%d.%d.%d", NIPQUAD(ntohl(host)));
new_session->AddrData.Port = port;
DebugPrint("New connection from '%s'\n" _C_ new_session->AddrData.IPStr);
LINK(Pool->First, new_session, Pool->Last, Pool->Count);
Pool->Sockets->AddSocket(new_socket);
}
if (NetSocketReady(HolePunchSocket, 0)) {
NetRecvUDP(HolePunchSocket, UDPBuffer, sizeof(UDPBuffer), &UDPHost, &UDPPort);
} else {
UDPBuffer[0] = '\0';
UDPHost = 0;
UDPPort = 0;
}
}
/**
** Kick idlers
*/
static void KickIdlers(void)
{
Session *session;
Session *next;
for (session = Pool->First; session; ) {
next = session->Next;
if (IdleSeconds(session) > Server.IdleTimeout) {
DebugPrint("Kicking idler '%s'\n" _C_ session->AddrData.IPStr);
KillSession(session);
}
session = next;
}
}
/**
** Read data
*/
static int ReadData()
{
int result = Pool->Sockets->Select(0);
if (result == 0) {
// No sockets ready
return 0;
}
if (result == -1) {
// FIXME: print error message
return -1;
}
// ready sockets
for (Session *session = Pool->First; session; ) {
Session *next = session->Next;
if (Pool->Sockets->HasDataToRead(session->Sock)) {
// socket ready
session->Idle = time(0);
int clen = strlen(session->Buffer);
result = NetRecvTCP(session->Sock, session->Buffer + clen,
sizeof(session->Buffer) - clen);
if (result < 0) {
KillSession(session);
} else {
session->Buffer[clen + result] = '\0';
}
}
session = next;
}
return 0;
}
/**
** Accepts new connections, receives data, manages buffers,
*/
int UpdateSessions(void)
{
AcceptConnections();
if (!Pool->First) {
// No connections
return 0;
}
KickIdlers();
return ReadData();
}
//@}