1 /*
2 * file com_dg_client.c - send datagrams to client
3 *
4 * $Id: com_dg_client.c,v 1.17 2006/02/18 21:40:02 fzago Exp $
5 *
6 * Program XBLAST
7 * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net)
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published
11 * by the Free Software Foundation; either version 2; or (at your option)
12 * any later version
13 *
14 * This program is distributed in the hope that it will be entertaining,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILTY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
17 * Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.
21 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23
24 #include "xblast.h"
25
26 /*
27 * local macros
28 */
29 #define CLIENT_UDP_PORT(id) (16168+(id))
30
31 /*
32 * local types
33 */
34 typedef struct
35 {
36 XBCommDgram dgram;
37 unsigned id;
38 long ping;
39 } XBCommDgramClient;
40
41 /*
42 * local variables
43 */
44 static XBCommDgramClient *commList[MAX_HOSTS] = {
45 /* this entry is never used (server) */
46 NULL,
47 /* up to 5 clients can connect */
48 NULL, NULL, NULL, NULL, NULL,
49 };
50 static int commCount = 0;
51
52 /*******************
53 * local functions *
54 *******************/
55
56 /*
57 * calculate difference for two timevals
58 */
59 static void
DiffTimeVal(struct timeval * diff,const struct timeval * a,const struct timeval * b)60 DiffTimeVal (struct timeval *diff, const struct timeval *a, const struct timeval *b)
61 {
62 assert (NULL != diff);
63 assert (NULL != a);
64 assert (NULL != b);
65
66 diff->tv_sec = a->tv_sec - b->tv_sec;
67 if (a->tv_usec < b->tv_usec) {
68 diff->tv_usec = 1000000 + a->tv_usec - b->tv_usec;
69 diff->tv_sec--;
70 }
71 else {
72 diff->tv_usec = a->tv_usec - b->tv_usec;
73 }
74 } /* DiffTimeVal */
75
76 /********************
77 * polling function *
78 ********************/
79
80 /*
81 * polling for datagram connections
82 */
83 static void
PollDatagram(const struct timeval * tv)84 PollDatagram (const struct timeval *tv)
85 {
86 int i, j;
87 struct timeval dtSnd;
88 struct timeval dtRcv;
89 XBBool initPingTime = XBFalse;
90 int pingTime[MAX_HOSTS];
91
92 assert (NULL != tv);
93 for (i = 1; i < MAX_HOSTS; i++) {
94 if (NULL != commList[i] && commList[i]->dgram.connected) {
95 /* when was last datagram send */
96 DiffTimeVal (&dtSnd, tv, &commList[i]->dgram.lastSnd);
97 DiffTimeVal (&dtRcv, tv, &commList[i]->dgram.lastRcv);
98 /* send a ping to client, when no datagram was send for over 500ms */
99 if (dtSnd.tv_sec >= 1 || dtSnd.tv_usec > 500000) {
100 if (!initPingTime) {
101 pingTime[0] = -1;
102 for (j = 1; j < MAX_HOSTS; j++) {
103 pingTime[j] = commList[j] ? commList[j]->ping : -1;
104 }
105 initPingTime = XBTrue;
106 }
107 Dgram_SendPingData (&commList[i]->dgram, pingTime);
108 }
109 /* check last chance to send datagram */
110 if (0 != commList[i]->dgram.lastRcv.tv_sec && dtRcv.tv_sec > LINK_LOST) {
111 /* inform application */
112 Dbg_D2C ("client %u timed out\n", i);
113 Server_DgramEvent (i, XBSC_Timeout);
114 }
115 }
116 }
117 } /* PollDatagram */
118
119 /************
120 * handlers *
121 ************/
122
123 /*
124 * server received ping or times from client (times are ignored)
125 */
126 static void
ReceivePing(XBCommDgram * dgram,unsigned clientID,unsigned short pingTime)127 ReceivePing (XBCommDgram * dgram, unsigned clientID, unsigned short pingTime)
128 {
129 struct timeval tvPing;
130 XBCommDgramClient *dComm = (XBCommDgramClient *) dgram;
131
132 /* server reacts to empty pings only */
133 if (0 == clientID) {
134 assert (dComm != NULL);
135 DiffTimeVal (&tvPing, &dgram->lastRcv, &dgram->lastSnd);
136 dComm->ping = 1000L * tvPing.tv_sec + (tvPing.tv_usec) / 1000L;
137 Dbg_D2C ("ping from client %u = %lu ms\n", dComm->id, dComm->ping);
138 /* inform application */
139 Server_ReceivePing (dComm->id, dComm->ping);
140 }
141 } /* ReceivePing */
142
143 /*
144 * server encountered some event while parsing
145 * return XBTrue will trigger delete handler
146 */
147 static XBBool
ReceiveInfo(XBCommDgram * dgram,XBDgramInfo info)148 ReceiveInfo (XBCommDgram * dgram, XBDgramInfo info)
149 {
150 XBCommDgramClient *dComm = (XBCommDgramClient *) dgram;
151 assert (dComm != NULL);
152 switch (info) {
153 case XBDI_LOSS:
154 Dbg_D2C ("future frames from client %u\n", dComm->id);
155 return (Server_DgramEvent (dComm->id, XBSC_GameTime));
156 case XBDI_PARSED:
157 Dbg_D2C ("Frames parsed [%lu,%lu]\n", (unsigned long)dgram->rcvfirst, (unsigned long)dgram->rcvnext);
158 dgram->queue = dgram->rcvnext;
159 return XBFalse;
160 case XBDI_IGNORE:
161 Dbg_D2C ("gametime %lu from client %u ignored\n", (unsigned long)dgram->ignore, dComm->id);
162 return XBFalse;
163 case XBDI_FINISH:
164 Dbg_D2C ("Receiving FINISH from client %u\n", dComm->id);
165 Server_ReceiveFinish (dComm->id);
166 return XBFalse;
167 case XBDI_CONSUCC:
168 Dbg_D2C ("udp connection established to client %u\n", dComm->id);
169 return XBFalse;
170 case XBDI_CONFAIL:
171 Dbg_D2C ("failed to connect to client %u\n", dComm->id);
172 return (Server_DgramEvent (dComm->id, XBSC_ConnFailed));
173 case XBDI_WRITEERR:
174 Dbg_D2C ("write error to client %u\n", dComm->id);
175 return (Server_DgramEvent (dComm->id, XBSC_WriteError));
176 case XBDI_CLOSE:
177 Dbg_D2C ("udp to client %u removed\n", dComm->id);
178 D2C_Clear (dComm->id);
179 return (Server_DgramEvent (dComm->id, XBSC_DgramClosed));
180 default:
181 Dbg_D2C ("ignoring unknown dgram event (%u)\n", info);
182 return XBFalse;
183 }
184 } /* ReceiveInfo */
185
186 /*
187 * server received action from client
188 */
189 static void
ReceivePlayerAction(XBCommDgram * dgram,int gameTime,const PlayerAction * playerAction)190 ReceivePlayerAction (XBCommDgram * dgram, int gameTime, const PlayerAction * playerAction)
191 {
192 XBCommDgramClient *dComm = (XBCommDgramClient *) dgram;
193 assert (dComm != NULL);
194 Dbg_D2C ("Receiving action from client %u at gt=%u\n", dComm->id, gameTime);
195 Server_ReceivePlayerAction (dComm->id, gameTime, playerAction);
196 } /* ReceivePlayerAction */
197
198 /***************
199 * constructor *
200 ***************/
201
202 /*
203 * create datagram connection to a game client
204 */
205 XBComm *
D2C_CreateComm(unsigned id,const char * localname,XBBool fixedPort)206 D2C_CreateComm (unsigned id, const char *localname, XBBool fixedPort)
207 {
208 XBSocket *pSocket;
209 /* sanity checks */
210 assert (id > 0);
211 assert (id < MAX_HOSTS);
212 assert (commList[id] == NULL);
213 /* create socket */
214 pSocket = Net_BindUdp (localname, fixedPort ? CLIENT_UDP_PORT (id) : 0);
215 if (NULL == pSocket) {
216 Dbg_D2C ("failed to create socket to client %u\n", id);
217 return NULL;
218 }
219 Dbg_D2C ("created socket to client %u on port %u\n", id, fixedPort ? CLIENT_UDP_PORT (id) : 0);
220 /* create communication data structure */
221 commList[id] = calloc (1, sizeof (*commList[id]));
222 assert (NULL != commList[id]);
223 /* set values */
224 Dgram_CommInit (&commList[id]->dgram, COMM_DgClient, pSocket, ReceivePing, ReceiveInfo,
225 ReceivePlayerAction);
226 commList[id]->id = id;
227 /* setup polling */
228 if (0 == commCount) {
229 GUI_AddPollFunction (PollDatagram);
230 }
231 commCount++;
232 /* that's all */
233 Dbg_D2C ("established handlers for client %u (poll #%u)\n", id, commCount);
234 return &commList[id]->dgram.comm;
235 } /* D2C_CreateComm */
236
237 /**********************
238 * connect/disconnect *
239 **********************/
240
241 /*
242 * connect to game client port
243 */
244 XBBool
D2C_Connect(unsigned id,const char * host,unsigned short port)245 D2C_Connect (unsigned id, const char *host, unsigned short port)
246 {
247 /* sanity checks */
248 assert (id > 0);
249 assert (id < MAX_HOSTS);
250 assert (commList[id] != NULL);
251 assert (commList[id]->dgram.comm.socket != NULL);
252 /* connect socket */
253 if (port != 0) {
254 /* connect to client address and port */
255 commList[id]->dgram.connected =
256 Net_ConnectUdp (commList[id]->dgram.comm.socket, host, port);
257 Dbg_D2C ("connecting to client %u (%s:%u) : %s\n", id, host, port,
258 commList[id]->dgram.connected ? "ok" : "failed");
259 return commList[id]->dgram.connected;
260 }
261 else {
262 /* client uses NAT we wait for his first datagram */
263 Dbg_D2C ("(NAT) setting expected host for client %u to %s\n", id, host);
264 commList[id]->dgram.host = host;
265 return XBTrue;
266 }
267 } /* D2C_Connect */
268
269 /*
270 * clear connection data
271 */
272 void
D2C_Clear(unsigned id)273 D2C_Clear (unsigned id)
274 {
275 assert (id > 0);
276 assert (id < MAX_HOSTS);
277 assert (commList[id] != NULL);
278 commList[id] = NULL;
279 /* disable polling */
280 commCount--;
281 if (0 == commCount) {
282 GUI_SubtractPollFunction (PollDatagram);
283 }
284 Dbg_D2C ("Cleared connection to client %u\n", id);
285 } /* ClearConnection */
286
287 /*
288 * disconnect given client
289 */
290 void
D2C_Disconnect(unsigned id)291 D2C_Disconnect (unsigned id)
292 {
293 assert (id > 0);
294 assert (id < MAX_HOSTS);
295 assert (commList[id] != NULL);
296 /* we only need to shutdown the socket */
297 Dbg_D2C ("disconnecting client %u\n", id);
298 CommDelete (&commList[id]->dgram.comm);
299 } /* D2C_Disconnect */
300
301 /******************
302 * get local data *
303 ******************/
304
305 /*
306 * get port for game client
307 */
308 unsigned short
D2C_Port(unsigned id)309 D2C_Port (unsigned id)
310 {
311 return Dgram_Port (&commList[id]->dgram);
312 } /* D2C_Port */
313
314 /*
315 * is client connected ?
316 */
317 XBBool
D2C_Connected(unsigned id)318 D2C_Connected (unsigned id)
319 {
320 assert (id < MAX_HOSTS);
321 return (commList[id] != NULL);
322 } /* D2C_Connected */
323
324 /*
325 * last ping time
326 */
327 long
D2C_LastPing(unsigned id)328 D2C_LastPing (unsigned id)
329 {
330 assert (id > 0);
331 assert (id < MAX_HOSTS);
332 assert (NULL != commList[id]);
333 return commList[id]->ping;
334 } /* D2C_Connected */
335
336 /******************
337 * set local data *
338 ******************/
339
340 /*
341 * reset communication after level start
342 */
343 void
D2C_Reset(unsigned id)344 D2C_Reset (unsigned id)
345 {
346 assert (id > 0);
347 assert (id < MAX_HOSTS);
348 assert (commList[id] != NULL);
349 Dbg_D2C ("resetting frames for client %u\n", id);
350 Dgram_Reset (&commList[id]->dgram);
351 } /* D2C_Reset */
352
353 /*
354 * set mask bytes for all client connections
355 */
356 void
D2C_SetMaskBytes(unsigned num)357 D2C_SetMaskBytes (unsigned num)
358 {
359 unsigned id;
360 assert (num < MAX_MASK_BYTES);
361 for (id = 1; id < MAX_HOSTS; id++) {
362 if (D2C_Connected (id)) {
363 Dgram_SetMaskBytes (&commList[id]->dgram, num);
364 }
365 }
366 Dbg_D2C ("setting mask bytes to %u\n", num);
367 } /* D2C_SetMaskBytes */
368
369 /**************
370 * queue data *
371 **************/
372
373 /*
374 * send player action to game client
375 */
376 void
D2C_SendPlayerAction(unsigned id,int gameTime,const PlayerAction * playerAction)377 D2C_SendPlayerAction (unsigned id, int gameTime, const PlayerAction * playerAction)
378 {
379 assert (id > 0);
380 assert (id < MAX_HOSTS);
381 assert (commList[id] != NULL);
382
383 Dbg_D2C ("queueing actions for client %u at gt=%u\n", id, gameTime);
384 Dgram_SendPlayerAction (&commList[id]->dgram, gameTime, playerAction);
385 } /* D2C_SendPlayerAction */
386
387 /*
388 * send finish level to game client
389 */
390 void
D2C_SendFinish(unsigned id,int gameTime)391 D2C_SendFinish (unsigned id, int gameTime)
392 {
393 assert (id > 0);
394 assert (id < MAX_HOSTS);
395 assert (commList[id] != NULL);
396
397 Dbg_D2C ("queueing FINISH to client %u at gt=%u\n", id, gameTime);
398 Dgram_SendFinish (&commList[id]->dgram, gameTime);
399 } /* D2C_SendFinish */
400
401 /*
402 * flush remaining datagrams
403 */
404 XBBool
D2C_Flush(unsigned id)405 D2C_Flush (unsigned id)
406 {
407 assert (id > 0);
408 assert (id < MAX_HOSTS);
409 assert (commList[id] != NULL);
410
411 /* for server, last send acknowledged if last receive equals last sent */
412 if (commList[id]->dgram.rcvnext == commList[id]->dgram.sndnext) {
413 return XBFalse;
414 }
415 Dbg_D2C ("resending frames to client %u\n", id);
416 return Dgram_Flush (&commList[id]->dgram);
417 } /* D2C_Flush */
418
419 /*
420 * end of file com_dg_client.c
421 */
422