1 // server.cpp: little more than enhanced multicaster
2 // runs dedicated or as client coroutine
10 void localservertoclient(int chan
, uchar
*buf
, int len
) {}
11 void fatal(const char *s
, ...)
15 s_sprintfdlv(msg
,s
,s
);
16 printf("servererror: %s\n", msg
);
23 igameclient
*cl
= NULL
;
24 igameserver
*sv
= NULL
;
25 iclientcom
*cc
= NULL
;
26 icliententities
*et
= NULL
;
28 hashtable
<const char *, igame
*> *gamereg
= NULL
;
30 vector
<char *> gameargs
;
32 void registergame(const char *name
, igame
*ig
)
34 if(!gamereg
) gamereg
= new hashtable
<const char *, igame
*>;
35 (*gamereg
)[name
] = ig
;
38 void initgame(const char *game
)
40 igame
**ig
= gamereg
->access(game
);
41 if(!ig
) fatal("cannot start game module: %s", game
);
42 sv
= (*ig
)->newserver();
43 cl
= (*ig
)->newclient();
52 if(!cl
|| !cl
->clientoption(gameargs
[i
]))
54 if(!sv
->serveroption(gameargs
[i
]))
56 printf("unknown command-line option: %s\n", gameargs
[i
]);
58 conoutf(CON_ERROR
, "unknown command-line option: %s", gameargs
[i
]);
64 // all network traffic is in 32bit ints, which are then compressed using the following simple scheme (assumes that most values are small).
66 void putint(ucharbuf
&p
, int n
)
68 if(n
<128 && n
>-127) p
.put(n
);
69 else if(n
<0x8000 && n
>=-0x8000) { p
.put(0x80); p
.put(n
); p
.put(n
>>8); }
70 else { p
.put(0x81); p
.put(n
); p
.put(n
>>8); p
.put(n
>>16); p
.put(n
>>24); }
73 int getint(ucharbuf
&p
)
75 int c
= (char)p
.get();
76 if(c
==-128) { int n
= p
.get(); n
|= char(p
.get())<<8; return n
; }
77 else if(c
==-127) { int n
= p
.get(); n
|= p
.get()<<8; n
|= p
.get()<<16; return n
|(p
.get()<<24); }
81 // much smaller encoding for unsigned integers up to 28 bits, but can handle signed
82 void putuint(ucharbuf
&p
, int n
)
84 if(n
< 0 || n
>= (1<<21))
86 p
.put(0x80 | (n
& 0x7F));
87 p
.put(0x80 | ((n
>> 7) & 0x7F));
88 p
.put(0x80 | ((n
>> 14) & 0x7F));
91 else if(n
< (1<<7)) p
.put(n
);
94 p
.put(0x80 | (n
& 0x7F));
99 p
.put(0x80 | (n
& 0x7F));
100 p
.put(0x80 | ((n
>> 7) & 0x7F));
105 int getuint(ucharbuf
&p
)
110 n
+= (p
.get() << 7) - 0x80;
111 if(n
& (1<<14)) n
+= (p
.get() << 14) - (1<<14);
112 if(n
& (1<<21)) n
+= (p
.get() << 21) - (1<<21);
113 if(n
& (1<<28)) n
|= 0xF0000000;
118 void sendstring(const char *t
, ucharbuf
&p
)
120 while(*t
) putint(p
, *t
++);
124 void getstring(char *text
, ucharbuf
&p
, int len
)
129 if(t
>=&text
[len
]) { text
[len
-1] = 0; return; }
130 if(!p
.remaining()) { *t
= 0; return; }
136 void filtertext(char *dst
, const char *src
, bool whitespace
, int len
)
138 for(int c
= *src
; c
; c
= *++src
)
142 case '\f': ++src
; continue;
144 if(isspace(c
) ? whitespace
: isprint(c
))
153 enum { ST_EMPTY
, ST_LOCAL
, ST_TCPIP
};
155 struct client
// server side version of "dynent" type
164 vector
<client
*> clients
;
166 ENetHost
*serverhost
= NULL
;
167 size_t bsend
= 0, brec
= 0;
169 ENetSocket pongsock
= ENET_SOCKET_NULL
;
173 if(serverhost
) enet_host_destroy(serverhost
);
176 void sendfile(int cn
, int chan
, FILE *file
, const char *format
, ...)
179 extern ENetHost
*clienthost
;
184 if(!clienthost
|| clienthost
->peers
[0].state
!= ENET_PEER_STATE_CONNECTED
)
188 else if(cn
>= clients
.length() || clients
[cn
]->type
!= ST_TCPIP
) return;
190 fseek(file
, 0, SEEK_END
);
191 int len
= ftell(file
);
192 bool reliable
= false;
193 if(*format
=='r') { reliable
= true; ++format
; }
194 ENetPacket
*packet
= enet_packet_create(NULL
, MAXTRANS
+len
, ENET_PACKET_FLAG_RELIABLE
);
197 ucharbuf
p(packet
->data
, packet
->dataLength
);
199 va_start(args
, format
);
200 while(*format
) switch(*format
++)
204 int n
= isdigit(*format
) ? *format
++-'0' : 1;
205 loopi(n
) putint(p
, va_arg(args
, int));
208 case 's': sendstring(va_arg(args
, const char *), p
); break;
209 case 'l': putint(p
, len
); break;
212 enet_packet_resize(packet
, p
.length()+len
);
214 fread(&packet
->data
[p
.length()], 1, len
, file
);
215 enet_packet_resize(packet
, p
.length()+len
);
217 if(cn
>= 0) enet_peer_send(clients
[cn
]->peer
, chan
, packet
);
219 else enet_peer_send(&clienthost
->peers
[0], chan
, packet
);
222 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
225 void process(ENetPacket
*packet
, int sender
, int chan
);
226 //void disconnect_client(int n, int reason);
228 void *getinfo(int i
) { return !clients
.inrange(i
) || clients
[i
]->type
==ST_EMPTY
? NULL
: clients
[i
]->info
; }
229 int getnumclients() { return clients
.length(); }
230 uint
getclientip(int n
) { return clients
.inrange(n
) && clients
[n
]->type
==ST_TCPIP
? clients
[n
]->peer
->address
.host
: 0; }
232 void sendpacket(int n
, int chan
, ENetPacket
*packet
, int exclude
)
236 sv
->recordpacket(chan
, packet
->data
, packet
->dataLength
);
237 loopv(clients
) if(i
!=exclude
) sendpacket(i
, chan
, packet
);
240 switch(clients
[n
]->type
)
244 enet_peer_send(clients
[n
]->peer
, chan
, packet
);
245 bsend
+= packet
->dataLength
;
250 localservertoclient(chan
, packet
->data
, (int)packet
->dataLength
);
255 void sendf(int cn
, int chan
, const char *format
, ...)
258 bool reliable
= false;
259 if(*format
=='r') { reliable
= true; ++format
; }
260 ENetPacket
*packet
= enet_packet_create(NULL
, MAXTRANS
, reliable
? ENET_PACKET_FLAG_RELIABLE
: 0);
261 ucharbuf
p(packet
->data
, packet
->dataLength
);
263 va_start(args
, format
);
264 while(*format
) switch(*format
++)
267 exclude
= va_arg(args
, int);
272 int n
= va_arg(args
, int);
273 int *v
= va_arg(args
, int *);
274 loopi(n
) putint(p
, v
[i
]);
280 int n
= isdigit(*format
) ? *format
++-'0' : 1;
281 loopi(n
) putint(p
, va_arg(args
, int));
284 case 's': sendstring(va_arg(args
, const char *), p
); break;
287 int n
= va_arg(args
, int);
288 enet_packet_resize(packet
, packet
->dataLength
+n
);
289 p
.buf
= packet
->data
;
291 p
.put(va_arg(args
, uchar
*), n
);
296 enet_packet_resize(packet
, p
.length());
297 sendpacket(cn
, chan
, packet
, exclude
);
298 if(packet
->referenceCount
==0) enet_packet_destroy(packet
);
301 const char *disc_reasons
[] = { "normal", "end of packet", "client num", "kicked/banned", "tag type", "ip is banned", "server is in private mode", "server FULL (maxclients)" };
303 void disconnect_client(int n
, int reason
)
305 if(clients
[n
]->type
!=ST_TCPIP
) return;
306 s_sprintfd(s
)("client (%s) disconnected because: %s\n", clients
[n
]->hostname
, disc_reasons
[reason
]);
308 enet_peer_disconnect(clients
[n
]->peer
, reason
);
309 sv
->clientdisconnect(n
);
310 clients
[n
]->type
= ST_EMPTY
;
311 clients
[n
]->peer
->data
= NULL
;
312 sv
->deleteinfo(clients
[n
]->info
);
313 clients
[n
]->info
= NULL
;
317 void process(ENetPacket
*packet
, int sender
, int chan
) // sender may be -1
319 ucharbuf
p(packet
->data
, (int)packet
->dataLength
);
320 sv
->parsepacket(sender
, chan
, (packet
->flags
&ENET_PACKET_FLAG_RELIABLE
)!=0, p
);
321 if(p
.overread()) { disconnect_client(sender
, DISC_EOP
); return; }
324 void send_welcome(int n
)
326 ENetPacket
*packet
= enet_packet_create (NULL
, MAXTRANS
, ENET_PACKET_FLAG_RELIABLE
);
327 ucharbuf
p(packet
->data
, packet
->dataLength
);
328 int chan
= sv
->welcomepacket(p
, n
, packet
);
329 enet_packet_resize(packet
, p
.length());
330 sendpacket(n
, chan
, packet
);
331 if(packet
->referenceCount
==0) enet_packet_destroy(packet
);
334 void localclienttoserver(int chan
, ENetPacket
*packet
)
336 process(packet
, 0, chan
);
337 if(packet
->referenceCount
==0) enet_packet_destroy(packet
);
342 loopv(clients
) if(clients
[i
]->type
==ST_EMPTY
)
344 clients
[i
]->info
= sv
->newinfo();
347 client
*c
= new client
;
348 c
->num
= clients
.length();
349 c
->info
= sv
->newinfo();
354 int localclients
= 0, nonlocalclients
= 0;
356 bool hasnonlocalclients() { return nonlocalclients
!=0; }
357 bool haslocalclients() { return localclients
!=0; }
359 static ENetAddress pongaddr
;
361 void sendserverinforeply(ucharbuf
&p
)
365 buf
.dataLength
= p
.length();
366 enet_socket_send(pongsock
, &pongaddr
, &buf
, 1);
369 void sendpongs() // reply all server info requests
372 uchar pong
[MAXTRANS
];
374 enet_uint32 events
= ENET_SOCKET_WAIT_RECEIVE
;
376 while(enet_socket_wait(pongsock
, &events
, 0) >= 0 && events
)
378 buf
.dataLength
= sizeof(pong
);
379 len
= enet_socket_receive(pongsock
, &pongaddr
, &buf
, 1);
381 ucharbuf
req(pong
, len
), p(pong
, sizeof(pong
));
383 sv
->serverinforeply(req
, p
);
388 bool resolverwait(const char *name
, ENetAddress
*address
)
390 return enet_address_set_host(address
, name
) >= 0;
393 int connectwithtimeout(ENetSocket sock
, const char *hostname
, ENetAddress
&remoteaddress
)
395 int result
= enet_socket_connect(sock
, &remoteaddress
);
396 if(result
<0) enet_socket_destroy(sock
);
401 ENetSocket
httpgetsend(ENetAddress
&remoteaddress
, const char *hostname
, const char *req
, const char *ref
, const char *agent
, ENetAddress
*localaddress
= NULL
)
403 if(remoteaddress
.host
==ENET_HOST_ANY
)
406 printf("looking up %s...\n", hostname
);
408 if(!resolverwait(hostname
, &remoteaddress
)) return ENET_SOCKET_NULL
;
410 ENetSocket sock
= enet_socket_create(ENET_SOCKET_TYPE_STREAM
, localaddress
);
411 if(sock
==ENET_SOCKET_NULL
|| connectwithtimeout(sock
, hostname
, remoteaddress
)<0)
414 printf(sock
==ENET_SOCKET_NULL
? "could not open socket\n" : "could not connect\n");
416 return ENET_SOCKET_NULL
;
419 s_sprintfd(httpget
)("GET %s HTTP/1.0\nHost: %s\nReferer: %s\nUser-Agent: %s\n\n", req
, hostname
, ref
, agent
);
421 buf
.dataLength
= strlen((char *)buf
.data
);
423 printf("sending request to %s...\n", hostname
);
425 enet_socket_send(sock
, NULL
, &buf
, 1);
429 bool httpgetreceive(ENetSocket sock
, ENetBuffer
&buf
, int timeout
= 0)
431 if(sock
==ENET_SOCKET_NULL
) return false;
432 enet_uint32 events
= ENET_SOCKET_WAIT_RECEIVE
;
433 if(enet_socket_wait(sock
, &events
, timeout
) >= 0 && events
)
435 int len
= enet_socket_receive(sock
, NULL
, &buf
, 1);
438 enet_socket_destroy(sock
);
441 buf
.data
= ((char *)buf
.data
)+len
;
442 ((char*)buf
.data
)[0] = 0;
443 buf
.dataLength
-= len
;
448 uchar
*stripheader(uchar
*b
)
450 char *s
= strstr((char *)b
, "\n\r\n");
451 if(!s
) s
= strstr((char *)b
, "\n\n");
452 return s
? (uchar
*)s
: b
;
455 ENetSocket mssock
= ENET_SOCKET_NULL
;
456 ENetAddress msaddress
= { ENET_HOST_ANY
, ENET_PORT_ANY
};
457 ENetAddress masterserver
= { ENET_HOST_ANY
, 80 };
458 int lastupdatemaster
= 0;
461 uchar masterrep
[MAXTRANS
];
464 void updatemasterserver()
466 s_sprintfd(path
)("%sregister.do?action=add", masterpath
);
467 if(mssock
!=ENET_SOCKET_NULL
) enet_socket_destroy(mssock
);
468 mssock
= httpgetsend(masterserver
, masterbase
, path
, sv
->servername(), sv
->servername(), &msaddress
);
470 masterb
.data
= masterrep
;
471 masterb
.dataLength
= MAXTRANS
-1;
474 void checkmasterreply()
476 if(mssock
!=ENET_SOCKET_NULL
&& !httpgetreceive(mssock
, masterb
))
478 mssock
= ENET_SOCKET_NULL
;
479 printf("masterserver reply: %s\n", stripheader(masterrep
));
485 #define RETRIEVELIMIT 20000
487 uchar
*retrieveservers(uchar
*buf
, int buflen
)
491 s_sprintfd(path
)("%sretrieve.do?item=list", masterpath
);
492 ENetAddress address
= masterserver
;
493 ENetSocket sock
= httpgetsend(address
, masterbase
, path
, sv
->servername(), sv
->servername());
494 if(sock
==ENET_SOCKET_NULL
) return buf
;
495 /* only cache this if connection succeeds */
496 masterserver
= address
;
498 s_sprintfd(text
)("retrieving servers from %s... (esc to abort)", masterbase
);
499 show_out_of_renderloop_progress(0, text
);
503 eb
.dataLength
= buflen
-1;
505 int starttime
= SDL_GetTicks(), timeout
= 0;
506 while(httpgetreceive(sock
, eb
, 250))
508 timeout
= SDL_GetTicks() - starttime
;
509 show_out_of_renderloop_progress(min(float(timeout
)/RETRIEVELIMIT
, 1.0f
), text
);
510 if(interceptkey(SDLK_ESCAPE
)) timeout
= RETRIEVELIMIT
+ 1;
511 if(timeout
> RETRIEVELIMIT
)
514 enet_socket_destroy(sock
);
519 return stripheader(buf
);
523 #define DEFAULTCLIENTS 6
525 int uprate
= 0, maxclients
= DEFAULTCLIENTS
;
526 const char *ip
= "", *master
= NULL
;
527 const char *game
= "fps";
530 int lastmillis
= 0, totalmillis
= 0;
533 void serverslice(uint timeout
) // main server update, called from main loop in sp, or from below in dedicated server
535 localclients
= nonlocalclients
= 0;
536 loopv(clients
) switch(clients
[i
]->type
)
538 case ST_LOCAL
: localclients
++; break;
539 case ST_TCPIP
: nonlocalclients
++; break;
544 sv
->serverupdate(lastmillis
, totalmillis
);
548 // below is network only
550 lastmillis
= totalmillis
= (int)enet_time_get();
551 sv
->serverupdate(lastmillis
, totalmillis
);
555 if(*masterpath
) checkmasterreply();
557 if(totalmillis
-lastupdatemaster
>60*60*1000 && *masterpath
) // send alive signal to masterserver every hour of uptime
559 updatemasterserver();
560 lastupdatemaster
= totalmillis
;
563 if(totalmillis
-laststatus
>60*1000) // display bandwidth stats, useful for server ops
565 laststatus
= totalmillis
;
566 if(nonlocalclients
|| bsend
|| brec
) printf("status: %d remote clients, %.1f send, %.1f rec (K/sec)\n", nonlocalclients
, bsend
/60.0f
/1024, brec
/60.0f
/1024);
571 bool serviced
= false;
574 if(enet_host_check_events(serverhost
, &event
) <= 0)
576 if(enet_host_service(serverhost
, &event
, timeout
) <= 0) break;
581 case ENET_EVENT_TYPE_CONNECT
:
583 client
&c
= addclient();
588 s_strcpy(c
.hostname
, (enet_address_get_host_ip(&c
.peer
->address
, hn
, sizeof(hn
))==0) ? hn
: "unknown");
589 printf("client connected (%s)\n", c
.hostname
);
590 int reason
= DISC_MAXCLIENTS
;
591 if(nonlocalclients
<maxclients
&& !(reason
= sv
->clientconnect(c
.num
, c
.peer
->address
.host
)))
596 else disconnect_client(c
.num
, reason
);
599 case ENET_EVENT_TYPE_RECEIVE
:
601 brec
+= event
.packet
->dataLength
;
602 client
*c
= (client
*)event
.peer
->data
;
603 if(c
) process(event
.packet
, c
->num
, event
.channelID
);
604 if(event
.packet
->referenceCount
==0) enet_packet_destroy(event
.packet
);
607 case ENET_EVENT_TYPE_DISCONNECT
:
609 client
*c
= (client
*)event
.peer
->data
;
611 printf("disconnected client (%s)\n", c
->hostname
);
612 sv
->clientdisconnect(c
->num
);
615 event
.peer
->data
= NULL
;
616 sv
->deleteinfo(c
->info
);
624 if(sv
->sendpackets()) enet_host_flush(serverhost
);
627 void localdisconnect()
629 loopv(clients
) if(clients
[i
]->type
==ST_LOCAL
)
631 sv
->localdisconnect(i
);
633 clients
[i
]->type
= ST_EMPTY
;
634 sv
->deleteinfo(clients
[i
]->info
);
635 clients
[i
]->info
= NULL
;
641 client
&c
= addclient();
643 s_strcpy(c
.hostname
, "local");
645 sv
->localconnect(c
.num
);
649 void initserver(bool dedicated
)
653 if(!master
) master
= sv
->getdefaultmaster();
654 const char *mid
= strstr(master
, "/");
655 if(!mid
) mid
= master
;
656 s_strcpy(masterpath
, mid
);
657 s_strncpy(masterbase
, master
, mid
-master
+1);
661 ENetAddress address
= { ENET_HOST_ANY
, sv
->serverport() };
664 if(enet_address_set_host(&address
, ip
)<0) printf("WARNING: server ip not resolved");
665 else msaddress
.host
= address
.host
;
667 serverhost
= enet_host_create(&address
, maxclients
+1, 0, uprate
);
668 if(!serverhost
) fatal("could not create server host");
669 loopi(maxclients
) serverhost
->peers
[i
].data
= NULL
;
670 address
.port
= sv
->serverinfoport();
671 pongsock
= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
, &address
);
672 if(pongsock
== ENET_SOCKET_NULL
) fatal("could not create server info socket");
673 else enet_socket_set_option(pongsock
, ENET_SOCKOPT_NONBLOCK
, 1);
678 if(dedicated
) // do not return, this becomes main loop
681 SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS
);
683 printf("dedicated server started, waiting for clients...\nCtrl-C to exit\n\n");
684 atexit(enet_deinitialize
);
685 atexit(cleanupserver
);
687 if(*masterpath
) updatemasterserver();
688 for(;;) serverslice(5);
692 bool serveroption(char *opt
)
696 case 'u': uprate
= atoi(opt
+2); return true;
699 int clients
= atoi(opt
+2);
700 if(clients
> 0) maxclients
= min(clients
, MAXCLIENTS
);
701 else maxclients
= DEFAULTCLIENTS
;
704 case 'i': ip
= opt
+2; return true;
705 case 'm': master
= opt
+2; return true;
706 case 'g': game
= opt
+2; return true;
707 default: return false;
712 int main(int argc
, char* argv
[])
714 for(int i
= 1; i
<argc
; i
++) if(argv
[i
][0]!='-' || !serveroption(argv
[i
])) gameargs
.add(argv
[i
]);
715 if(enet_initialize()<0) fatal("Unable to initialise network module");