forked from patterson7019/ssobjects
-
Notifications
You must be signed in to change notification settings - Fork 0
/
simpleserver.cpp
548 lines (472 loc) · 13.9 KB
/
simpleserver.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
/********************************************************************
Copyright (c) 2006, Lee Patterson & Ant Works Software
http://ssobjects.sourceforge.net
created : December 19, 2000
filename : simpleserver.cpp
author : Lee Patterson (workerant@users.sourceforge.net)
purpose : //TODO: describe the two modes this class runs as
*********************************************************************/
#include "simpleserver.h"
#include "serversocket.h"
#include "serverhandler.h"
#include "stopwatch.h"
using namespace ssobjects;
/**
This constructs the server object to run as a single threaded server.
[Win32] WSAStartup is called at this point. You should construct the server
before calling canBind method.
\param saBind Port and protocal address information you want to bind to. See
SimpleServer::canBind for more details on using saBind.
\param nFreq How often idle method will be called in milli-seconds.
\param nMaxCon Reserved for future expansion.
\throw SimpleServerException If nFreq is out of range.
\throw GeneralException [Win32] If WSAStartup call failes.
\todo
Test passing in the protocol address.
**/
SimpleServer::SimpleServer(
const SockAddr& saBind,
const unsigned32 nFreq,
const unsigned32 nMaxCon)
: m_sListen(),
m_saServer(saBind),
m_listClients(),
m_que(),
m_nIdleFrequency(nFreq),
m_nMaxCon(nMaxCon),
m_bUsingThread(false),
m_bPause(false),
m_nSleepTime(0),
m_tvServerStarted(),
m_rset(),
m_wset()
{
#ifdef _WIN32
WSADATA wsd;
if(WSAStartup(0x0101,&wsd)!=0)
throwGeneralException("unable to start windows socket layer");
#endif
if(m_nIdleFrequency < MIN_FREQ)
{
CStr msg;
msg.format("Server frequency of %d is too fine. Minimum is %d.",nFreq,MIN_FREQ);
throwSimpleServerException(msg);
}
}
/**
Creating a simple server as a handler for a socket. This constructor is used
in the simple manager. The socket is already attached. This instance of a simple
server is meant to be run multi-threaded.
\param psocket Client socket that is connected.
\param nFreq idle frequency. (See note below.)
\Todo
Finish explaining how threading and idling is accomplished.
**/
SimpleServer::SimpleServer(ServerSocket* const psocket,const unsigned32 nFreq)
: m_sListen(),
m_saServer(),
m_listClients(),
m_que(),
m_nIdleFrequency(nFreq),
m_nMaxCon(0),
m_bUsingThread(true),
m_bPause(false),
m_nSleepTime(0),
m_tvServerStarted(),
m_rset(),
m_wset()
{
m_listClients.addTail(psocket);
}
/**
Destroys the server object, closes the listening socket, removes all msg's
in the message que, removes all client socket connects and closes them.
[Win32] Calls WSACleanup()
**/
SimpleServer::~SimpleServer()
{
m_que.purge(); //remove all items in the list, and delete any packets that where in the que
m_listClients.purge(); //remove all clients from the list, and delete the sockets
m_sListen.close(); //close the listening socket
#ifdef _WIN32
WSACleanup();
#endif
}
/**
Creates a listen socket and tries binding it using the port information
passed in. Once the connection has been bound, the connection is put into
listen mode, and is then closed. Good for when you want to start the server
in single threaded mode, but need to make sure that you were able to bind.
When using the protocol address in saBind, this will bind this server to a specific IP
address. Useful when the machine this server is running on has more then one IP address
assigned to it. See the manpage on bind for more details.
\param saBind Port and protocol address information you want to bind to.
**/
bool
SimpleServer::canBind(SockAddr& saBind)
{
SocketInstance s;
try
{
s.create();
s.bind(saBind);
s.listen();
s.close();
}
catch(GeneralException& e)
{
UNUSED_ALWAYS(e);
s.cleanup();
return false;
}
return true;
}
/**
Creates the listen socket, binds to the address passed in at construction,
and puts the listen socket into listen mode.
This method does not return until the server has stopped. The message pump
and idler are your only access points after this call. You would typically
do any setup you need to do before this is called. Once this returns, the
server object can be destroyed.
**/
void
SimpleServer::startServer()
{
gettimeofday(&m_tvServerStarted,NULL);
m_sListen.create();
m_sListen.bind(m_saServer);
m_sListen.listen();
setRunning(true);
ThreadHandlerProc(); //call the main handler directly non-threaded
setRunning(false);
}
void
SimpleServer::pauseIncomingConnections()
{
m_bPause=true;
}
void
SimpleServer::killServer()
{
m_bRun = false;
if(m_bUsingThread)
stop();
}
/**
\return (windows) number of ticks since windows started.
(linux) number of ticks since server app started
**/
unsigned32
SimpleServer::getTicks()
{
#ifdef _WIN32
//just return the multimedia timer value
return timeGetTime();
#else
//calculate how many ticks it has been since the server started
struct timeval tv;
unsigned32 nTicks;
unsigned32 nSec,nMicroSec;
gettimeofday(&tv,NULL);
nSec = tv.tv_sec - m_tvServerStarted.tv_sec;
nMicroSec = tv.tv_usec - m_tvServerStarted.tv_usec;
//figure out how many microseconds, then convert to milliseconds
nTicks = (nSec * 1000000 + nMicroSec)/1000;
return nTicks;
#endif
}
//
//finds the highest socket number and sets the rset and wset bits
//from our list of clients and our listening socket.
//
int
SimpleServer::getMaxFD()
{
int imax = m_sListen;
ServerSocket* s;
FD_ZERO(&m_wset);
FD_ZERO(&m_rset);
if(!m_bUsingThread)
{
//when we are using threads, this simple server object is only
//paying attention to one socket. Other simple server objects
//will pay attention to other sockets. Listen socket is taken
//care of in the manager.
FD_SET(m_sListen,&m_rset); //select on listening socket
}
for(s = m_listClients.getHead(); s; s = m_listClients.getNext())
{
FD_SET(*s,&m_rset); //select for readability
if(s->getOutBufferSize())
FD_SET(*s,&m_wset); //need to send data out, select for writability
if((int)(*s) > imax)
imax = *s;
}
return imax;
}
//
//main worker function. called directly when in single thread, or
//runs as a thread in multi threaded mode.
//
//PORTING NOTE: On Linux, tv is modified to reflect the amount of
//time not slept; most other implementations do not do this. See help
//for more information
threadReturn
SimpleServer::ThreadHandlerProc(void)
{
struct timeval tv;
int iReady;
StopWatch timer;
idle();
timer.start();
while(running())
{
int iMaxFD = getMaxFD();
calcSleepTime(&tv,m_nIdleFrequency,timer);
iReady = select(iMaxFD+1,&m_rset,NULL /*&m_wset*/,NULL,&tv); //see porting note above for select behaviour
// LOG("elapsed sleep time: %ums",timer.milliseconds());
if(running()) //if we are still running, process errors/messages
{
if(-1 == iReady)
processSelectError();
else if(iReady > 0)
{
if(processSockets(iReady)) //check for incoming data,socket closers,socket errors,new connections
processMessages();
}
if(timer.milliseconds() >= (m_nIdleFrequency-MIN_FREQ) && running()) //20ms error range as a timeout isn't percise
{
timer.start();
idle();
}
}
}
return 0;
}
void
SimpleServer::calcSleepTime(struct timeval* tv,const unsigned32 msecSleepTime,const StopWatch& timer)
{
if(timer.milliseconds() < msecSleepTime)
{
//calculate how much more time we need to sleep
long usecTimer = (msecSleepTime - timer.milliseconds())*1000;
tv->tv_sec = usecTimer/1000000;
tv->tv_usec = usecTimer-tv->tv_sec*1000000;
}
else
{
//reset sleep to full sleep time
tv->tv_sec = msecSleepTime/1000;
tv->tv_usec = (msecSleepTime - tv->tv_sec*1000)*1000;
}
// LOG("sec=%d usec=%d msec=%d",tv->tv_sec,tv->tv_usec,tv->tv_usec/1000);
}
long
SimpleServer::getSleepTime()
{
m_nSleepTime = m_nIdleFrequency * 1000;
return m_nSleepTime;
}
void
SimpleServer::processSelectError()
{
throwSimpleServerException(strerror(errno));
}
//
//
//runs through all messages, calling on processSingleMsg for each msg
//
void
SimpleServer::processMessages()
{
PacketMessage* m;
m = m_que.get();
while(m)
{
processSingleMsg(m);
delete m; //we are finished with the message
m = m_que.get();
}
}
//
//RETURNS true if there are any messages in the msg que (msg's will have been just added)
//
bool
SimpleServer::processSockets(int iReady)
{
//check for a new connection
if(FD_ISSET(m_sListen,&m_rset))
{
acceptConnection();
iReady--;
}
//loop through all sockets or until there are no more ready connections
ServerSocket* s;
PacketMessage* pmsg;
for(s=m_listClients.getHead(); s && iReady; s=m_listClients.getNext())
{
try
{
if(FD_ISSET(*s,&m_rset))
{
s->readData(); //reads into a buffer the serv socket has
PacketBuffer* ppacket;
while((ppacket = s->extractPacket()))
{
pmsg = new PacketMessage(s,ppacket);
m_que.add(pmsg);
}
}
if(s)
{
if(FD_ISSET(*s,&m_wset))
s->sendBuffer();
}
}
catch(SocketInstanceException& e)
{
// There was a socket error, this guy should be shut down.
UNUSED_ALWAYS(e);
queClosedMessage(s);
s->cleanup();
m_listClients.removeCurrent(listPREV);
DELETE_NULL(s);
}
catch(ServerSocketException& e)
{
// An invalid packet was detected, this socket should be shut down.
UNUSED_ALWAYS(e);
queClosedMessage(s);
s->cleanup();
m_listClients.removeCurrent(listPREV);
DELETE_NULL(s);
}
}
return !m_que.isEmpty(); //return false if the que is empty instead of isEmpty()'s true
}
void
SimpleServer::acceptConnection()
{
ServerSocket* pServSocket;
SocketInstance sClient;
SockAddr saClient;
//TODO: put in a try/catch block
m_sListen.accept(sClient,saClient);
pServSocket = new ServerSocket(sClient,saClient,8000,120);
m_listClients.addTail(pServSocket);
PacketBuffer* pktNew = new PacketBuffer(PacketBuffer::pcNewConnection);
PacketMessage* pmsg = new PacketMessage(pServSocket,pktNew);
m_que.add(pmsg);
}
/**
Use this when you want to send a packet to all client connections.
\param ppacket Pointer to the packet you want to send. You are responsible
for deleting the packet when you are finished with it.
\throw SocketInstanceException if the send operation had an error.
**/
void
SimpleServer::send2All(const PacketBuffer* ppacket)
{
ServerSocket* s;
for(s=m_listClients.getHead(); s; s=m_listClients.getNext())
sendPacket(s,ppacket);
}
/**
Use this when you want to send a packet to all client connections.
\param packet Packet you wish to send.
\throw SocketInstanceException if the send operation had an error.
**/
void
SimpleServer::send2All(const PacketBuffer& packet)
{
ServerSocket* s;
for(s=m_listClients.getHead(); s; s=m_listClients.getNext())
{
sendPacket(s,&packet);
}
}
/**
\param s Socket you are sending this packet to.
\param packet Packet containing the data you are sending.
\throw SocketInstanceException if the send operation had an error.
**/
void
SimpleServer::sendPacket(ServerSocket* s,const PacketBuffer& packet)
{
sendPacket(s,&packet);
}
/**
\param s Socket you are sending this packet to.
\param ppacket Packet containing the data you are sending.
\throw SocketInstanceException if the send operation had an error.
**/
void
SimpleServer::sendPacket(ServerSocket* s,const PacketBuffer* ppacket)
{
if(s)
try
{
s->sendPacket(ppacket);
}
catch(SocketInstanceException& e)
{
//there was a send error
UNUSED_ALWAYS(e);
removeSocket(s);
throw e;
}
#ifdef DEBUG
else
{
DLOG("WARNING: ignorning NULL socket");
}
#endif
}
/**
Removes this socket from our client list, deletes the object, and posts a
message to say that this guy is gone. Once this function returns, the socket
passed in can no longer be used. Undefined behavior will occur If you attempt
to use the socket after a call to this function.
You will receive a pcClosed message in a call to processSingleMsg. Included
in the message is the socket number, and dotted decimal IP address that was
attached to the socket. You should no longer use the socket number, or the
socket object.
Typically, you store the socket number in a list somewhere so when a socket
is closed, you will be able to find it and remove it from your user data.
\param psocketRemoving Pointer to the socket object that should be removed.
**/
//
//TODO: make psocket const?
//
void
SimpleServer::removeSocket(ServerSocket* psocketRemoving)
{
//socket was closed, post remove msg to server, and remove from our server
queClosedMessage(psocketRemoving);
ServerSocket* s;
for(s=m_listClients.getHead(); s; s=m_listClients.getNext())
{
m_listClients.removeCurrent(listPREV);
DELETE_NULL(s);
return;
}
}
/**
Creates and posts a PacketBuffer::pcClosed message to the server message
que. The message contains the socket handle, and the ip address in dotted
decimal format. The socket you pass in is the socket that you are
removing. The socket should still be valid (not yet deleted) when this method
is called.
\param s Pointer to the socket that is being removed.
**/
void
SimpleServer::queClosedMessage(ServerSocket* s)
{
PacketBuffer* pktRemove = new PacketBuffer(PacketBuffer::pcClosed);
*pktRemove << (signed32) s->getSocket();
*pktRemove << s->getAddr().dottedDecimal();
pktRemove->rewind();
PacketMessage* pmsg = new PacketMessage(s,pktRemove); //socket is no longer valid
m_que.add(pmsg);
}
//--------------------