ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/socket/lowlevel.C
Revision: 1.29
Committed: Thu Dec 21 23:37:06 2006 UTC (17 years, 5 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.28: +7 -4 lines
Log Message:
- made state a per-client variable
  (that does not magically make state a per-client thing!)
- rename player->socket to player->ns. its not a good name for "client",
  but it is an historical artifact, and better than "socket".

File Contents

# Content
1 /*
2 CrossFire, A Multiplayer game for X-windows
3
4 Copyright (C) 1992 Frank Tore Johansen
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 The author can be reached via e-mail to mark@pyramid.com
21 */
22
23 /**
24 * \file
25 * Low-level socket-related functions.
26 *
27 * \date 2003-12-02
28 *
29 * Contains some base functions that both the client and server
30 * can use. As such, depending what we are being compiled for will
31 * determine what we can include. the client is designed have
32 * CFCLIENT defined as part of its compile flags.
33 */
34
35 using namespace std;
36
37 #include <global.h>
38 #include <sproto.h>
39 #include <cstdarg>
40
41 #ifdef __linux__
42 # include <sys/types.h>
43 # include <sys/socket.h>
44 # include <netinet/in.h>
45 # include <netinet/tcp.h>
46 #endif
47
48 // use a really low timeout, as it doesn't cost any bandwidth, and you can
49 // easily die in 20 seconds...
50 #define SOCKET_TIMEOUT1 10 * 1000
51 #define SOCKET_TIMEOUT2 20 * 1000
52
53 void
54 client::flush ()
55 {
56 if (destroyed ())
57 return;
58
59 #ifdef __linux__
60 // check time of last ack, and, if too old, kill connection
61 struct tcp_info tcpi;
62 socklen_t len = sizeof (tcpi);
63
64 if (!getsockopt (fd, IPPROTO_TCP, TCP_INFO, &tcpi, &len) && len == sizeof (tcpi))
65 {
66 unsigned int diff = tcpi.tcpi_last_ack_recv - tcpi.tcpi_last_data_sent;
67
68 rtt = tcpi.tcpi_rtt;
69 rttvar = tcpi.tcpi_rttvar;
70
71 if (tcpi.tcpi_unacked && SOCKET_TIMEOUT1 < diff && diff < 0x80000000UL // ack delayed for 20s
72 && SOCKET_TIMEOUT2 < tcpi.tcpi_last_data_sent) // no data sent for 10s
73 {
74 LOG (llevDebug, "Connection on fd %d closed due to ack timeout (%u/%u/%u)\n", fd,
75 (unsigned) tcpi.tcpi_last_ack_recv, (unsigned) tcpi.tcpi_last_data_sent, (unsigned) tcpi.tcpi_unacked);
76 destroy ();
77 }
78 }
79 #endif
80
81 /**
82 * Writes data to socket.
83 *
84 * When the socket is clear to write, and we have backlogged data, this
85 * is called to write it out.
86 */
87
88 if (!outputbuffer.len || socket_ev.poll () & PE_W)
89 return;
90
91 write_outputbuffer ();
92 }
93
94 void
95 client::write_outputbuffer ()
96 {
97 while (outputbuffer.len)
98 {
99 int res = write (fd, outputbuffer.data + outputbuffer.start,
100 min (outputbuffer.len, SOCKETBUFSIZE - outputbuffer.start));
101
102 if (res > 0)
103 {
104 outputbuffer.start += res;
105 /* wrap back to start of buffer */
106 if (outputbuffer.start == SOCKETBUFSIZE)
107 outputbuffer.start = 0;
108
109 outputbuffer.len -= res;
110 #ifdef CS_LOGSTATS
111 cst_tot.obytes += res;
112 cst_lst.obytes += res;
113 #endif
114 }
115 else if (res == 0)
116 {
117 LOG (llevError, "socket write failed, connection closed.\n");
118 destroy ();
119 return;
120 }
121 else if (errno == EINTR)
122 {
123 // just retry
124 }
125 else if (errno == EAGAIN)
126 {
127 // delay till ready
128 socket_ev.poll (socket_ev.poll () | PE_W);
129 socket_ev.start ();
130 return;
131 }
132 else
133 {
134 LOG (llevError, "socket write failed: %s\n", strerror (errno));
135 destroy ();
136 return;
137 }
138 }
139
140 socket_ev.poll (socket_ev.poll () & ~PE_W);
141 }
142
143 /******************************************************************************
144 *
145 * Start of read routines.
146 *
147 ******************************************************************************/
148
149 int
150 client::next_packet ()
151 {
152 if (inbuf_len >= 2)
153 {
154 int pkt_len = (inbuf [0] << 8) | inbuf [1];
155
156 if (inbuf_len >= 2 + pkt_len)
157 return 2 + pkt_len;
158
159 if (inbuf_len == sizeof (inbuf))
160 {
161 send_packet_printf ("drawinfo %d input buffer overflow - closing connection.", NDI_RED);
162 destroy ();
163 return -1;
164 }
165 }
166
167 return 0;
168 }
169
170 void
171 client::skip_packet (int len)
172 {
173 inbuf_len -= len;
174 memmove (inbuf, inbuf + len, inbuf_len);
175 }
176
177 /*****************************************************************************
178 * Start of command dispatch area.
179 * The commands here are protocol commands.
180 ****************************************************************************/
181
182 // SocketCommand, PlayingCommand, should not exist with those ugly casts
183 #define SC(cb) (void *)static_cast<void (*)(char *, int, client *)>(cb),
184 #define PC(cb) (void *)static_cast<void (*)(char *, int, player *)>(cb), PF_PLAYER |
185
186 /**
187 * Dispatch table for the server.
188 */
189 static struct packet_type packets[] = {
190 {"ncom", PC(NewPlayerCmd) PF_PLAYING },
191 {"command", PC(PlayerCmd) PF_PLAYING },
192
193 {"examine", PC(ExamineCmd) PF_PLAYING },
194 {"apply", PC(ApplyCmd) PF_PLAYING },
195 {"lookat", PC(LookAt) PF_PLAYING },
196 {"lock", PC(LockItem) PF_PLAYING },
197 {"mark", PC(MarkItem) PF_PLAYING },
198 {"move", PC(MoveCmd) PF_PLAYING },
199 {"ext", PC(ExtCmd) 0 }, /* CF+ */
200 {"mapredraw", PC(MapRedrawCmd) 0 }, /* Added: phil */
201 {"mapinfo", PC(MapInfoCmd) 0 }, /* CF+ */
202
203 {"reply", SC(ReplyCmd) 0 },
204 {"exti", SC(ExtiCmd) 0 }, /* CF+ */
205 {"addme", SC(AddMeCmd) 0 },
206 {"askface", SC(SendFaceCmd) 0 }, /* Added: phil */
207 {"requestinfo", SC(RequestInfo) 0 },
208 {"setfacemode", SC(SetFaceMode) 0 },
209 {"setsound", SC(SetSound) 0 },
210 {"setup", SC(SetUp) 0 },
211 {"version", SC(VersionCmd) 0 },
212 {"toggleextendedinfos", SC(ToggleExtendedInfos) 0 }, /*Added: tchize */
213 {"toggleextendedtext", SC(ToggleExtendedText) 0 }, /*Added: tchize */
214 {"asksmooth", SC(AskSmooth) 0 }, /*Added: tchize (smoothing technologies) */
215 };
216
217 bool
218 client::may_execute (const packet_type *pkt) const
219 {
220 return (!(pkt->flags & PF_PLAYER) || pl)
221 && (!(pkt->flags & PF_PLAYING) || state == ST_PLAYING);
222 }
223
224 void
225 client::execute (const packet_type *pkt, char *data, int datalen)
226 {
227 if (may_execute (pkt))
228 {
229 //TODO: only one format
230 if (pkt->flags & PF_PLAYER)
231 ((void (*)(char *, int, player *))pkt->cb)((char *)data, datalen, pl);
232 else
233 ((void (*)(char *, int, client *))pkt->cb)((char *)data, datalen, this);
234 }
235 else
236 send_packet_printf ("drawinfo %d ERROR: you cannot execute '%s' now.", NDI_RED, pkt->name);
237 }
238
239 bool
240 client::handle_packet ()
241 {
242 int pkt_len = next_packet ();
243
244 if (!pkt_len)
245 return false;
246 else if (pkt_len < 0)
247 {
248 LOG (llevError, "read error on player %s\n",
249 pl && pl->ob ? &pl->ob->name : "[anonymous]");
250 destroy ();
251 return false;
252 }
253
254 inbuf [pkt_len] = 0; /* Terminate buffer - useful for string data */
255
256 /* First, break out beginning word. There are at least
257 * a few commands that do not have any paremeters. If
258 * we get such a command, don't worry about trying
259 * to break it up.
260 */
261 int datalen;
262 char *data = strchr ((char *)inbuf + 2, ' ');
263
264 if (data)
265 {
266 *data++ = 0;
267 datalen = pkt_len - (data - (char *)inbuf);
268 }
269 else
270 {
271 data = (char *)inbuf + 2; // better read garbage than segfault
272 datalen = 0;
273 }
274
275 for (packet_type *pkt = packets; pkt < packets + (sizeof (packets) / sizeof (packets[0])); ++pkt)
276 if (!strcmp ((char *)inbuf + 2, pkt->name))
277 {
278 if (pkt->flags & PF_PLAYER)
279 queue_command (pkt, data, datalen);
280 else
281 execute (pkt, data, datalen);
282
283 goto next_packet;
284 }
285
286 // If we get here, we didn't find a valid command.
287 send_packet_printf ("drawinfo %d ERROR: command '%s' not supported.", NDI_RED, (char *)inbuf + 2);
288 next_packet:
289 skip_packet (pkt_len);
290
291 // input buffer has space again
292 socket_ev.poll (socket_ev.poll () | PE_R);
293
294 return true;
295 }
296
297 // callback called when socket is either readable or writable
298 void
299 client::socket_cb (iow &w, int got)
300 {
301 //TODO remove when we have better socket cleanup logic
302 if (destroyed ())
303 {
304 socket_ev.poll (0);
305 return;
306 }
307
308 if (got & PE_W)
309 {
310 write_outputbuffer ();
311
312 if (!outputbuffer.len)
313 socket_ev.poll (socket_ev.poll () & ~PE_W);
314 }
315
316 if (got & PE_R)
317 {
318 //TODO: rate-limit tcp connection in better ways, important
319
320 int amount = sizeof (inbuf) - inbuf_len;
321
322 if (!amount)
323 {
324 // input buffer full
325 socket_ev.poll (socket_ev.poll () & ~PE_R);
326 return;
327 }
328
329 amount = read (fd, inbuf + inbuf_len, amount);
330
331 if (!amount)
332 {
333 destroy ();
334 return;
335 }
336 else if (amount < 0)
337 {
338 if (errno != EAGAIN && errno != EINTR)
339 {
340 LOG (llevError, "read error: %s\n", strerror (errno));
341 destroy ();
342 return;
343 }
344
345 // should not be here, normally
346 }
347 else
348 {
349 inbuf_len += amount;
350
351 cst_tot.ibytes += amount;
352 cst_lst.ibytes += amount;
353
354 cmd_ev.start ();
355 }
356 }
357 }
358
359 // called whenever we have additional commands to process
360 void
361 client::cmd_cb (iw &w)
362 {
363 if (handle_packet ())
364 w.start ();
365 else
366 flush ();
367 }
368
369 /*******************************************************************************
370 *
371 * Start of write related routines.
372 *
373 ******************************************************************************/
374
375 /**
376 * Adds data to a socket buffer for whatever reason.
377 *
378 * ns is the socket we are adding the data to, buf is the start of the
379 * data, and len is the number of bytes to add.
380 */
381 void
382 client::send (void *buf_, int len)
383 {
384 char *buf = (char *)buf_;
385 char *pos = buf;
386 int amt = 0;
387
388 if (destroyed () || !buf)
389 return;
390
391 if ((len + outputbuffer.len) > SOCKETBUFSIZE)
392 {
393 LOG (llevDebug, "socket on fd %d has overrun internal buffer - marking as dead\n", fd);
394 destroy ();
395 return;
396 }
397
398 int avail, end;
399
400 /* data + end is where we start putting the new data. The last byte
401 * currently in use is actually data + end -1
402 */
403 end = outputbuffer.start + outputbuffer.len;
404 /* The buffer is already in a wrapped state, so adjust end */
405 if (end >= SOCKETBUFSIZE)
406 end -= SOCKETBUFSIZE;
407
408 avail = SOCKETBUFSIZE - end;
409
410 /* We can all fit it behind the current data without wrapping */
411 if (avail >= len)
412 memcpy (outputbuffer.data + end, buf, len);
413 else
414 {
415 memcpy (outputbuffer.data + end, buf, avail);
416 memcpy (outputbuffer.data, buf + avail, len - avail);
417 }
418
419 outputbuffer.len += len;
420 }
421
422 /**
423 * Takes a string of data, and writes it out to the socket. A very handy
424 * shortcut function.
425 */
426 void
427 client::send_packet (packet &sl)
428 {
429 if (destroyed ())
430 return;
431
432 if (sl.length () >= MAXSOCKBUF)
433 {
434 LOG (llevError, "Trying to send a buffer beyond properly size, len =%d\n", sl.length ());
435 /* Almost certainly we've overflowed a buffer, so quit now to make
436 * it easier to debug.
437 */
438 abort ();
439 }
440
441 if (!sl.length ())
442 return;
443
444 assert (sl.hdrlen == 2);
445
446 sl.buf_ [0] = sl.length () >> 8;
447 sl.buf_ [1] = sl.length () ;
448
449 send (sl.buf_, sl.length () + sl.hdrlen);
450 }
451
452 void
453 client::send_packet (const char *buf, int len)
454 {
455 packet sl;
456
457 sl << data (buf, len);
458 send_packet (sl);
459 }
460
461 void
462 client::send_packet (const char *buf)
463 {
464 send_packet (buf, strlen (buf));
465 }
466
467 void
468 client::send_packet_printf (const char *format, ...)
469 {
470 packet sl;
471
472 va_list ap;
473 va_start (ap, format);
474 sl.vprintf (format, ap);
475 va_end (ap);
476
477 send_packet (sl);
478 }
479
480 /***********************************************************************
481 *
482 * packet functions/utilities
483 *
484 **********************************************************************/
485
486 packet::packet (const char *name)
487 {
488 reset ();
489
490 int len = strlen (name);
491 memcpy (cur, name, len); cur += len;
492 *cur++ = ' ';
493 }
494
495 packet &packet::operator <<(const data &v)
496 {
497 if (room () < v.len)
498 reset ();
499 else
500 {
501 if (v.len)
502 {
503 memcpy (cur, v.ptr, v.len);
504 cur += v.len;
505 }
506 }
507
508 return *this;
509 }
510
511 packet &packet::operator <<(const data8 &v)
512 {
513 unsigned int len = min (v.len, 0x00FF);
514 return *this << uint8 (len) << data (v.ptr, len);
515 }
516
517 packet &packet::operator <<(const data16 &v)
518 {
519 unsigned int len = min (v.len, 0xFFFF);
520 return *this << uint16 (len) << data (v.ptr, len);
521 }
522
523 packet &packet::operator <<(const char *v)
524 {
525 return *this << data (v, strlen (v ? v : 0));
526 }
527
528 void
529 packet::vprintf (const char *format, va_list ap)
530 {
531 int size = room ();
532
533 int len = vsnprintf ((char *)cur, size, format, ap);
534
535 if (len >= size)
536 return reset ();
537
538 cur += len;
539 }
540
541 /******************************************************************************
542 *
543 * statistics logging functions.
544 *
545 ******************************************************************************/
546
547 #ifdef CS_LOGSTATS
548
549 /* cst_tot is for the life of the server, cst_last is for the last series of
550 * stats
551 */
552 CS_Stats cst_tot, cst_lst;
553
554 /**
555 * Writes out the gathered stats. We clear cst_lst.
556 */
557 void
558 write_cs_stats (void)
559 {
560 time_t now = time (NULL);
561
562 /* If no connections recently, don't both to log anything */
563 if (cst_lst.ibytes == 0 && cst_lst.obytes == 0)
564 return;
565
566 /* CSSTAT is put in so scripts can easily find the line */
567 LOG (llevInfo, "CSSTAT: %.16s tot %d %d %d %d inc %d %d %d %d\n",
568 ctime (&now), cst_tot.ibytes, cst_tot.obytes, cst_tot.max_conn,
569 now - cst_tot.time_start, cst_lst.ibytes, cst_lst.obytes, cst_lst.max_conn, now - cst_lst.time_start);
570 cst_lst.ibytes = 0;
571 cst_lst.obytes = 0;
572 cst_lst.time_start = now;
573 }
574 #endif
575