www.unix.org.ua/orelly/perl/advprog/ch12_03.htm
The real world is ugly, and you have to resol ve this issue. There are three ways of doing this: 1 Create multiple threads of control (processes or threads) and have ea ch call block in its own thread. We'll call this the "select" approach, because we use the select call to ensure that a socket has something to offer.
As we shall see, option 2 should be used in conjunction with option 3 in production systems. In all cases, the client code remains unaffected whi le we try out these options. Some systems support an asynchron ous I/O notification: a SIGIO signal is sent to the process if a specifi ed socket is ready to do I/O. We will not pay attention to this approach because there is no way for a signal handler to know which socket is re ady for reading or writing.
The server process acts as a full-time reception ist: it blocks on accept, and when a connection request comes in, it spa wns a child process and goes back to accept. The newly created child pro cess meanwhile has a copy of its parent's environment and shares all ope n file descriptors. Hence it is able to read from, and write to, the new socket returned by accept. When the child is done with the conversation , it simply exits. Each process is therefore dedicated to its own task a nd doesn't interfere with the other.
Malcolm Beattie has a working prototype of a threaded Perl interprete r, which will be incorporated into the mainstream in the Perl 5005 rele ase. if ($pid == 0) { # Child process while (defined ($buf = <$new_sock>)) { # do something with $buf .... The parent gets a posit ive return value, the process ID ($pid) of the child process. Both proce sses check this return value and execute their own logic; the main proce ss goes back to accept, and the child process reads a line from the sock et and echoes it back to the client. Incidentally, the CHLD signal has nothing to do with IPC per se. On Unix, when a child process exits (or terminates abnormally), the system gets rid of the memory, files, and other resources associated with it. But it retains a small amount of information (the exit status if the child was able to execute exit(), or a termination status otherwise), just in cas e the parent uses wait or waitpid to enquire about this status. The term inated child process is also known as a zombie process, and it is always a good thing to remove it using wait; otherwise, the process tables kee p filling up with junk. In the preceding code, wait doesn't block, becau se it is called only when we know for sure that a child process has died - the CHLD signal arranges that for us. Be sure to read the online docu mentation for quirks associated with signals in general and SIGCHLD in p articular. We can instead use the selec t call, introduced in BSD Unix, that returns control when a socket (any filehandle, in fact) can be read from or written to. This approach allow s us to use a single-threaded process - somewhat akin to firing the rece ptionist and handling all the incoming calls and conversations ourselves . The interface to the native select call is not very pretty, so we use the IO::Select wrapper module instead. The select met hod (which calls Perl's native select function) accepts three sets of fi lehandles, or IO::Select objects, which are monitored for readability, w ritability, and error conditions, respectively. In the preceding snippet of code, we create two such sets - a filehandle can be added to any or all of these sets if you so wish - and supply them to the select method as follows: ($r_ready, $w_ready, $error) = IO::Select->select($read_set, $write_set, $error_set, $timeout); select blocks until an interesting event occurs (one or more filehandles are ready for reading, writing, or reporting an error condition) or the time-out interval has elapsed. At this point, it creates three separate lists of ready filehandles and returns references to them. The time-out is in seconds but can be expressed as a floating-point number to get mil lisecond resolution. Let us use this information to implement a program that retrieves message s from one or more clients: # Create main socket ($main_socket) as before ... foreach $sock (@$new_readable) { if ($sock == $main_socket) { $new_sock = $sock->accept(); We do the same here, and remove # it from the readable_handles list $readable_handles->remove($sock); We then add this socket to a newly created IO::Selec t collection object. When select returns the first time, $main_socket ha s something to read from (or has an error, a possibility that we ignore for the moment); in other words, it has received a connection request an d is guaranteed not to block when accept is called. Now, we are not inte rested in being blocked if the socket returned from accept has nothing t o say, so we add it to the list of filehandles being monitored for reada bility. When select returns the next time, we know that one of the two s ockets is ready for reading (or both are ready). If $main_socket is read y, we repeat the exercise above. select also returns if one or more remote sockets are closed. The corresp onding sockets on the listening end return 0 when any of the I/O operato rs are used (0 bytes read or written). The server above removes these so ckets from the IO::Select collections to prevent from select returning t he same defunct sockets every time. Unfortunately, we still don't know how much data h as accumulated in the I/O buffers (for purposes of reading) or how much can be written to it (the other side may be reading slowly, and there's a limit to how much you can pump in from this side). Both sysread and sy swrite return the number of bytes actually read or written, so you would have to invoke them in a loop until the entire message is read or writt en. Once you have drained the buffers (or filled them, as the case may b e), there is the very real possibility that it might block the next time you attempt to read or write if the other side doesn't do something qui ck. One option is to invoke select in every iteration of the loop and pr oceed only if it confirms the socket's availability. This slows you down when the filehandle can accommodate your read or write requests. Beside s, you have to quit the loop anyway when select tells you that a filehan dle isn't ready and make the attempt later on when the file descriptor c hanges state. For single-threaded programs the next option is to make the filehandle no n-blocking. The fcntl function takes a command like F_SETFL ( "set flag") and an argument that is specific to that command. Depending on the operating system, the flag to set nonblocking I/O may also be kno wn as O_NDELAY or FNDELAY. In any case, once this operation is carried out, sysread and syswrite ret urn undef (not 0) and set $! to EAGAIN (or EWOULDBLOCK on BSD 43) if th ey cannot carry out the operation right away. The following code account s for these return and error values when reading a socket: # Want to read 1024 bytes $bytes_to_read = 1024; Here we choose to # spin around waiting for something to read. This approach is a constant drain on the CPU because the process is never idle.
"Marked by inactivity or repose," as Webster's Tenth Collegiate Dicti onary puts it. You might have noticed that we have ignored clients in all these discussi on. If a client is willing to block, there is no issue at all, since, un like the server side, it is not talking to more than one entity. But if it contains a GUI, it clearly cannot afford to block, and we have much t he same problem.
In a system in which there is no clear delineation between "clients" and "servers" - a cluster of bank compute rs is an example of such a peer-to-peer system - every process is modele d on the more general server framework described in the preceding pages. You can now see that all three approaches to creating servers have their individual quirks and failings. The next section introduces us to techni ques and strategies used in typical production servers.
|