blob: 8e16a0442ccf681eee2b63527e993c0da0c531c6 [file] [log] [blame]
Emeric Vigier2f625822012-08-06 11:09:52 -04001//
2// tcpservice.cpp
3//
4// Copyright 2000 - Gianni Mariani <gianni@mariani.ws>
5//
6// An example of a simple chatty server using CommonC++.
7//
8// This simple application basically operates as a
9// very simple chat system. From a telnet session
10// on localhost:3999 , any messages typed from a telnet
11// client are written to all participating sessions.
12//
13// This is free software licensed under the terms of the GNU
14// Public License
15//
16// This example:
17//
18// This demostrates a simple threaded server, actually,
19// the sessions are not all threaded though they could be
20// if that's what you wanted. Basically it demonstrates the
21// use of SocketService, SocketPorts and Threads.
22//
23// For those familiar with Unix network programming, SocketService
24// basically encapsulates all the work to communicate with
25// the select() or poll() system calls. SocketPorts are
26// basically encapsulations of sessions or open file descriptors.
27//
28// Anyhow, this example is a very simple echo server but
29// it echos to all connected clients. So it's a poor man's
30// IRC ! You connect via telnet to localhost port 3999 and
31// it will echo to all other connected clients what you type in !
32//
33
34#include <cc++/socketport.h>
35
36#include <iostream>
37
38// For starters, we need a thread safe list, we'll make one
39// out of the STL list<> template -
40// http://www.sgi.com/Technology/STL/index.html
41//
42// Thread safe list class
43//
44#include <list>
45
46#ifdef CCXX_NAMESPACES
47using namespace std;
48using namespace ost;
49#endif
50
51class ts_list_item;
52typedef list<ts_list_item *> ts_list;
53
54// a list head - containing a list and a Mutex.
55// It would be really nice to teach stl to do this.
56
57class ts_list_head {
58public:
59
60 // No point inheriting, I'd have to implement
61 // alot of code. We'll hold off on that exercise.
62
63 // Using the CommonC++ Mutex class.
64 Mutex linkmutex;
65 // And the STL template.
66 ts_list list_o_items;
67
68 // Not nessasary, but nice to be explicit.
69 ts_list_head()
70 : linkmutex(), list_o_items() {
71 }
72
73 // This thing knows how to remove and insert items.
74 void RemoveListItem( ts_list_item * li );
75 void InsertListItem( ts_list_item * li );
76
77 // And it knows how to notify that it became empty
78 // or an element was deleted and it was the last one.
79 virtual void ListDepleted() {
80 }
81
82 virtual ~ts_list_head() {
83 }
84};
85
86
87// This item knows how to remove itself from the
88// list it belongs to.
89class ts_list_item {
90public:
91 ts_list::iterator linkpoint;
92 ts_list_head * listhead;
93
94 virtual ~ts_list_item() {
95 listhead->RemoveListItem( this );
96 }
97
98 ts_list_item( ts_list_head * head ) {
99 listhead = head;
100 head->InsertListItem( this );
101 }
102};
103
104void ts_list_head::RemoveListItem( ts_list_item * li )
105{
106 bool is_empty;
107 linkmutex.enterMutex();
108 list_o_items.erase( li->linkpoint );
109 is_empty = list_o_items.empty();
110 linkmutex.leaveMutex();
111
112 // There is a slim possibility that at this time
113 // we recieve a connection.
114 if ( is_empty ) {
115 ListDepleted();
116 }
117}
118
119void ts_list_head::InsertListItem( ts_list_item * li )
120{
121 linkmutex.enterMutex();
122 list_o_items.push_front( li );
123 li->linkpoint = list_o_items.begin();
124 linkmutex.leaveMutex();
125}
126
127// ChatterSession operates on the individual connections
128// from clients as are managed by the SocketService
129// contained in CCExec. ChatterThread simply waits in
130// a loop to create these, listening forever.
131//
132// Even though the SocketService contains a list of
133// clients it serves, it may actually serve more than
134// one type of session so we create our own list by
135// inheriting the ts_list_item.
136//
137
138class ChatterSession :
139 public virtual SocketPort,
140 public virtual ts_list_item {
141public:
142
143 enum { size_o_buf = 2048 };
144
145 // Nothing special to do here, it's all handled
146 // by SocketPort and ts_list_item
147
148 virtual ~ChatterSession() {
149 cerr << "ChatterSession deleted !\n";
150 }
151
152 // When you create a ChatterSession it waits to accept a
153 // connection. This is done by it's own
154 ChatterSession(
155 TCPSocket & server,
156 SocketService * svc,
157 ts_list_head * head
158 ) :
159 SocketPort( NULL, server ),
160 ts_list_item( head ) {
161 cerr << "ChatterSession Created\n";
162
163 tpport_t port;
164 InetHostAddress ia = getPeer( & port );
165
166 cerr << "connecting from " << ia.getHostname() <<
167 ":" << port << endl;
168
169 // Set up non-blocking reads
170 setCompletion( false );
171
172 // Set yerself to time out in 10 seconds
173 setTimer( 100000 );
174 attach(svc);
175 }
176
177 //
178 // This is called by the SocketService thread when it the
179 // object has expired.
180 //
181
182 virtual void expired() {
183 // Get outa here - this guy is a LOOSER - type or terminate
184 cerr << "ChatterSession Expired\n";
185 delete this;
186 }
187
188 //
189 // This is called by the SocketService thread when it detects
190 // that there is somthing to read on this connection.
191 //
192
193 virtual void pending() {
194 // Implement the echo
195
196 cerr << "Pending called\n";
197
198 // reset the timer
199 setTimer( 100000 );
200 try {
201 int len;
202 unsigned int total = 0;
203 char buf[ size_o_buf ];
204
205 while ( (len = receive(buf, sizeof(buf) )) > 0 ) {
206 total += len;
207 cerr << "Read '";
208 cerr.write( buf, len );
209 cerr << "'\n";
210
211 // Send it to all the sessions.
212 // We probably don't really want to lock the
213 // entire list for the entire time.
214 // The best way to do this would be to place the
215 // message somewhere and use the service function.
216 // But what are examples for ?
217
218 bool sent = false;
219 listhead->linkmutex.enterMutex();
220 for (
221 ts_list::iterator iter = listhead->list_o_items.begin();
222 iter != listhead->list_o_items.end();
223 iter ++
224 ) {
225 ChatterSession * sess =
226 dynamic_cast< ChatterSession * >( * iter );
227 if ( sess != this ) {
228 sess->send( buf, len );
229 sent = true;
230 }
231 }
232 listhead->linkmutex.leaveMutex();
233
234 if ( ! sent ) {
235 send(
236 ( void * ) "No one else listening\n",
237 sizeof( "No one else listening\n" ) - 1
238 );
239
240 send( buf, len );
241 }
242 }
243 if (total == 0) {
244 cerr << "Broken connection!\n" << endl;
245 delete this;
246 }
247 }
248 catch ( ... ) {
249 // somthing wrong happened here !
250 cerr << "Socket port write sent an exception !\n";
251 }
252
253 }
254
255 virtual void disconnect() {
256 // Called by the SocketService thread when the client
257 // hangs up.
258 cerr << "ChatterSession disconnected!\n";
259
260 delete this;
261 }
262
263};
264
265class ChatterThread;
266
267//
268// This is the main application object containing all the
269// state for the application. It uses a SocketService object
270// (and thread) to do all the work, however, that object could
271// theoretically be use by more than one main application.
272//
273// It creates a ChatterThread to sit and wait for connections
274// from clients.
275
276class CCExec : public virtual ts_list_head {
277public:
278
279 SocketService * service;
280 ChatterThread * my_Chatter;
281 Semaphore mainsem[1];
282
283 CCExec():my_Chatter(NULL) {
284 service = new SocketService( 0 );
285 }
286
287 virtual void ListDepleted();
288
289 // These methods defined later.
290 virtual ~CCExec();
291 int RunApp( char * hn = (char *)"localhost" );
292
293};
294
295//
296// ChatterThread simply creates ChatterSession all the time until
297// it has an error. I suspect you could create as many of these
298// as the OS could take.
299//
300
301class ChatterThread : public virtual TCPSocket, public virtual Thread {
302public:
303
304 CCExec * exec;
305
306 void run () {
307 while ( 1 ) {
308 try {
309 // new does all the work to accept a new connection
310 // attach itself to the SocketService AND include
311 // itself in the CCExec list of sessions.
312 new ChatterSession(
313 * ( TCPSocket * ) this,
314 exec->service,
315 exec
316 );
317 }
318 catch ( ... ) {
319 // Bummer - there was an error.
320 cerr << "ChatterSession create failed\n";
321 exit();
322 }
323 }
324 }
325
326 ChatterThread(
327 InetHostAddress & machine,
328 int port,
329 CCExec * inexec
330
331 ) : TCPSocket( machine, port ),
332 Thread(),
333 exec( inexec ) {
334 start();
335 }
336
337
338};
339
340//
341// Bug here, this should go ahead and shut down all sessions
342// for application. An exercise left to the reader.
343
344CCExec::~CCExec()
345{
346 // MUST delete my_Chatter first or it may end up using
347 // a deleted service.
348 if ( my_Chatter ) delete my_Chatter;
349
350 // Delete whatever is left.
351 delete service;
352}
353
354//
355// Run App would normally read some config file or take some
356// parameters about which port to connect to and then
357// do that !
358int CCExec::RunApp( char * hn )
359{
360 // which port ?
361
362 InetHostAddress machine( hn );
363
364 if ( machine.isInetAddress() == false ) {
365 cerr << "machine is not address" << endl;
366 }
367
368 cerr << "machine is " << machine.getHostname() << endl;
369
370 // Start accepting connections - this will bind to the
371 // port as well.
372 try {
373 my_Chatter = new ChatterThread(
374 machine,
375 3999,
376 this
377 );
378 }
379 catch ( ... ) {
380 cerr << "Failed to bind\n";
381 return false;
382 }
383
384 return true;
385}
386
387// When there is no one else connected - terminate !
388void CCExec::ListDepleted()
389{
390 mainsem->post();
391}
392
393
394int main( int argc, char ** argv )
395{
396 CCExec * server;
397
398 server = new CCExec();
399
400 // take the first command line option as a hostname
401 // to listen to.
402 if ( argc > 1 ) {
403 server->RunApp( argv[ 1 ] );
404 } else {
405 server->RunApp();
406 }
407
408 server->mainsem->wait();
409
410 delete server;
411
412 return 0;
413}