After reading the code we've just described you may be asking yourself "Where's the receptionist?" Good question. The code that belongs in the receptionist has been incorporated right into the body of the program. This works fine for this code because the program is so simple and because the receptionist doesn't deal with much state. For more complicated receptionists, though, this situation is not acceptable. In this section, we'll pull the receptionist code out of main() and create a sequential receptionist class.
Usually, you use a C++ object by calling member functions on the object. The member function runs, and when it is finished control returns to you.
Using a receptionist object is a little different. Rather than you calling the receptionist, you want the receptionist to call you, when a call is waiting. To arrange this, you first have to tell the receptionist which routine to call when a connection arrives. Since you are asking the receptionist to call you back, the function that the receptionist should call is a callback. Telling the receptionist which callback to use is called registering the callback.
In this tutorial, callbacks are global functions. They take parameters indicating the state being passed by the receptionist, plus one extra parameter, the client data. This is a void* pointer which you provide to the receptionist when registering the callback. You can use this pointer to pass data the worker needs to know about. For example, if the worker is itself an object, then you can pass the address of the worker object to a global callback function as client data, and have the callback function call the appropriate member function of the worker object.
The callback and client data strategy for handling callbacks is used in this tutorial because it is the most straightforward approach. For a very interesting discussion of callbacks, and an awesome C++ approach to callbacks, check out Rich Hickey's article "Callbacks in C++ Using Template Functions" in the February 1995 issue of the C++ Report.
At long last, we are ready to present the sequential receptionist class. Here's the class declaration.
class SequentialReceptionist { public: SequentialReceptionist(const RWSockAddrBase& addr, void (*callback)(RWPortal portal, void *clientData), void *clientData = 0); //1 void answer(); //2 private: RWSocketListener phone_; //3 void (*callback_)(RWPortal portal, void *clientData); void *clientData_; };
//1 | The constructor requires an address, a callback function, and client data. The constructor sets up phone_ (declared on line //3) to watch for calls arriving at this address. |
//2 | The answer() member function waits for the next incoming connection, and then responds to it using the callback function. After one call is handled, answer() returns. To continue taking calls, call answer() repeatedly. |
Here's how SequentialReceptionist is implemented:
SequentialReceptionist::SequentialReceptionist( const RWSockAddrBase& addr, void (*f)(RWPortal portal,void*), void *clientData) : phone_(addr), callback_(f), clientData_(clientData) { } //1 void SequentialReceptionist::answer() { RWPortal sock = phone_(); //2 (*callback_)(sock,clientData_); //3 }
//1 | The constructor simply initializes its pieces of member data. The initialization of phone_ sets up for reception of incoming calls. |
//2 | The first step in the implementation of answer() is to get the next incoming connection. This is done here. |
//3 | Once we have an incoming connection, the semantics of the sequential receptionist are to pass the connection to a worker for handling. This is done by invoking the callback function. |
To use the receptionist, we need to set up a callback function. Here's a callback that implements the greeting protocol.
void greet(RWPortal portal, void *) { static RWCString greeting = "It's my first time. honest."; // Read in the greeting from the portal RWPortalIStream strm(portal); RWCString newGreeting; newGreeting.readLine(strm); // Send the previous greeting portal.sendAtLeast(greeting); // Remember the new greeting greeting = newGreeting; }
All we need to do now is build a receptionist object, register the callback, and let it go. Here's the mainline code.
int main() { try { RWWinSockInfo info; RWInetAddr addr(3010); //1 SequentialReceptionist receptionist(addr,greet); //2 for(;;) { //3 try { receptionist.answer(); //4 } catch(const RWxmsg& x) { //5 cout << "Problem with call: " << x.why() << endl; } } } catch(const RWxmsg& x) { cout << "Exception: " << x.why() << endl; } return 0; }
//1 | Construct the address on which to wait for connections. This hardwires the address to TCP port 3010. |
//2 | Build the receptionist object. The callback function is registered at construction time. |
//3 | The application runs in an infinite loop, answering one call after another. |
//4 | Handle a single call. |
//5 | Problems with a call are handled by an inner try block and caught here. After an error message is printed, the server goes on and answers the next incoming call. Without the nested try block, any error would cause the entire server application to end. This is not what we want if the error is something like a client resetting the connection. |
©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.