--- deliantra/server/socket/lowlevel.C 2006/12/17 19:14:00 1.25 +++ deliantra/server/socket/lowlevel.C 2007/03/14 15:44:47 1.36 @@ -1,25 +1,26 @@ /* - CrossFire, A Multiplayer game for X-windows - - Copyright (C) 1992 Frank Tore Johansen - - 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; either version 2 of the License, or - (at your option) any later version. - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - - The author can be reached via e-mail to mark@pyramid.com -*/ - + * CrossFire, A Multiplayer game for X-windows + * + * Copyright (C) 2005, 2006, 2007 Marc Lehmann & Crossfire+ Development Team + * Copyright (C) 1992 Frank Tore Johansen + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * The author can be reached via e-mail to mark@pyramid.com + */ + /** * \file * Low-level socket-related functions. @@ -42,18 +43,20 @@ # include # include # include -# define TCP_HZ 1000 // sorry... # include #endif // use a really low timeout, as it doesn't cost any bandwidth, and you can // easily die in 20 seconds... -#define SOCKET_TIMEOUT1 10 -#define SOCKET_TIMEOUT2 20 +#define SOCKET_TIMEOUT1 10 * 1000 +#define SOCKET_TIMEOUT2 20 * 1000 void client::flush () { + if (destroyed ()) + return; + #ifdef __linux__ // check time of last ack, and, if too old, kill connection struct tcp_info tcpi; @@ -66,8 +69,8 @@ rtt = tcpi.tcpi_rtt; rttvar = tcpi.tcpi_rttvar; - if (tcpi.tcpi_unacked && SOCKET_TIMEOUT1 * TCP_HZ < diff && diff < 0x80000000UL // ack delayed for 20s - && SOCKET_TIMEOUT2 * TCP_HZ < tcpi.tcpi_last_data_sent) // no data sent for 10s + if (tcpi.tcpi_unacked && SOCKET_TIMEOUT1 < diff && diff < 0x80000000UL // ack delayed for 20s + && SOCKET_TIMEOUT2 < tcpi.tcpi_last_data_sent) // no data sent for 10s { LOG (llevDebug, "Connection on fd %d closed due to ack timeout (%u/%u/%u)\n", fd, (unsigned) tcpi.tcpi_last_ack_recv, (unsigned) tcpi.tcpi_last_data_sent, (unsigned) tcpi.tcpi_unacked); @@ -185,44 +188,72 @@ * Dispatch table for the server. */ static struct packet_type packets[] = { - {"ncom", PC(NewPlayerCmd) PF_PLAYING }, - {"command", PC(PlayerCmd) PF_PLAYING }, + {"ncom", PC(NewPlayerCmd) PF_PLAYING | PF_COMMAND6 }, + {"command", PC(PlayerCmd) PF_PLAYING | PF_COMMAND0 }, {"examine", PC(ExamineCmd) PF_PLAYING }, {"apply", PC(ApplyCmd) PF_PLAYING }, - {"reply", PC(ReplyCmd) PF_IMMEDIATE }, {"lookat", PC(LookAt) PF_PLAYING }, {"lock", PC(LockItem) PF_PLAYING }, {"mark", PC(MarkItem) PF_PLAYING }, {"move", PC(MoveCmd) PF_PLAYING }, - {"exti", PC(ExtCmd) PF_IMMEDIATE }, /* CF+ */ {"ext", PC(ExtCmd) 0 }, /* CF+ */ - {"mapredraw", PC(MapRedrawCmd) PF_IMMEDIATE }, /* Added: phil */ + {"mapredraw", PC(MapRedrawCmd) 0 }, /* Added: phil */ {"mapinfo", PC(MapInfoCmd) 0 }, /* CF+ */ - {"addme", SC(AddMeCmd) PF_IMMEDIATE }, - {"askface", SC(SendFaceCmd) PF_IMMEDIATE }, /* Added: phil */ - {"requestinfo", SC(RequestInfo) PF_IMMEDIATE }, - {"setfacemode", SC(SetFaceMode) PF_IMMEDIATE }, - {"setsound", SC(SetSound) PF_IMMEDIATE }, - {"setup", SC(SetUp) PF_IMMEDIATE }, - {"version", SC(VersionCmd) PF_IMMEDIATE }, - {"toggleextendedinfos", SC(ToggleExtendedInfos) PF_IMMEDIATE }, /*Added: tchize */ - {"toggleextendedtext", SC(ToggleExtendedText) PF_IMMEDIATE }, /*Added: tchize */ - {"asksmooth", SC(AskSmooth) PF_IMMEDIATE }, /*Added: tchize (smoothing technologies) */ + {"reply", SC(ReplyCmd) 0 }, + {"exti", SC(ExtiCmd) 0 }, /* CF+ */ + {"addme", SC(AddMeCmd) 0 }, + {"askface", SC(AskFaceCmd) 0 }, + {"requestinfo", SC(RequestInfo) 0 }, + {"setfacemode", SC(SetFaceMode) 0 }, + {"setsound", SC(SetSound) 0 }, + {"setup", SC(SetUp) 0 }, + {"version", SC(VersionCmd) 0 }, + {"toggleextendedinfos", SC(ToggleExtendedInfos) 0 }, /*Added: tchize */ + {"toggleextendedtext", SC(ToggleExtendedText) 0 }, /*Added: tchize */ + {"asksmooth", SC(AskSmooth) 0 }, /*Added: tchize (smoothing technologies) */ }; bool client::may_execute (const packet_type *pkt) const { return (!(pkt->flags & PF_PLAYER) || pl) - && (!(pkt->flags & PF_PLAYING) || (pl && pl->state == ST_PLAYING)); + && (!(pkt->flags & PF_PLAYING) || state == ST_PLAYING); +} + +// HACK: some commands currently should be executed +// even when the player is frozen. this hack detects +// those commands. it should be folded into may_execute, +// but kept seperate to emphasise the hack aspect, i.e. +// do it better, then remove. +static bool +always_immediate (const client *ns, const packet_type *pkt, const char *data, int len) +{ + if (!(pkt->flags & (PF_COMMAND0 | PF_COMMAND6))) + return false; + + if (!ns->pl || !ns->pl->ob || !ns->pl->ob->map) + return false; + + if (pkt->flags & PF_COMMAND6) + { + data += 6; + len -= 6; + } + + if (len > 4 && !strncmp (data, "say " , 4)) + return true; + if (len > 5 && !strncmp (data, "chat ", 5)) + return true; + + return false; } void client::execute (const packet_type *pkt, char *data, int datalen) { - if (may_execute (pkt)) + if (may_execute (pkt) || always_immediate (this, pkt, data, datalen)) { //TODO: only one format if (pkt->flags & PF_PLAYER) @@ -273,18 +304,15 @@ for (packet_type *pkt = packets; pkt < packets + (sizeof (packets) / sizeof (packets[0])); ++pkt) if (!strcmp ((char *)inbuf + 2, pkt->name)) { - if (pkt->flags & PF_IMMEDIATE) - execute (pkt, data, datalen); - else + if (pkt->flags & PF_PLAYER && !always_immediate (this, pkt, data, datalen)) queue_command (pkt, data, datalen); + else + execute (pkt, data, datalen); goto next_packet; } - /* If we get here, we didn't find a valid command. Logging - * this might be questionable, because a broken client/malicious - * user could certainly send a whole bunch of invalid commands. - */ + // If we get here, we didn't find a valid command. send_packet_printf ("drawinfo %d ERROR: command '%s' not supported.", NDI_RED, (char *)inbuf + 2); next_packet: skip_packet (pkt_len); @@ -300,7 +328,7 @@ client::socket_cb (iow &w, int got) { //TODO remove when we have better socket cleanup logic - if (status == Ns_Dead) + if (destroyed ()) { socket_ev.poll (0); return; @@ -361,7 +389,7 @@ void client::cmd_cb (iw &w) { - if (handle_packet () || handle_command ()) + if (handle_packet ()) w.start (); else flush (); @@ -383,16 +411,16 @@ client::send (void *buf_, int len) { char *buf = (char *)buf_; - char *pos = buf; - int amt = 0; - if (status == Ns_Dead || !buf) + if (destroyed () || !buf) return; - if ((len + outputbuffer.len) > SOCKETBUFSIZE) + if (len + outputbuffer.len > SOCKETBUFSIZE) { LOG (llevDebug, "socket on fd %d has overrun internal buffer - marking as dead\n", fd); - destroy (); + // shutdown the socket, this is safer than destroying it immediately + // as lots of code in the callchain might still access the map etc. + shutdown (fd, SHUT_RDWR); return; } @@ -427,7 +455,7 @@ void client::send_packet (packet &sl) { - if (status == Ns_Dead) + if (destroyed ()) return; if (sl.length () >= MAXSOCKBUF) @@ -478,12 +506,27 @@ send_packet (sl); } +void +client::send_drawinfo (const char *msg, int flags) +{ + send_packet_printf ("drawinfo %d %s", flags, msg); +} + /*********************************************************************** * * packet functions/utilities * **********************************************************************/ +packet::packet (const char *name) +{ + reset (); + + int len = strlen (name); + memcpy (cur, name, len); cur += len; + *cur++ = ' '; +} + packet &packet::operator <<(const data &v) { if (room () < v.len)