ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/socket/loop.C
(Generate patch)

Comparing deliantra/server/socket/loop.C (file contents):
Revision 1.5 by root, Sat Sep 9 21:48:29 2006 UTC vs.
Revision 1.36 by pippijn, Sat Jan 6 14:42:31 2007 UTC

1
2/*
3 * static char *rcsid_loop_c =
4 * "$Id: loop.C,v 1.5 2006/09/09 21:48:29 root Exp $";
5 */
6
7/* 1/*
8 CrossFire, A Multiplayer game for X-windows 2 CrossFire, A Multiplayer game for X-windows
9 3
4 Copyright (C) 2005, 2006, 2007 Marc Lehmann & Crossfire+ Development Team
10 Copyright (C) 2002-2003 Mark Wedel & The Crossfire Development Team 5 Copyright (C) 2002-2003 Mark Wedel & The Crossfire Development Team
11 Copyright (C) 1992 Frank Tore Johansen 6 Copyright (C) 1992 Frank Tore Johansen
12 7
13 This program is free software; you can redistribute it and/or modify 8 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by 9 it under the terms of the GNU General Public License as published by
22 17
23 You should have received a copy of the GNU General Public License 18 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software 19 along with this program; if not, write to the Free Software
25 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 21
27 The author can be reached via e-mail to crossfire-devel@real-time.com 22 The author can be reached via e-mail to <crossfire@schmorp.de>
28*/ 23*/
29 24
30/** 25/**
31 * \file 26 * \file
32 * Main client/server loops. 27 * Main client/server loops.
33 * 28 *
34 * \date 2003-12-02 29 * \date 2003-12-02
35 * 30 *
36 * loop.c mainly deals with initialization and higher level socket 31 * loop.c mainly deals with initialization and higher level socket
37 * maintenance (checking for lost connections and if data has arrived.) 32 * maintanance (checking for lost connections and if data has arrived.)
38 * The reading of data is handled in ericserver.c
39 */ 33 */
40 34
41 35
42#include <global.h> 36#include <global.h>
43#ifndef __CEXTRACT__
44#include <sproto.h> 37#include <sproto.h>
45#include <sockproto.h> 38#include <sockproto.h>
46#endif
47 39
48#ifndef WIN32 /* ---win32 exclude unix headers */
49#include <sys/types.h> 40#include <sys/types.h>
50#include <sys/time.h> 41#include <sys/time.h>
51#include <sys/socket.h> 42#include <sys/socket.h>
52#include <netinet/in.h> 43#include <netinet/in.h>
53#include <netdb.h> 44#include <netdb.h>
54#endif /* end win32 */
55 45
56#ifdef HAVE_UNISTD_H
57#include <unistd.h> 46#include <unistd.h>
58#endif
59
60#ifdef HAVE_ARPA_INET_H
61#include <arpa/inet.h> 47#include <arpa/inet.h>
62#endif
63 48
64#include <loader.h> 49#include <loader.h>
65#include <newserver.h>
66 50
67/***************************************************************************** 51#define MAX_QUEUE_DEPTH 500 //TODO
68 * Start of command dispatch area. 52#define MAX_QUEUE_BACKLOG 3. //TODO
69 * The commands here are protocol commands.
70 ****************************************************************************/
71 53
72/* Either keep this near the start or end of the file so it is 54void
73 * at least reasonablye easy to find. 55client::reset_state ()
74 * There are really 2 commands - those which are sent/received 56{
75 * before player joins, and those happen after the player has joined. 57 if (!pl)
76 * As such, we have function types that might be called, so 58 return;
77 * we end up having 2 tables.
78 */
79 59
80typedef void (*func_uint8_int_ns) (char*, int, NewSocket *); 60 pl->run_on = 0;
61 pl->fire_on = 0;
62}
81 63
82struct NsCmdMapping { 64void
83 const char *cmdname; 65client::queue_command (packet_type *handler, char *data, int datalen)
84 func_uint8_int_ns cmdproc; 66{
85}; 67 tstamp stamp = now ();
86 68
87typedef void (*func_uint8_int_pl)(char*, int, player *); 69 if (cmd_queue.size () >= MAX_QUEUE_DEPTH)
88struct PlCmdMapping { 70 {
89 const char *cmdname; 71 //TODO: just disconnect here?
90 func_uint8_int_pl cmdproc; 72 reset_state ();
91 uint8 flag; 73 send_packet_printf ("drawinfo %d command queue overflow, ignoring.", NDI_RED);
92}; 74 }
75 else
76 {
77 cmd_queue.push_back (command ());
78 command &cmd = cmd_queue.back ();
79 cmd.stamp = stamp;
80 cmd.handler = handler;
81 cmd.data = salloc<char> (datalen + 1, data);
82 cmd.datalen = datalen;
83 }
84}
93 85
94/** 86bool
95 * Dispatch table for the server. 87client::handle_command ()
96 * 88{
97 * CmdMapping is the dispatch table for the server, used in HandleClient, 89 bool skipping = false;
98 * which gets called when the client has input. All commands called here
99 * use the same parameter form (char* data, int len, int clientnum.
100 * We do implicit casts, because the data that is being passed is
101 * unsigned (pretty much needs to be for binary data), however, most
102 * of these treat it only as strings, so it makes things easier
103 * to cast it here instead of a bunch of times in the function itself.
104 * flag is 1 if the player must be in the playing state to issue the
105 * command, 0 if they can issue it at any time.
106 */
107static struct PlCmdMapping plcommands[] = {
108 { "examine", ExamineCmd, 1},
109 { "apply", ApplyCmd, 1},
110 { "move", MoveCmd, 1},
111 { "reply", ReplyCmd, 0},
112 { "command", PlayerCmd, 1},
113 { "ncom", (func_uint8_int_pl)NewPlayerCmd, 1},
114 { "lookat", LookAt, 1},
115 { "lock", (func_uint8_int_pl)LockItem, 1},
116 { "mark", (func_uint8_int_pl)MarkItem, 1},
117 { "mapredraw", MapRedrawCmd, 0}, /* Added: phil */
118 { "mapinfo", MapInfoCmd, 2}, /* CF+ */
119 { "ext", ExtCmd, 2}, /* CF+ */
120 { NULL, NULL, 0} /* terminator */
121};
122 90
123/** Face-related commands */ 91 while (!cmd_queue.empty ()
124static struct NsCmdMapping nscommands[] = { 92 && !(state == ST_PLAYING && pl->ob && pl->ob->speed_left < 0))
125 { "addme", AddMeCmd }, 93 {
126 { "askface", SendFaceCmd}, /* Added: phil */ 94 command &cmd = cmd_queue.front ();
127 { "requestinfo", RequestInfo},
128 { "setfacemode", SetFaceMode},
129 { "setsound", SetSound},
130 { "setup", SetUp},
131 { "version", VersionCmd },
132 { "toggleextendedinfos", ToggleExtendedInfos}, /*Added: tchize*/
133 { "toggleextendedtext", ToggleExtendedText}, /*Added: tchize*/
134 { "asksmooth", AskSmooth}, /*Added: tchize (smoothing technologies)*/
135 { NULL, NULL} /* terminator (I, II & III)*/
136};
137 95
138/** 96 if (cmd.stamp + MAX_QUEUE_BACKLOG < now ())
139 * RequestInfo is sort of a meta command. There is some specific 97 {
140 * request of information, but we call other functions to provide 98 if (!skipping)
141 * that information. 99 {
142 */ 100 skipping = true;
143void RequestInfo(char *buf, int len, NewSocket *ns) 101 reset_state ();
102 send_packet_printf ("drawinfo %d ignoring delayed commands.", NDI_RED);
103 }
104
105 cmd_queue.pop_front ();
106 }
107 else
108 {
109 execute (cmd.handler, cmd.data, cmd.datalen);
110 cmd_queue.pop_front ();
111 return true;
112 }
113 }
114
115 return false;
116}
117
118void
119flush_sockets (void)
144{ 120{
145 char *params=NULL, *cp; 121 for (sockvec::iterator i = clients.begin (); i != clients.end (); ++i)
146 /* No match */ 122 (*i)->flush ();
147 char bigbuf[MAX_BUF];
148 int slen;
149
150 /* Set up replyinfo before we modify any of the buffers - this is used
151 * if we don't find a match.
152 */
153 strcpy(bigbuf,"replyinfo ");
154 slen = strlen(bigbuf);
155 safe_strcat(bigbuf, buf, &slen, MAX_BUF);
156
157 /* find the first space, make it null, and update the
158 * params pointer.
159 */
160 for (cp = buf; *cp != '\0'; cp++)
161 if (*cp==' ') {
162 *cp = '\0';
163 params = cp + 1;
164 break;
165 }
166 if (!strcmp(buf, "image_info")) send_image_info(ns, params);
167 else if (!strcmp(buf,"image_sums")) send_image_sums(ns, params);
168 else if (!strcmp(buf,"skill_info")) send_skill_info(ns, params);
169 else if (!strcmp(buf,"spell_paths")) send_spell_paths(ns, params);
170 else Write_String_To_Socket(ns, bigbuf, len);
171} 123}
172 124
173/** 125/**
174 * Handle client input.
175 *
176 * HandleClient is actually not named really well - we only get here once
177 * there is input, so we don't do exception or other stuff here.
178 * sock is the output socket information. pl is the player associated
179 * with this socket, null if no player (one of the init_sockets for just
180 * starting a connection)
181 */
182
183void HandleClient(NewSocket *ns, player *pl)
184{
185 int len=0,i,cnt;
186 char *data;
187
188 /* Loop through this - maybe we have several complete packets here. */
189 // limit to a few commands only, though, as to not monopolise the server
190 for (cnt = 16; cnt--; ) {
191 /* If it is a player, and they don't have any speed left, we
192 * return, and will read in the data when they do have time.
193 */
194 if (pl && pl->state==ST_PLAYING && pl->ob != NULL && pl->ob->speed_left < 0) {
195 return;
196 }
197
198 i=SockList_ReadPacket(ns->fd, &ns->inbuf, MAXSOCKBUF-1);
199
200 if (i<0) {
201#ifdef ESRV_DEBUG
202 LOG(llevDebug,"HandleClient: Read error on connection player %s\n", (pl?pl->ob->name:"None"));
203#endif
204 /* Caller will take care of cleaning this up */
205 ns->status =Ns_Dead;
206 return;
207 }
208 /* Still dont have a full packet */
209 if (i==0) return;
210
211// //D//TODO//temporarily log long commands
212// if (ns->inbuf.len >= 40 && pl && pl->ob)
213// LOG (llevDebug, "HandleClient: long comamnd from <%s,%s> %d<%s>\n", pl->ob->name, ns->host, ns->inbuf.len, ns->inbuf.buf + 2);
214
215 /* First, break out beginning word. There are at least
216 * a few commands that do not have any paremeters. If
217 * we get such a command, don't worry about trying
218 * to break it up.
219 */
220 data = (char *)strchr((char*)ns->inbuf.buf +2, ' ');
221 if (data) {
222 *data='\0';
223 data++;
224 len = ns->inbuf.len - (data - (char*)ns->inbuf.buf);
225 }
226 else len=0;
227
228 ns->inbuf.buf[ns->inbuf.len]='\0'; /* Terminate buffer - useful for string data */
229 for (i=0; nscommands[i].cmdname !=NULL; i++) {
230 if (strcmp((char*)ns->inbuf.buf+2,nscommands[i].cmdname)==0) {
231 nscommands[i].cmdproc((char*)data,len,ns);
232 ns->inbuf.len=0;
233 return;//D// not doing this causes random memory corruption
234 goto next_packet;
235 }
236 }
237 /* Player must be in the playing state or the flag on the
238 * the command must be zero for the user to use the command -
239 * otherwise, a player cam save, be in the play_again state, and
240 * the map they were on gets swapped out, yet things that try to look
241 * at the map causes a crash. If the command is valid, but
242 * one they can't use, we still swallow it up.
243 */
244 if (pl) for (i=0; plcommands[i].cmdname !=NULL; i++) {
245 if (strcmp((char*)ns->inbuf.buf+2,plcommands[i].cmdname)==0) {
246 if (pl->state == ST_PLAYING || !(plcommands[i].flag & 1))
247 plcommands[i].cmdproc((char*)data,len,pl);
248 ns->inbuf.len=0;
249 //D// not doing this causes random memory corruption
250 if (plcommands[i].flag & 2)
251 goto next_packet;
252 return;
253 }
254 }
255 /* If we get here, we didn't find a valid command. Logging
256 * this might be questionable, because a broken client/malicious
257 * user could certainly send a whole bunch of invalid commands.
258 */
259 LOG(llevDebug,"Bad command from client (%s)\n",ns->inbuf.buf+2);
260 next_packet:
261 ;
262 }
263}
264
265
266/*****************************************************************************
267 *
268 * Low level socket looping - select calls and watchdog udp packet
269 * sending.
270 *
271 ******************************************************************************/
272
273#ifdef WATCHDOG
274/**
275 * Tell watchdog that we are still alive
276 *
277 * I put the function here since we should hopefully already be getting
278 * all the needed include files for socket support
279 */
280
281void watchdog(void)
282{
283 static int fd=-1;
284 static struct sockaddr_in insock;
285
286 if (fd==-1)
287 {
288 struct protoent *protoent;
289
290 if ((protoent=getprotobyname("udp"))==NULL ||
291 (fd=socket(PF_INET, SOCK_DGRAM, protoent->p_proto))==-1)
292 {
293 return;
294 }
295 insock.sin_family=AF_INET;
296 insock.sin_port=htons((unsigned short)13325);
297 insock.sin_addr.s_addr=inet_addr("127.0.0.1");
298 }
299 sendto(fd,(void *)&fd,1,0,(struct sockaddr *)&insock,sizeof(insock));
300}
301#endif
302
303void flush_sockets(void)
304{
305 player *pl;
306
307 for (pl = first_player; pl != NULL; pl = pl->next)
308 if (pl->socket.status != Ns_Dead)
309 Socket_Flush (&pl->socket);
310}
311
312/**
313 * This checks the sockets for input and exceptions, does the right thing. 126 * This checks the sockets for input, does the right thing.
314 * 127 *
315 * A bit of this code is grabbed out of socket.c 128 * A bit of this code is grabbed out of socket.c
316 * There are 2 lists we need to look through - init_sockets is a list 129 * There are 2 lists we need to look through - init_sockets is a list
317 * 130 *
318 */ 131 */
132void
319void doeric_server(void) 133doeric_server (void)
320{ 134{
321 int i, pollret;
322 fd_set tmp_read, tmp_exceptions, tmp_write;
323 struct sockaddr_in addr;
324 socklen_t addrlen=sizeof(struct sockaddr);
325 player *pl, *next;
326
327#ifdef CS_LOGSTATS 135#ifdef CS_LOGSTATS
328 if ((time(NULL)-cst_lst.time_start)>=CS_LOGTIME) 136 if ((time (NULL) - cst_lst.time_start) >= CS_LOGTIME)
329 write_cs_stats(); 137 write_cs_stats ();
330#endif 138#endif
331 139
332 FD_ZERO(&tmp_read); 140 //TODO: should not be done here, either
333 FD_ZERO(&tmp_write); 141 for (int i = 0; i < clients.size (); ++i)
334 FD_ZERO(&tmp_exceptions); 142 {
143 client *s = clients [i];
144 player *pl = s->pl;
335 145
336 for(i=0;i<socket_info.allocated_sockets;i++) { 146 if (pl && pl->ns && !pl->ns->destroyed ())
337 if (init_sockets[i].status == Ns_Dead) { 147 {
338 free_newsocket(&init_sockets[i]); 148 /* Update the players stats once per tick. More efficient than
339 init_sockets[i].status = Ns_Avail; 149 * sending them whenever they change, and probably just as useful
340 socket_info.nconns--; 150 */
341 } else if (init_sockets[i].status != Ns_Avail){ 151 esrv_update_stats (pl);
342 FD_SET((uint32)init_sockets[i].fd, &tmp_read); 152
343 FD_SET((uint32)init_sockets[i].fd, &tmp_write); 153 if (pl->last_weight != -1 && pl->last_weight != WEIGHT (pl->ob))
344 FD_SET((uint32)init_sockets[i].fd, &tmp_exceptions); 154 {
155 esrv_update_item (UPD_WEIGHT, pl->ob, pl->ob);
156 if (pl->last_weight != WEIGHT (pl->ob))
157 LOG (llevError, "esrv_update_item(UPD_WEIGHT) did not set player weight: is %lu, should be %lu\n",
158 (unsigned long) pl->last_weight, WEIGHT (pl->ob));
159 }
160
161 draw_client_map (pl->ob);
162
163 if (s->update_look)
164 esrv_draw_look (pl->ob);
345 } 165 }
346 }
347 166
348 /* Go through the players. Let the loop set the next pl value, 167 s->refcnt_chk ();
349 * since we may remove some
350 */
351 for (pl=first_player; pl!=NULL; ) {
352 if (pl->socket.status == Ns_Dead) {
353 player *npl=pl->next;
354
355 save_player(pl->ob, 0);
356 if(!QUERY_FLAG(pl->ob,FLAG_REMOVED)) {
357 terminate_all_pets(pl->ob);
358 remove_ob(pl->ob);
359 }
360 leave(pl,1);
361 final_free_player(pl);
362 pl=npl;
363 }
364 else {
365 FD_SET((uint32)pl->socket.fd, &tmp_read);
366 FD_SET((uint32)pl->socket.fd, &tmp_write);
367 FD_SET((uint32)pl->socket.fd, &tmp_exceptions);
368 pl=pl->next;
369 }
370 }
371
372 /* Reset timeout each time, since some OS's will change the values on
373 * the return from select.
374 */
375 socket_info.timeout.tv_sec = 0;
376 socket_info.timeout.tv_usec = 0;
377
378 pollret= select(socket_info.max_filedescriptor, &tmp_read, &tmp_write,
379 &tmp_exceptions, &socket_info.timeout);
380
381 if (pollret==-1) {
382 LOG(llevError, "select failed: %s\n", strerror(errno));
383 return;
384 }
385
386 /* We need to do some of the processing below regardless */
387/* if (!pollret) return;*/
388
389 /* Following adds a new connection */
390 if (pollret && FD_ISSET(init_sockets[0].fd, &tmp_read)) {
391 int newsocknum=0;
392
393#ifdef ESRV_DEBUG
394 LOG(llevDebug,"doeric_server: New Connection\n");
395#endif
396 /* If this is the case, all sockets currently in used */
397 if (socket_info.allocated_sockets <= socket_info.nconns) {
398 init_sockets = (NewSocket *) realloc(init_sockets,sizeof(NewSocket)*(socket_info.nconns+1));
399 if (!init_sockets) fatal(OUT_OF_MEMORY);
400 newsocknum = socket_info.allocated_sockets;
401 socket_info.allocated_sockets++;
402 init_sockets[newsocknum].faces_sent_len = nrofpixmaps;
403 init_sockets[newsocknum].faces_sent = (uint8*) malloc(nrofpixmaps*sizeof(*init_sockets[newsocknum].faces_sent));
404 if (!init_sockets[newsocknum].faces_sent) fatal(OUT_OF_MEMORY);
405 init_sockets[newsocknum].status = Ns_Avail;
406 }
407 else {
408 int j;
409
410 for (j=1; j<socket_info.allocated_sockets; j++)
411 if (init_sockets[j].status == Ns_Avail) {
412 newsocknum=j;
413 break;
414 }
415 }
416 init_sockets[newsocknum].fd=accept(init_sockets[0].fd, (struct sockaddr *)&addr, &addrlen);
417 if (init_sockets[newsocknum].fd==-1) {
418 LOG(llevError, "accept failed: %s\n", strerror(errno));
419 }
420 else {
421 char buf[MAX_BUF];
422 long ip;
423 NewSocket *ns;
424
425 ns = &init_sockets[newsocknum];
426
427 ip = ntohl(addr.sin_addr.s_addr);
428 sprintf(buf, "%ld.%ld.%ld.%ld", (ip>>24)&255, (ip>>16)&255, (ip>>8)&255, ip&255);
429
430 if (checkbanned(NULL, buf)) {
431 LOG(llevInfo, "Banned host tried to connect: [%s]\n", buf);
432 close(init_sockets[newsocknum].fd);
433 init_sockets[newsocknum].fd = -1;
434 }
435 else {
436 InitConnection(ns, buf);
437 socket_info.nconns++;
438 }
439 }
440 }
441
442 /* Check for any exceptions/input on the sockets */
443 if (pollret) for(i=1;i<socket_info.allocated_sockets;i++) {
444 if (init_sockets[i].status == Ns_Avail) continue;
445 if (FD_ISSET(init_sockets[i].fd,&tmp_exceptions)) {
446 free_newsocket(&init_sockets[i]);
447 init_sockets[i].status = Ns_Avail;
448 socket_info.nconns--;
449 continue;
450 }
451 if (FD_ISSET(init_sockets[i].fd, &tmp_read)) {
452 HandleClient(&init_sockets[i], NULL);
453 }
454 if (FD_ISSET(init_sockets[i].fd, &tmp_write)) {
455 init_sockets[i].can_write=1;
456 }
457 }
458
459 /* This does roughly the same thing, but for the players now */
460 for (pl=first_player; pl!=NULL; pl=next) {
461
462 next=pl->next;
463 if (pl->socket.status==Ns_Dead) continue;
464
465 if (FD_ISSET(pl->socket.fd,&tmp_write)) {
466 if (!pl->socket.can_write) {
467#if 0
468 LOG(llevDebug,"Player %s socket now write enabled\n", pl->ob->name);
469#endif
470 pl->socket.can_write=1;
471 write_socket_buffer(&pl->socket);
472 }
473 /* if we get an error on the write_socket buffer, no reason to
474 * continue on this socket.
475 */
476 if (pl->socket.status==Ns_Dead) continue;
477 }
478 else pl->socket.can_write=0;
479
480 if (FD_ISSET(pl->socket.fd,&tmp_exceptions)) {
481 save_player(pl->ob, 0);
482 if(!QUERY_FLAG(pl->ob,FLAG_REMOVED)) {
483 terminate_all_pets(pl->ob);
484 remove_ob(pl->ob);
485 }
486 leave(pl,1);
487 final_free_player(pl);
488 }
489 else {
490 HandleClient(&pl->socket, pl);
491 /* If the player has left the game, then the socket status
492 * will be set to this be the leave function. We don't
493 * need to call leave again, as it has already been called
494 * once.
495 */
496 if (pl->socket.status==Ns_Dead) {
497 save_player(pl->ob, 0);
498 if(!QUERY_FLAG(pl->ob,FLAG_REMOVED)) {
499 terminate_all_pets(pl->ob);
500 remove_ob(pl->ob);
501 }
502 leave(pl,1);
503 final_free_player(pl);
504 } else {
505
506 /* Update the players stats once per tick. More efficient than
507 * sending them whenever they change, and probably just as useful
508 */
509 esrv_update_stats(pl);
510 if (pl->last_weight != -1 && pl->last_weight != WEIGHT(pl->ob)) {
511 esrv_update_item(UPD_WEIGHT, pl->ob, pl->ob);
512 if(pl->last_weight != WEIGHT(pl->ob))
513 LOG(llevError, "esrv_update_item(UPD_WEIGHT) did not set player weight: is %lu, should be %lu\n", (unsigned long)pl->last_weight, WEIGHT(pl->ob));
514 }
515 /* draw_client_map does sanity checking that map is
516 * valid, so don't do it here.
517 */
518 draw_client_map(pl->ob);
519 if (pl->socket.update_look) esrv_draw_look(pl->ob);
520 }
521 }
522 } 168 }
523} 169}
170

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines