350 lines
8.1 KiB
C++
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();
|
|
}
|
|
|
|
//@}
|