Network Programming with Perl


 
Network Programming with Perl

By Lincoln  D.  Stein

Slots : 1

Table of Contents
Chapter  10.   Forking Servers and the inetd Daemon

    Content

A Client Script for the Psychotherapist Server

Before we push onward with our discussion of forking servers, let's write a client that we can use to talk to the psychotherapist server. After all, we shouldn't have to be stuck with musty old telnet when we can use Perl! Seriously, though, this script illustrates the usefulness of sysread () and syswrite() for working with unbuffered byte streams.

At first, the gab2.pl script developed in Chapter 5 (Figure 5.8) would seem to fit the bill for the client side of the equation. But there's a problem. gab2.pl was designed for line-oriented communications, in which the server transmits complete lines terminated in CRLF. The psychotherapist server, however, is not entirely line oriented. For one thing, it terminates its lines with what Chatbot::Eliza thinks is appropriate (which happens to be the logical newline " \n " character). For another, the "you:" prompt that the server transmits after each utterance does not end with a newline. The combined effect of these problems is that when we point gab2.pl at the psychotherapist server's port, we see no output.

What we need is a more general bytestream-oriented client that reads and writes its data in arbitrary chunks as they become ready, rather than waiting for complete lines. As it turns out, very few modifications to gab2.pl are needed to turn it into this type of client.

Figure 10.4 shows the revised script, gab3.pl . The significant changes are in the user_to_host() and host_to_user() subroutines. Instead of the line-oriented read and write calls of the earlier version, these subroutines now consist of tight loops using sysread() and syswrite() . For example, here is the code fragment from host-to- user () that reads from the socket and writes to STDOUT :

Figure 10.4. A bytestream-oriented gab client

syswrite(STDOUT,$data) while sysread($s,$data,BUFSIZE);

Similarly, the user_to_host() subroutine, which is responsible for copying user data to the socket, is modified to look like this:

syswrite($s,$data) while sysread(STDIN,$data,BUFSIZE)

The BUFSIZE parameter is relatively arbitrary. For performance it should be roughly as large as the largest chunk of text that can be emitted by the psychotherapist server, but it will work just fine if it's smaller or larger. In this case, I chose 1024 for the constant, which seems to work pretty well.

The significance of using sysread() here rather than read() , its buffered alternative, is that sysread() allows partial reads. If there are no BUFSIZE bytes ready to be read, sysread() returns whatever is available. read() , in contrast, blocks while waiting to satisfy the request, delaying the psychotherapist's responses indefinitely. The same argument doesn't apply to syswrite() , versus print() , however. Since IO::Socket objects are autoflushed by default, syswrite() and print() have exactly the same effect.

Notes on gab3.pl

After developing gab3.pl , I was interested in how it performed relative to the send-wait-read version of Figure 5.6 ( gab1.pl ) and the forking line-oriented version of Figure 5.7 ( gab2.pl ). To do this, I timed the three scripts while transmitting a large text file to a conventional echo server. This test allowed both the line-oriented scripts and the byte-stream script to function properly.

Relative to gab1.pl , I found an approximately 5-fold increase in speed, and relative to gab2.pl , a 1.5-fold increase. The big efficiency gain when switching from the single to the multitasking design was dramatic, and represents the fact that the multitasking design keeps the network pipe full and running in both directions simultaneously , while the send-wait-read design uses only half the bandwidth at any time, and waits to receive the entire transmission before sending a response.

Another interesting benchmark result is that when I tried replacing the built-in calls to syswrite() and sysread() in gab3.pl with their object-oriented wrappers, I found a 20 percent decrease in efficiency, reflecting Perl's method call overhead. This probably won't make a significant difference in most networking applications, which are dominated by network speeds, but is worth keeping in mind for those tight inner loops where efficiency is critical.

As an aside, while testing gab3.pl with eliza_server.pl , I discovered an apparent bug in the Eliza module's command_interface() method. When it reads a line of input from STDIN , it never checks for end of file. As a result, if you terminate the connection at the client side, command_interface() goes into a very unattractive infinite loop that wastes CPU time.

The easy solution is to replace the Chatbot::Eliza_testquit() method, which checks the input string for words like "quit" and "bye." By checking whether the string is undefined, _testquit() can detect end of file. Insert this definition somewhere near the bottom of the Eliza server:

sub Chatbot::Eliza::_testquit { my ($self,$string) = @_; return 1 unless defined $string; # test for EOF foreach (@{$self->{quit}}) { return 1 if $string =~ /\b$_\b/i }; }

The server will now detect and respond correctly to the end-of-file condition.


   
Top

Категории