Site Administration Page 2 - Design and Architecture |
The first strategy for handling multiple connections that we’ll discuss is multiplexing. Multiplexing is a way of handling multiple clients in a single server process. The application allows clients to connect to the server and adds them to a watch list. This watch list is just an array of socket descriptors. Then the operating system tells the application which clients (if any) need to be serviced or if a new client has established a connection. As an example, think of a restaurant with only one waiter. The waiter is responsible for attending to all the tables at the same time. As customers come in and are seated, the waiter adds them to a mental list of tables to check on. Then, when a table needs attention, he attends to it. Of course, only one table may be serviced at a time, and the possibility exists of a single table using up all the waiter’s time. The select() Functionselect()is a system function that allows us to specify a set of descriptors (sockets, in this case) that we are interested in. It is worth noting thatselect()works with any descriptor, including files, pipes, FIFOs, etc. The system puts our program to sleep, polls the sockets for activity, and wakes the program when an event occurs at one of the sockets. This keeps us from writing a busy loop and wasting clock cycles. Theselect()function prototype looks like this: #include <sys/select.h> The first parameter specifies the highest numbered descriptor (plus 1) to watch in the three sets. It is important to remember that you must add 1 to the highest numbered descriptor in the sets. The reason is that the watch lists are linear arrays of bit values, with 1 bit for every available descriptor in the system. What we are really passing to the function is the number of descriptors in the array that it needs to copy. Since descriptors start at 0, the number we pass is the largest descriptor number plus 1. Next, we provide three descriptor sets. The first set contains descriptors to be watched for read events, the second for write events, and the third for exceptions or error events. Finally, we provide atimevalthat specifies a timeout. If no event occurs in any of the sets before the timeout, thenselect()returns a 0. We can also specify a null pointer for thetimeoutparameter. In this case, the call will not return until an event happens on one of the watched descriptors. Otherwise, it returns the number of descriptors in the three sets. It is important to note thatselect()does modify the descriptor sets that are passed to it. Upon return, the sets will contain only those descriptors that had some activity. To call select multiple times, we must retain a copy of the original sets. Other than a socket error, if any error occurs, then –1 is returned. Four macros are provided to help deal with the descriptor sets. They areFD_CLR,FD_ISSET,FD_SET, andFD_ZERO. Each takes a pointer to a variable typefd_set. Except forFD_ZERO, each takes a descriptor as well. It is important to note that the behavior of these macros is undefined if you pass in a descriptor that is less than zero or greater than FD_SETSIZE. The macros are prototyped as follows:
int sd; /* our socket descriptor */ In this example, the program will wait indefinitely for a read event to occur on the descriptor whose value is specified insd. A Multiplexing ServerIn our example, the server usesselect()to listen for new connections, check for client disconnects, and read events on existing connections. If a read event occurs on the server’s listening socket, then a new connection is initiated and the server callsaccept()to get the new socket descriptor. The new descriptor is then added to the server’s watch set. On the other hand, if a read event occurs on another socket, then the server callsrecvto retrieve any data sent by the client. If no data is received, then the client has disconnected, and the server removes the respective descriptor from the watch set. Otherwise, the data is read and echoed back to the client. Figure 5-1 shows the basic architecture of a multiplexing server.
Here is the program (server1.c) to implement the preceding example: /* server1.c */ Next, we set up the variables that we’ll need. Asselect()modifies the set passed to it, we use two variables: one to maintain our state and another to interact with theselect()function. We need to keep the master set separately: { Then we create the listening socket. This is the socket that will listen for incoming connections from the clients. listensock = socket(AF_INET, SOCK_STREAM, Afterward, we set the socket optionSO_REUSEADDR. While debugging, you’ll be starting and stopping your server often. Linux tends to keep the address and port that was used by your program reserved. This option allows you to avoid the dreaded “address in use” error. val = 1; Here, we bind the socket to the listening port. We use the special addressINADDR_ANYto specify that we’ll listen on all IP addresses associated with the server: sAddr.sin_family = AF_INET; We put the socket into “listen” mode so that we can accept incoming connections: result = listen(listensock, 5); We initialize our descriptor set usingFD_ZERO. Then we add the listening socket to the set so that the system will notify us when a client wishes to connect. Connection requests are treated as read events on the listening socket: FD_ZERO(&readset); Notice that we assign our descriptor set to an alternate variable to be passed to theselect()function. As noted previously, this is becauseselect()will alter the set we pass, so that upon return, only those sockets with activity are flagged in the set. Our call toselect()signifies that we are interested only in read events. In a real-world application, we would need to be concerned with errors and possibly write events. We loop through the entire set of descriptors.FD_SETSIZEis a constant set in the kernel and is usually 1024. A more efficient server implementation would keep track of the highest numbered descriptor and not loop through the entire set.FD_ISSETis used to determine if the descriptor is flagged as having activity. It returns a nonzero value if the supplied descriptor is set as having had activity; otherwise, it returns 0. while (1){ If the activity is on the listening socket, then we accept the new connection and add its socket to our watch set. Otherwise, we read characters from the client. If the number of characters read is less than or equal to zero, then the client is assumed to have closed the connection. We close the connection on our side and remove the descriptor from our watch list. Otherwise, we echo the characters to the screen and back to the client. if (x == listensock){ The server can be compiled with a command similar to the example client. Figure 5-2 shows a sample of the output obtained on executing the program.
Notice that, for a brief time, all five clients are connected at the same time.
blog comments powered by Disqus |
|
|
|
|
|
|
|