\section{tcpservice.cpp} \footnotesize\begin{verbatim}1 // 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 47 using namespace std; 48 using namespace ost; 49 #endif 50 51 class ts_list_item; 52 typedef 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 57 class ts_list_head { 58 public: 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 74 // This thing knows how to remove and insert items. 75 void RemoveListItem( ts_list_item * li ); 76 void InsertListItem( ts_list_item * li ); 77 78 // And it knows how to notify that it became empty 79 // or an element was deleted and it was the last one. 80 virtual void ListDepleted() 81 { 82 } 83 84 virtual ~ts_list_head() 85 { 86 } 87 }; 88 89 90 // This item knows how to remove itself from the 91 // list it belongs to. 92 class ts_list_item { 93 public: 94 ts_list::iterator linkpoint; 95 ts_list_head * listhead; 96 97 virtual ~ts_list_item() 98 { 99 listhead->RemoveListItem( this ); 100 } 101 102 ts_list_item( ts_list_head * head ) 103 { 104 listhead = head; 105 head->InsertListItem( this ); 106 } 107 }; 108 109 void ts_list_head::RemoveListItem( ts_list_item * li ) 110 { 111 bool is_empty; 112 linkmutex.enterMutex(); 113 list_o_items.erase( li->linkpoint ); 114 is_empty = list_o_items.empty(); 115 linkmutex.leaveMutex(); 116 117 // There is a slim possibility that at this time 118 // we recieve a connection. 119 if ( is_empty ) { 120 ListDepleted(); 121 } 122 } 123 124 void ts_list_head::InsertListItem( ts_list_item * li ) 125 { 126 linkmutex.enterMutex(); 127 list_o_items.push_front( li ); 128 li->linkpoint = list_o_items.begin(); 129 linkmutex.leaveMutex(); 130 } 131 132 // ChatterSession operates on the individual connections 133 // from clients as are managed by the SocketService 134 // contained in CCExec. ChatterThread simply waits in 135 // a loop to create these, listening forever. 136 // 137 // Even though the SocketService contains a list of 138 // clients it serves, it may actually serve more than 139 // one type of session so we create our own list by 140 // inheriting the ts_list_item. 141 // 142 143 class ChatterSession : 144 public virtual SocketPort, 145 public virtual ts_list_item 146 { 147 public: 148 149 enum { size_o_buf = 2048 }; 150 151 // Nothing special to do here, it's all handled 152 // by SocketPort and ts_list_item 153 154 virtual ~ChatterSession() 155 { 156 cerr << "ChatterSession deleted !\n"; 157 } 158 159 // When you create a ChatterSession it waits to accept a 160 // connection. This is done by it's own 161 ChatterSession( 162 TCPSocket & server, 163 SocketService * svc, 164 ts_list_head * head 165 ) : 166 SocketPort( NULL, server ), 167 ts_list_item( head ) 168 { 169 cerr << "ChatterSession Created\n"; 170 171 tpport_t port; 172 InetHostAddress ia = getPeer( & port ); 173 174 cerr << "connecting from " << ia.getHostname() << 175 ":" << port << endl; 176 177 // Set up non-blocking reads 178 setCompletion( false ); 179 180 // Set yerself to time out in 10 seconds 181 setTimer( 100000 ); 182 attach(svc); 183 } 184 185 // 186 // This is called by the SocketService thread when it the 187 // object has expired. 188 // 189 190 virtual void expired() 191 { 192 // Get outa here - this guy is a LOOSER - type or terminate 193 cerr << "ChatterSession Expired\n"; 194 delete this; 195 } 196 197 // 198 // This is called by the SocketService thread when it detects 199 // that there is somthing to read on this connection. 200 // 201 202 virtual void pending() 203 { 204 // Implement the echo 205 206 cerr << "Pending called\n"; 207 208 // reset the timer 209 setTimer( 100000 ); 210 try { 211 int len; 212 unsigned int total = 0; 213 char buf[ size_o_buf ]; 214 215 while ( (len = receive(buf, sizeof(buf) )) > 0 ) { 216 total += len; 217 cerr << "Read '"; 218 cerr.write( buf, len ); 219 cerr << "'\n"; 220 221 // Send it to all the sessions. 222 // We probably don't really want to lock the 223 // entire list for the entire time. 224 // The best way to do this would be to place the 225 // message somewhere and use the service function. 226 // But what are examples for ? 227 228 bool sent = false; 229 listhead->linkmutex.enterMutex(); 230 for ( 231 ts_list::iterator iter = listhead->list_o_items.begin(); 232 iter != listhead->list_o_items.end(); 233 iter ++ 234 ) { 235 ChatterSession * sess = 236 dynamic_cast< ChatterSession * >( * iter ); 237 if ( sess != this ) { 238 sess->send( buf, len ); 239 sent = true; 240 } 241 } 242 listhead->linkmutex.leaveMutex(); 243 244 if ( ! sent ) { 245 send( 246 ( void * ) "No one else listening\n", 247 sizeof( "No one else listening\n" ) - 1 248 ); 249 250 send( buf, len ); 251 } 252 } 253 if (total == 0) 254 { 255 cerr << "Broken connection!\n" << endl; 256 delete this; 257 } 258 } 259 catch ( ... ) 260 { 261 // somthing wrong happened here ! 262 cerr << "Socket port write sent an exception !\n"; 263 } 264 265 } 266 267 virtual void disconnect() 268 { 269 // Called by the SocketService thread when the client 270 // hangs up. 271 cerr << "ChatterSession disconnected!\n"; 272 273 delete this; 274 } 275 276 }; 277 278 class ChatterThread; 279 280 // 281 // This is the main application object containing all the 282 // state for the application. It uses a SocketService object 283 // (and thread) to do all the work, however, that object could 284 // theoretically be use by more than one main application. 285 // 286 // It creates a ChatterThread to sit and wait for connections 287 // from clients. 288 289 class CCExec : public virtual ts_list_head { 290 public: 291 292 SocketService * service; 293 ChatterThread * my_Chatter; 294 Semaphore mainsem[1]; 295 296 CCExec():my_Chatter(NULL) 297 { 298 service = new SocketService( 0 ); 299 } 300 301 virtual void ListDepleted(); 302 303 // These methods defined later. 304 virtual ~CCExec(); 305 int RunApp( char * hn = "localhost" ); 306 307 }; 308 309 // 310 // ChatterThread simply creates ChatterSession all the time until 311 // it has an error. I suspect you could create as many of these 312 // as the OS could take. 313 // 314 315 class ChatterThread : public virtual TCPSocket, public virtual Thread { 316 public: 317 318 CCExec * exec; 319 320 void run () 321 { 322 while ( 1 ) { 323 try { 324 // new does all the work to accept a new connection 325 // attach itself to the SocketService AND include 326 // itself in the CCExec list of sessions. 327 new ChatterSession( 328 * ( TCPSocket * ) this, 329 exec->service, 330 exec 331 ); 332 } 333 catch ( ... ) 334 { 335 // Bummer - there was an error. 336 cerr << "ChatterSession create failed\n"; 337 exit(); 338 } 339 } 340 } 341 342 ChatterThread( 343 InetHostAddress & machine, 344 int port, 345 CCExec * inexec 346 347 ) : TCPSocket( machine, port ), 348 Thread(), 349 exec( inexec ) 350 { 351 start(); 352 } 353 354 355 }; 356 357 // 358 // Bug here, this should go ahead and shut down all sessions 359 // for application. An exercise left to the reader. 360 361 CCExec::~CCExec() 362 { 363 // MUST delete my_Chatter first or it may end up using 364 // a deleted service. 365 if ( my_Chatter ) delete my_Chatter; 366 367 // Delete whatever is left. 368 delete service; 369 } 370 371 // 372 // Run App would normally read some config file or take some 373 // parameters about which port to connect to and then 374 // do that ! 375 int CCExec::RunApp( char * hn ) 376 { 377 // which port ? 378 379 InetHostAddress machine( hn ); 380 381 if ( machine.isInetAddress() == false ) { 382 cerr << "machine is not address" << endl; 383 } 384 385 cerr << "machine is " << machine.getHostname() << endl; 386 387 // Start accepting connections - this will bind to the 388 // port as well. 389 try { 390 my_Chatter = new ChatterThread( 391 machine, 392 3999, 393 this 394 ); 395 } 396 catch ( ... ) 397 { 398 cerr << "Failed to bind\n"; 399 return false; 400 } 401 402 return true; 403 } 404 405 // When there is no one else connected - terminate ! 406 void CCExec::ListDepleted() 407 { 408 mainsem->post(); 409 } 410 411 412 int main( int argc, char ** argv ) 413 { 414 CCExec * server; 415 416 server = new CCExec(); 417 418 // take the first command line option as a hostname 419 // to listen to. 420 if ( argc > 1 ) { 421 server->RunApp( argv[ 1 ] ); 422 } else { 423 server->RunApp(); 424 } 425 426 server->mainsem->wait(); 427 428 delete server; 429 430 return 0; 431 } \end{verbatim} \normalsize