ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/socket/lowlevel.C
Revision: 1.4
Committed: Wed Aug 30 16:30:37 2006 UTC (17 years, 8 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.3: +5 -5 lines
Log Message:
remove compression support, intiialise perl earlier etc. etc.

File Contents

# User Rev Content
1 elmex 1.1
2     /*
3     * static char *rcsid_sockets_c =
4 root 1.4 * "$Id: lowlevel.C,v 1.3 2006-08-29 08:01:38 root Exp $";
5 elmex 1.1 */
6    
7     /*
8     CrossFire, A Multiplayer game for X-windows
9    
10     Copyright (C) 1992 Frank Tore Johansen
11    
12     This program is free software; you can redistribute it and/or modify
13     it under the terms of the GNU General Public License as published by
14     the Free Software Foundation; either version 2 of the License, or
15     (at your option) any later version.
16    
17     This program is distributed in the hope that it will be useful,
18     but WITHOUT ANY WARRANTY; without even the implied warranty of
19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20     GNU General Public License for more details.
21    
22     You should have received a copy of the GNU General Public License
23     along with this program; if not, write to the Free Software
24     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25    
26     The author can be reached via e-mail to mark@pyramid.com
27     */
28    
29     /**
30     * \file
31     * Low-level socket-related functions.
32     *
33     * \date 2003-12-02
34     *
35     * Contains some base functions that both the client and server
36     * can use. As such, depending what we are being compiled for will
37     * determine what we can include. the client is designed have
38     * CFCLIENT defined as part of its compile flags.
39     */
40    
41     using namespace std;
42    
43     #include <global.h>
44     #include <newclient.h>
45     #include <sproto.h>
46    
47     #ifdef __linux__
48     # include <sys/types.h>
49     # include <sys/socket.h>
50     # include <netinet/in.h>
51     # define TCP_HZ 1000 // sorry...
52     # include <netinet/tcp.h>
53     #endif
54    
55     // use a really low timeout, as it doesn't cost any bandwidth, and you can
56     // easily die in 20 seconds...
57     #define SOCKET_TIMEOUT1 10
58     #define SOCKET_TIMEOUT2 20
59    
60     void Socket_Flush (NewSocket *ns)
61     {
62     #ifdef __linux__
63     // check time of last ack, and, if too old, kill connection
64     struct tcp_info tcpi;
65     socklen_t len = sizeof (tcpi);
66    
67     if (!getsockopt (ns->fd, IPPROTO_TCP, TCP_INFO, &tcpi, &len) && len == sizeof (tcpi))
68     {
69     unsigned int diff = tcpi.tcpi_last_ack_recv - tcpi.tcpi_last_data_sent;
70    
71     if (tcpi.tcpi_unacked
72     && SOCKET_TIMEOUT1 * TCP_HZ < diff && diff < 0x80000000UL // ack delayed for 20s
73     && SOCKET_TIMEOUT2 * TCP_HZ < tcpi.tcpi_last_data_sent) // no data sent for 10s
74     {
75 root 1.3 LOG (llevDebug, "Connection on fd %d closed due to ack timeout (%u/%u/%u)\n", ns->fd,
76 elmex 1.1 (unsigned)tcpi.tcpi_last_ack_recv, (unsigned)tcpi.tcpi_last_data_sent, (unsigned)tcpi.tcpi_unacked);
77     ns->status = Ns_Dead;
78     }
79     }
80    
81     int val;
82     val = 0; setsockopt (ns->fd, IPPROTO_TCP, TCP_CORK, &val, sizeof (val));
83     val = 1; setsockopt (ns->fd, IPPROTO_TCP, TCP_CORK, &val, sizeof (val));
84     #endif
85     }
86    
87     /***********************************************************************
88     *
89     * SockList functions/utilities
90     *
91     **********************************************************************/
92    
93     void SockList_Init(SockList *sl)
94     {
95     sl->len=0;
96     sl->buf=NULL;
97     }
98    
99     void SockList_AddInt(SockList *sl, uint32 data)
100     {
101     sl->buf[sl->len++]= (data>>24)&0xff;
102     sl->buf[sl->len++]= (data>>16)&0xff;
103     sl->buf[sl->len++]= (data>>8)&0xff;
104     sl->buf[sl->len++] = data & 0xff;
105     }
106    
107     void SockList_AddInt64(SockList *sl, uint64 data)
108     {
109     sl->buf[sl->len++]= ( char )( (data>>56)&0xff );
110     sl->buf[sl->len++]= ( char )( (data>>48)&0xff );
111     sl->buf[sl->len++]= ( char )( (data>>40)&0xff );
112     sl->buf[sl->len++]= ( char )( (data>>32)&0xff );
113    
114     sl->buf[sl->len++]= ( char )( (data>>24)&0xff );
115     sl->buf[sl->len++]= ( char )( (data>>16)&0xff );
116     sl->buf[sl->len++]= ( char )( (data>>8)&0xff );
117     sl->buf[sl->len++] =( char )( data & 0xff );
118     }
119    
120     /* Basically does the reverse of SockList_AddInt, but on
121     * strings instead. Same for the GetShort, but for 16 bits.
122     */
123     int GetInt_String(unsigned char *data)
124     {
125     return ((data[0]<<24) + (data[1]<<16) + (data[2]<<8) + data[3]);
126     }
127    
128     short GetShort_String(unsigned char *data) {
129     return ((data[0]<<8)+data[1]);
130     }
131    
132     /******************************************************************************
133     *
134     * Start of read routines.
135     *
136     ******************************************************************************/
137    
138     /**
139     * This reads from fd and puts the data in sl. We return true if we think
140     * we have a full packet, 0 if we have a partial packet. The only processing
141     * we do is remove the intial size value. len (As passed) is the size of the
142     * buffer allocated in the socklist. We make the assumption the buffer is
143     * at least 2 bytes long.
144     */
145    
146     int SockList_ReadPacket(int fd, SockList *sl, int len)
147     {
148     int stat,toread;
149    
150     /* Sanity check - shouldn't happen */
151     if (sl->len < 0) {
152 root 1.3 abort();
153 elmex 1.1 }
154     /* We already have a partial packet */
155     if (sl->len<2) {
156     #ifdef WIN32 /* ***WIN32 SockList_ReadPacket: change read() to recv() */
157    
158 root 1.3 stat=recv(fd, sl->buf + sl->len, 2-sl->len,0);
159 elmex 1.1
160     #else
161 root 1.3 do {
162     stat=read(fd, sl->buf + sl->len, 2-sl->len);
163     } while ((stat==-1) && (errno==EINTR));
164     #endif
165     if (stat<0) {
166     /* In non blocking mode, EAGAIN is set when there is no
167     * data available.
168     */
169 elmex 1.1 #ifdef WIN32 /* ***WIN32 SockList_ReadPacket: error handling for win32 */
170 root 1.3 if ((stat==-1) && WSAGetLastError() !=WSAEWOULDBLOCK) {
171     if(WSAGetLastError() == WSAECONNRESET)
172     LOG(llevDebug,"Connection closed by client\n");
173     else
174     {
175     LOG(llevDebug,"ReadPacket got error %d, returning 0\n",WSAGetLastError());
176     }
177     return -1; /* kick this user! */
178     }
179 elmex 1.1 #else
180 root 1.3 if (errno != EAGAIN && errno !=EWOULDBLOCK) {
181 root 1.4 LOG(llevDebug, "ReadPacket got error %s, returning 0\n", strerror(errno));
182 root 1.3 }
183     #endif
184     return 0; /*Error */
185     }
186     if (stat==0) return -1;
187     sl->len += stat;
188 elmex 1.1 #ifdef CS_LOGSTATS
189 root 1.3 cst_tot.ibytes += stat;
190     cst_lst.ibytes += stat;
191 elmex 1.1 #endif
192 root 1.3 if (stat<2) return 0; /* Still don't have a full packet */
193 elmex 1.1 }
194     /* Figure out how much more data we need to read. Add 2 from the
195     * end of this - size header information is not included.
196     */
197     toread = 2+(sl->buf[0] << 8) + sl->buf[1] - sl->len;
198     if ((toread + sl->len) >= len) {
199 root 1.3 LOG(llevError,"SockList_ReadPacket: Want to read more bytes than will fit in buffer (%d>=%d).\n",
200     toread + sl->len, len);
201     /* Quick hack in case for 'oldsocketmode' input. If we are
202     * closing the socket anyways, then reading this extra 100 bytes
203     * shouldn't hurt.
204     */
205 elmex 1.1 #ifdef WIN32 /* ***win32 SockList_ReadPacket: change read() to recv() */
206 root 1.3 recv(fd, sl->buf+2, 100, 0);
207 elmex 1.1 #else
208 root 1.3 read(fd, sl->buf+2, 100);
209 elmex 1.1 #endif /* end win32 */
210    
211 root 1.3 /* return error so the socket is closed */
212     return -1;
213 elmex 1.1 }
214     do {
215     #ifdef WIN32 /* ***win32 SockList_ReadPacket: change read() to recv() */
216 root 1.3 stat = recv(fd, sl->buf+ sl->len, toread, 0);
217 elmex 1.1 #else
218 root 1.3 do {
219     stat = read(fd, sl->buf+ sl->len, toread);
220     } while ((stat<0) && (errno==EINTR));
221 elmex 1.1 #endif
222 root 1.3 if (stat<0) {
223 elmex 1.1
224     #ifdef WIN32 /* ***win32 SockList_ReadPacket: change error handling for win32 */
225 root 1.3 if ((stat==-1) && WSAGetLastError() !=WSAEWOULDBLOCK) {
226     if(WSAGetLastError() == WSAECONNRESET)
227     LOG(llevDebug,"Connection closed by client\n");
228     else
229     {
230     LOG(llevDebug,"ReadPacket got error %d, returning 0\n",WSAGetLastError());
231     }
232     return -1; /* kick this user! */
233     }
234 elmex 1.1 #else
235 root 1.3 if (errno != EAGAIN && errno !=EWOULDBLOCK) {
236 root 1.4 LOG(llevDebug, "ReadPacket got error %s, returning 0\n", strerror(errno));
237 root 1.3 }
238     #endif
239     return 0; /*Error */
240     }
241     if (stat==0) return -1;
242     sl->len += stat;
243 elmex 1.1 #ifdef CS_LOGSTATS
244 root 1.3 cst_tot.ibytes += stat;
245     cst_lst.ibytes += stat;
246 elmex 1.1 #endif
247 root 1.3 toread -= stat;
248     if (toread==0) return 1;
249     if (toread < 0) {
250     LOG(llevError,"SockList_ReadPacket: Read more bytes than desired.\n");
251     return 1;
252     }
253 elmex 1.1 } while (toread>0);
254     return 0;
255     }
256    
257     /*******************************************************************************
258     *
259     * Start of write related routines.
260     *
261     ******************************************************************************/
262    
263     /**
264     * Adds data to a socket buffer for whatever reason.
265     *
266     * ns is the socket we are adding the data to, buf is the start of the
267     * data, and len is the number of bytes to add.
268     */
269    
270     static void add_to_buffer(NewSocket *ns, char *buf, int len)
271     {
272     int avail, end;
273    
274     if ((len+ns->outputbuffer.len)>SOCKETBUFSIZE) {
275 root 1.3 LOG(llevDebug,"Socket on fd %d has overrun internal buffer - marking as dead\n",
276     ns->fd);
277     ns->status = Ns_Dead;
278     return;
279 elmex 1.1 }
280    
281     /* data + end is where we start putting the new data. The last byte
282     * currently in use is actually data + end -1
283     */
284    
285     end=ns->outputbuffer.start + ns->outputbuffer.len;
286     /* The buffer is already in a wrapped state, so adjust end */
287     if (end>=SOCKETBUFSIZE) end-=SOCKETBUFSIZE;
288     avail=SOCKETBUFSIZE - end;
289    
290     /* We can all fit it behind the current data without wrapping */
291     if (avail >=len ) {
292 root 1.3 memcpy(ns->outputbuffer.data + end, buf, len);
293 elmex 1.1 }
294     else {
295 root 1.3 memcpy(ns->outputbuffer.data + end, buf, avail);
296     memcpy(ns->outputbuffer.data, buf+avail, len-avail);
297 elmex 1.1 }
298     ns->outputbuffer.len += len;
299     #if 0
300     LOG(llevDebug,"Added %d to output buffer, total length now %d, start=%d\n", len,
301 root 1.3 ns->outputbuffer.len, ns->outputbuffer.start);
302 elmex 1.1 #endif
303     }
304    
305     /**
306     * Writes data to socket.
307     *
308     * When the socket is clear to write, and we have backlogged data, this
309     * is called to write it out.
310     */
311     void write_socket_buffer(NewSocket *ns)
312     {
313     int amt, max;
314    
315     if (ns->outputbuffer.len==0) {
316 root 1.3 LOG(llevDebug,"write_socket_buffer called when there is no data, fd=%d\n",
317     ns->fd);
318     return;
319 elmex 1.1 }
320    
321     do {
322 root 1.3 max = SOCKETBUFSIZE - ns->outputbuffer.start;
323     if (ns->outputbuffer.len<max) max = ns->outputbuffer.len;
324 elmex 1.1
325     #ifdef WIN32 /* ***win32 write_socket_buffer: change write() to send() */
326 root 1.3 amt=send(ns->fd, ns->outputbuffer.data + ns->outputbuffer.start, max,0);
327 elmex 1.1 #else
328 root 1.3 do {
329     amt=write(ns->fd, ns->outputbuffer.data + ns->outputbuffer.start, max);
330     } while ((amt<0) && (errno==EINTR));
331 elmex 1.1 #endif
332    
333 root 1.3 if (amt < 0) { /* We got an error */
334 elmex 1.1
335     #ifdef WIN32 /* ***win32 write_socket_buffer: change error handling */
336 root 1.3 if (amt == -1 && WSAGetLastError() !=WSAEWOULDBLOCK) {
337     LOG(llevError,"New socket write failed (wsb) (%d).\n", WSAGetLastError());
338 elmex 1.1 #else
339 root 1.3 if (errno !=EWOULDBLOCK) {
340     LOG(llevError,"New socket write failed (wsb) (%d: %s).\n",
341 root 1.4 errno, strerror(errno));
342 root 1.3 #endif
343     ns->status=Ns_Dead;
344     return;
345     }
346     else { /* EWOULDBLOCK */
347     /* can't write it, so store it away. */
348     ns->can_write=0;
349     return;
350     }
351     }
352     ns->outputbuffer.start += amt;
353     /* wrap back to start of buffer */
354     if (ns->outputbuffer.start==SOCKETBUFSIZE) ns->outputbuffer.start=0;
355     ns->outputbuffer.len -= amt;
356 elmex 1.1 #ifdef CS_LOGSTATS
357 root 1.3 cst_tot.obytes += amt;
358     cst_lst.obytes += amt;
359 elmex 1.1 #endif
360     } while (ns->outputbuffer.len>0);
361     }
362    
363     /**
364     * This writes data to the socket. - It is very low level -
365     * all we try and do is write out the data to the socket
366     * provided (ns). buf is the data to write, len is the number
367     * of bytes to write. IT doesn't return anything - rather, it
368     * updates the ns structure if we get an error.
369     */
370     void Write_To_Socket(NewSocket *ns, char *buf, int len)
371     {
372     int amt=0;
373     char *pos=buf;
374    
375     if (ns->status == Ns_Dead || !buf) {
376 root 1.3 LOG(llevDebug,"Write_To_Socket called with dead socket\n");
377     return;
378 elmex 1.1 }
379    
380     #ifndef __GNU__ /* This caused problems on Hurd */
381     if (!ns->can_write) {
382 root 1.3 add_to_buffer(ns, buf, len);
383     return;
384 elmex 1.1 }
385     #endif
386     /* If we manage to write more than we wanted, take it as a bonus */
387     while (len>0) {
388    
389     #ifdef WIN32 /* ***win32 Write_To_Socket: change write() to send() */
390 root 1.3 amt=send(ns->fd, pos, len,0);
391 elmex 1.1 #else
392 root 1.3 do {
393     amt=write(ns->fd, pos, len);
394     } while ((amt<0) && (errno==EINTR));
395 elmex 1.1 #endif
396    
397 root 1.3 if (amt < 0) { /* We got an error */
398 elmex 1.1 #ifdef WIN32 /* ***win32 Write_To_Socket: change error handling */
399 root 1.3 if (amt == -1 && WSAGetLastError() !=WSAEWOULDBLOCK) {
400     LOG(llevError,"New socket write failed WTS (%d).\n",WSAGetLastError());
401 elmex 1.1 #else
402 root 1.3 if (errno !=EWOULDBLOCK) {
403     LOG(llevError,"New socket write failed WTS (%d: %s).\n", /* ---WIN32 */
404 root 1.4 errno, strerror(errno));
405 root 1.3 #endif
406     ns->status=Ns_Dead;
407     return;
408     }
409     else { /* EWOULDBLOCK */
410     /* can't write it, so store it away. */
411     add_to_buffer(ns, pos, len);
412     ns->can_write=0;
413     return;
414     }
415     }
416     /* amt gets set to 0 above in blocking code, so we do this as
417     * an else if to make sure we don't reprocess it.
418     */
419     else if (amt==0) {
420     LOG(llevError,"Write_To_Socket: No data written out.\n");
421     }
422     len -= amt;
423     pos += amt;
424 elmex 1.1 #ifdef CS_LOGSTATS
425 root 1.3 cst_tot.obytes += amt;
426     cst_lst.obytes += amt;
427 elmex 1.1 #endif
428     }
429     }
430    
431    
432     /**
433     * Takes a string of data, and writes it out to the socket. A very handy
434     * shortcut function.
435     */
436     void cs_write_string(NewSocket *ns, const char *buf, int len)
437     {
438     SockList sl;
439    
440     sl.len = len;
441     sl.buf = (unsigned char*)buf;
442     Send_With_Handling(ns, &sl);
443     }
444    
445    
446     /**
447     * Calls Write_To_Socket to send data to the client.
448     *
449     * The only difference in this function is that we take a SockList
450     *, and we prepend the length information.
451     */
452     void Send_With_Handling(NewSocket *ns,SockList *msg)
453     {
454     unsigned char sbuf[4];
455    
456     if (ns->status == Ns_Dead || !msg)
457 root 1.3 return;
458 elmex 1.1
459     if (msg->len >= MAXSOCKBUF) {
460 root 1.3 LOG(llevError,"Trying to send a buffer beyond properly size, len =%d\n",
461     msg->len);
462     /* Almost certainly we've overflowed a buffer, so quite now to make
463     * it easier to debug.
464     */
465     abort();
466 elmex 1.1 }
467     sbuf[0] = ((uint32)(msg->len) >> 8) & 0xFF;
468     sbuf[1] = ((uint32)(msg->len)) & 0xFF;
469     if (ns->status != Ns_Old)
470 root 1.3 Write_To_Socket(ns, (char *) sbuf, 2);
471 elmex 1.1 Write_To_Socket(ns, (char*)msg->buf, msg->len);
472     }
473    
474     /**
475     * Takes a string of data, and writes it out to the socket. A very handy
476     * shortcut function.
477     */
478     void Write_String_To_Socket(NewSocket *ns, char *buf, int len)
479     {
480     SockList sl;
481    
482     sl.len = len;
483     sl.buf = (unsigned char*) buf;
484     Send_With_Handling(ns, &sl);
485     }
486    
487    
488     /******************************************************************************
489     *
490     * statistics logging functions.
491     *
492     ******************************************************************************/
493    
494     #ifdef CS_LOGSTATS
495     /* cst_tot is for the life of the server, cst_last is for the last series of
496     * stats
497     */
498     CS_Stats cst_tot, cst_lst;
499    
500     /**
501     * Writes out the gathered stats. We clear cst_lst.
502     */
503     void write_cs_stats(void)
504     {
505     time_t now=time(NULL);
506    
507     /* If no connections recently, don't both to log anything */
508     if (cst_lst.ibytes==0 && cst_lst.obytes==0) return;
509    
510     /* CSSTAT is put in so scripts can easily find the line */
511     LOG(llevInfo, "CSSTAT: %.16s tot %d %d %d %d inc %d %d %d %d\n",
512 root 1.3 ctime(&now), cst_tot.ibytes, cst_tot.obytes, cst_tot.max_conn,
513     now - cst_tot.time_start, cst_lst.ibytes, cst_lst.obytes,
514     cst_lst.max_conn, now - cst_lst.time_start);
515 elmex 1.1 cst_lst.ibytes=0;
516     cst_lst.obytes=0;
517     cst_lst.max_conn=socket_info.nconns;
518     cst_lst.time_start=now;
519     }
520     #endif