Files
assaultcube-/source/src/serverms.cpp
T
flowtron 70250d7d41 clean-up & commentary
git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@6106 e016b1c5-ed68-44aa-a2f7-31cac88cffde
2011-02-15 18:42:26 +00:00

363 lines
12 KiB
C++

// all server side masterserver and pinging functionality
#include "cube.h"
#ifdef STANDALONE
bool resolverwait(const char *name, ENetAddress *address)
{
return enet_address_set_host(address, name) >= 0;
}
int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &remoteaddress)
{
int result = enet_socket_connect(sock, &remoteaddress);
if(result<0) enet_socket_destroy(sock);
return result;
}
#endif
ENetSocket mastersock = ENET_SOCKET_NULL;
ENetAddress masteraddress = { ENET_HOST_ANY, ENET_PORT_ANY }, serveraddress = { ENET_HOST_ANY, ENET_PORT_ANY };
string mastername = AC_MASTER_URI;
int lastupdatemaster = 0;
vector<char> masterout, masterin;
int masteroutpos = 0, masterinpos = 0;
void disconnectmaster()
{
if(mastersock == ENET_SOCKET_NULL) return;
enet_socket_destroy(mastersock);
mastersock = ENET_SOCKET_NULL;
masterout.setsize(0);
masterin.setsize(0);
masteroutpos = masterinpos = 0;
masteraddress.host = ENET_HOST_ANY;
masteraddress.port = ENET_PORT_ANY;
//lastupdatemaster = 0;
}
ENetSocket connectmaster()
{
if(!mastername[0]) return ENET_SOCKET_NULL;
extern servercommandline scl;
if(scl.maxclients>MAXCL) { logline(ACLOG_WARNING, "maxclient exceeded: cannot register"); return ENET_SOCKET_NULL; }
if(masteraddress.host == ENET_HOST_ANY)
{
logline(ACLOG_INFO, "looking up %s...", mastername);
masteraddress.port = AC_MASTER_PORT;
if(!resolverwait(mastername, &masteraddress)) return ENET_SOCKET_NULL;
}
ENetSocket sock = enet_socket_create(ENET_SOCKET_TYPE_STREAM);
if(sock != ENET_SOCKET_NULL && serveraddress.host != ENET_HOST_ANY && enet_socket_bind(sock, &serveraddress) < 0)
{
enet_socket_destroy(sock);
sock = ENET_SOCKET_NULL;
}
if(sock == ENET_SOCKET_NULL || connectwithtimeout(sock, mastername, masteraddress) < 0)
{
logline(ACLOG_WARNING, sock==ENET_SOCKET_NULL ? "could not open socket" : "could not connect");
return ENET_SOCKET_NULL;
}
enet_socket_set_option(sock, ENET_SOCKOPT_NONBLOCK, 1);
return sock;
}
bool requestmaster(const char *req)
{
if(mastersock == ENET_SOCKET_NULL)
{
mastersock = connectmaster();
if(mastersock == ENET_SOCKET_NULL) return false;
}
masterout.put(req, strlen(req));
return true;
}
bool requestmasterf(const char *fmt, ...)
{
defvformatstring(req, fmt, fmt);
return requestmaster(req);
}
extern void processmasterinput(const char *cmd, int cmdlen, const char *args);
void processmasterinput()
{
if(masterinpos >= masterin.length()) return;
char *input = &masterin[masterinpos], *end = (char *)memchr(input, '\n', masterin.length() - masterinpos);
while(end)
{
*end++ = '\0';
const char *args = input;
while(args < end && !isspace(*args)) args++;
int cmdlen = args - input;
while(args < end && isspace(*args)) args++;
if(!strncmp(input, "failreg", cmdlen))
logline(ACLOG_WARNING, "master server registration failed: %s", args);
else if(!strncmp(input, "succreg", cmdlen))
{
logline(ACLOG_INFO, "master server registration succeeded");
}
else processmasterinput(input, cmdlen, args);
masterinpos = end - masterin.getbuf();
input = end;
end = (char *)memchr(input, '\n', masterin.length() - masterinpos);
}
if(masterinpos >= masterin.length())
{
masterin.setsize(0);
masterinpos = 0;
}
}
void flushmasteroutput()
{
if(masterout.empty()) return;
ENetBuffer buf;
buf.data = &masterout[masteroutpos];
buf.dataLength = masterout.length() - masteroutpos;
int sent = enet_socket_send(mastersock, NULL, &buf, 1);
if(sent >= 0)
{
masteroutpos += sent;
if(masteroutpos >= masterout.length())
{
masterout.setsize(0);
masteroutpos = 0;
}
}
else disconnectmaster();
}
void flushmasterinput()
{
if(masterin.length() >= masterin.capacity())
masterin.reserve(4096);
ENetBuffer buf;
buf.data = &masterin[masterin.length()];
buf.dataLength = masterin.capacity() - masterin.length();
int recv = enet_socket_receive(mastersock, NULL, &buf, 1);
if(recv > 0)
{
masterin.advance(recv);
processmasterinput();
}
else disconnectmaster();
}
extern char *global_name;
extern int interm;
extern int totalclients;
// send alive signal to masterserver after 40 minutes of uptime and if currently in intermission (so theoretically <= 1 hour)
// TODO?: implement a thread to drop this "only in intermission" business, we'll need it once AUTH gets active!
static inline void updatemasterserver(int millis, int port)
{
if(!lastupdatemaster || ((millis-lastupdatemaster)>40*60*1000 && (interm || !totalclients)))
{
char servername[30]; memset(servername,'\0',30); filtertext(servername,global_name,-1,20);
if(mastername[0]) requestmasterf("regserv %d %s %d\n", port, servername[0] ? servername : "noname", AC_VERSION);
lastupdatemaster = millis + 1;
}
}
ENetSocket pongsock = ENET_SOCKET_NULL, lansock = ENET_SOCKET_NULL;
extern int getpongflags(enet_uint32 ip);
void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version)
{
flushmasteroutput();
updatemasterserver(millis, localaddr.port);
static ENetSocketSet sockset;
ENET_SOCKETSET_EMPTY(sockset);
ENetSocket maxsock = pongsock;
ENET_SOCKETSET_ADD(sockset, pongsock);
if(mastersock != ENET_SOCKET_NULL)
{
maxsock = max(maxsock, mastersock);
ENET_SOCKETSET_ADD(sockset, mastersock);
}
if(lansock != ENET_SOCKET_NULL)
{
maxsock = max(maxsock, lansock);
ENET_SOCKETSET_ADD(sockset, lansock);
}
if(enet_socketset_select(maxsock, &sockset, NULL, 0) <= 0) return;
// reply all server info requests
static uchar data[MAXTRANS];
ENetBuffer buf;
ENetAddress addr;
buf.data = data;
int len;
loopi(2)
{
ENetSocket sock = i ? lansock : pongsock;
if(sock == ENET_SOCKET_NULL || !ENET_SOCKETSET_CHECK(sockset, sock)) continue;
buf.dataLength = sizeof(data);
len = enet_socket_receive(sock, &addr, &buf, 1);
if(len < 0) continue;
// ping & pong buf
ucharbuf pi(data, len), po(&data[len], sizeof(data)-len);
bool std = false;
if(getint(pi) != 0) // std pong
{
extern struct servercommandline scl;
extern string servdesc_current;
(*mnum)++; *mrec += len; std = true;
putint(po, protocol_version);
putint(po, mode);
putint(po, numplayers);
putint(po, minremain);
sendstring(smapname, po);
sendstring(servdesc_current, po);
putint(po, scl.maxclients);
putint(po, getpongflags(addr.host));
if(pi.remaining())
{
int query = getint(pi);
switch(query)
{
case EXTPING_NAMELIST:
{
extern void extping_namelist(ucharbuf &p);
putint(po, query);
extping_namelist(po);
break;
}
case EXTPING_SERVERINFO:
{
extern void extping_serverinfo(ucharbuf &pi, ucharbuf &po);
putint(po, query);
extping_serverinfo(pi, po);
break;
}
case EXTPING_MAPROT:
{
extern void extping_maprot(ucharbuf &po);
putint(po, query);
extping_maprot(po);
break;
}
case EXTPING_UPLINKSTATS:
{
extern void extping_uplinkstats(ucharbuf &po);
putint(po, query);
extping_uplinkstats(po);
break;
}
case EXTPING_NOP:
default:
putint(po, EXTPING_NOP);
break;
}
}
}
else // ext pong - additional server infos
{
(*cnum)++; *crec += len;
int extcmd = getint(pi);
putint(po, EXT_ACK);
putint(po, EXT_VERSION);
switch(extcmd)
{
case EXT_UPTIME: // uptime in seconds
{
putint(po, uint(millis)/1000);
break;
}
case EXT_PLAYERSTATS: // playerstats
{
int cn = getint(pi); // get requested player, -1 for all
if(!valid_client(cn) && cn != -1)
{
putint(po, EXT_ERROR);
break;
}
putint(po, EXT_ERROR_NONE); // add no error flag
int bpos = po.length(); // remember buffer position
putint(po, EXT_PLAYERSTATS_RESP_IDS); // send player ids following
extinfo_cnbuf(po, cn);
*csend += int(buf.dataLength = len + po.length());
enet_socket_send(pongsock, &addr, &buf, 1); // send all available player ids
po.len = bpos;
extinfo_statsbuf(po, cn, bpos, pongsock, addr, buf, len, csend);
return;
}
case EXT_TEAMSCORE:
extinfo_teamscorebuf(po);
break;
default:
putint(po,EXT_ERROR);
break;
}
}
buf.dataLength = len + po.length();
enet_socket_send(pongsock, &addr, &buf, 1);
if(std) *msend += (int)buf.dataLength;
else *csend += (int)buf.dataLength;
}
if(mastersock != ENET_SOCKET_NULL && ENET_SOCKETSET_CHECK(sockset, mastersock)) flushmasterinput();
}
// this function should be made better, because it is used just ONCE (no need of so much parameters)
void servermsinit(const char *master, const char *ip, int infoport, bool listen)
{
copystring(mastername, master);
disconnectmaster();
if(listen)
{
ENetAddress address = { ENET_HOST_ANY, infoport };
if(*ip)
{
if(enet_address_set_host(&address, ip)<0) logline(ACLOG_WARNING, "server ip not resolved");
else serveraddress.host = address.host;
}
pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
if(pongsock != ENET_SOCKET_NULL && enet_socket_bind(pongsock, &address) < 0)
{
enet_socket_destroy(pongsock);
pongsock = ENET_SOCKET_NULL;
}
if(pongsock == ENET_SOCKET_NULL) fatal("could not create server info socket");
else enet_socket_set_option(pongsock, ENET_SOCKOPT_NONBLOCK, 1);
address.port = CUBE_SERVINFO_PORT_LAN;
lansock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
if(lansock != ENET_SOCKET_NULL && (enet_socket_set_option(lansock, ENET_SOCKOPT_REUSEADDR, 1) < 0 || enet_socket_bind(lansock, &address) < 0))
{
enet_socket_destroy(lansock);
lansock = ENET_SOCKET_NULL;
}
if(lansock == ENET_SOCKET_NULL) logline(ACLOG_WARNING, "could not create LAN server info socket");
else enet_socket_set_option(lansock, ENET_SOCKOPT_NONBLOCK, 1);
}
}