The event-driven receptionist allows you to handle multiple calls at once, without using concurrent processing. It does this by mediating all communication between clients and worker callbacks.
Here's the public interface to the event-driven receptionist.
class EventDrivenReceptionist { public: typedef void (*Callback)( int id, const RWCString& input, EventDrivenReceptionist& me, void *clientData); //1
EventDrivenReceptionist(const RWSockAddrBase& addr, Callback fn, void *clientData=0); //2 void send(int id, const RWCString&); //3 void close(int id); //4 void processPacket(); //5 };
//1 | This is a typedef of the callback function signature. The callback function signature is a little different than for the sequential server. In the sequential server case, the callback function was passed back a net library portal object directly to use for communication. Since this receptionist insists on mediating all communication, this won't do here. Also, the event-driven receptionist does not call the worker for each incoming connection, but rather for each chunk of input received from a client.
The callback function receives an integer identifier which uniquely identifies the client sending the input, the input itself, a reference to the receptionist, and its client data. As we'll see, the reference to the receptionist together with the client identifier can be used to tell the receptionist to communicate with the client. |
//2 | The constructor for the event-driven receptionist is exactly like the sequential receptionist constructor. |
//3 | This function is used by the worker to send data to a client. The implementation does not communicate directly with the client (this might block, and a fundamental principal of this receptionist is that workers do not block. Instead, the receptionist maintains a buffer of data waiting to be sent to each client, and send() adds the data to the appropriate buffer. |
//4 | When the worker is ready to hang up, it calls close() on the receptionist. As with send(), the action is not carried out immediately. Instead, this connection is added to the list of connections ready to be closed. The connection will actually be closed only after all pending output has been sent. |
//5 | This call causes the receptionist to handle a single communication call on each portal which is ready. If no new connections have arrived, none of the currently active input lines have available input, and none of the output lines with data pending are ready for writing, then processPacket() blocks until something can be done.
This function is the heart of the event-driven receptionist. It's implementation uses RWSocketAttributes and the net library global function rwSocketSelect() to detect which communication channels are ready. |
The greeting callback for the event-driven server is more complicated than for the iterative server. The complexity arises because the callback must be able to handle multiple clients at the same time. To do this, the callback maintains a dictionary of input buffers for each connection it is currently handling. If new input arrives and does not contain a newline, then we don't have the complete salutation for this client, so the input is appended to the client's input buffer. If the input does contain a newline, then we send the previous greeting and shut down the connection. Here's the code.
void greet(int id, const RWCString& input, EventDrivenReceptionist& receptionist, void*) { static RWCString greeting = "It's my first time. honest."; static RWTValSlistDictionary<int,RWCString> inputBuffers; //1 size_t pos = input.index(RWCRegexp("[\r\n]")); //2 if (pos!=RW_NPOS || input.isNull()) { //3 RWCString newGreeting; if (pos!=RW_NPOS) newGreeting = input(0,pos); //4 if (inputBuffers.contains(id)) { //5 newGreeting.prepend(inputBuffers[id]); inputBuffers.remove(id); } if (!newGreeting.isNull()) { receptionist.send(id,greeting); //6 greeting = newGreeting; } receptionist.close(id); //7 } else { inputBuffers[id] += input; //8 } }
//1 | This is the mapping from client identifiers to unprocessed input from that client. Without this saving of old input, we could not handle the case of multiple clients, where each salutation was not always delivered in a single packet. The dictionary is declared using static storage so that it persists from one call of the callback to the next. |
//2 | Search the input for either a newline character or a line feed. Although the greeting protocol calls for a newline, we want to make the server more robust by allowing salutations that terminate with a carriage return/ linefeed pair. |
//3 | Test if we are in shape to respond to the client. We are ready to respond if either we found the end of the salutation, or if the input is null. Null input indicates that the client has closed its writing side of the connection. |
//4 | Chop off the rest of the input past the salutation. |
//5 | If there has been previous input queued up for this client, we merge the previous input with the string newGreeting and clean up the buffer. |
//6 | Greet the client. Of course, we don't communicate with the client directly. Instead, go through the receptionist. |
//7 | Close the connection, since we are done. As with sending data to the client, we close the connection indirectly via the receptionist. |
//8 | If we have not yet received the complete greeting, then append this input to an input buffer. |
The event-driven greeting mainline is nearly identical to the sequential greeting mainline.
int main() { try { RWWinSockInfo info; RWInetAddr addr(3010); //1 EventDrivenReceptionist receptionist(addr,greet); //2 for(;;) { //3 try { receptionist.processPacket(); //4 } catch(const RWxmsg& x) { //5 cout << "Problem with packet: " << 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 packet. |
//5 | Problems with a single packet are handled by an inner try block and caught here. After an error message is printed, the server goes on and processes other packets. This improves the robustness of the server. |
©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.