📄 perlipc.pod
字号:
my $coderef = shift; unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') { confess "usage: spawn CODEREF"; } my $pid; if (!defined($pid = fork)) { logmsg "cannot fork: $!"; return; } elsif ($pid) { logmsg "begat $pid"; return; # I'm the parent } # else I'm the child -- go spawn open(STDIN, "<&Client") || die "can't dup client to stdin"; open(STDOUT, ">&Client") || die "can't dup client to stdout"; ## open(STDERR, ">&STDOUT") || die "can't dup stdout to stderr"; exit &$coderef(); }This server takes the trouble to clone off a child version via fork() foreach incoming request. That way it can handle many requests at once,which you might not always want. Even if you don't fork(), the listen()will allow that many pending connections. Forking servers have to beparticularly careful about cleaning up their dead children (called"zombies" in Unix parlance), because otherwise you'll quickly fill up yourprocess table.We suggest that you use the B<-T> flag to use taint checking (see L<perlsec>)even if we aren't running setuid or setgid. This is always a good ideafor servers and other programs run on behalf of someone else (like CGIscripts), because it lessens the chances that people from the outside willbe able to compromise your system.Let's look at another TCP client. This one connects to the TCP "time"service on a number of different machines and shows how far their clocksdiffer from the system on which it's being run: #!/usr/bin/perl -w use strict; use Socket; my $SECS_of_70_YEARS = 2208988800; sub ctime { scalar localtime(shift) } my $iaddr = gethostbyname('localhost'); my $proto = getprotobyname('tcp'); my $port = getservbyname('time', 'tcp'); my $paddr = sockaddr_in(0, $iaddr); my($host); $| = 1; printf "%-24s %8s %s\n", "localhost", 0, ctime(time()); foreach $host (@ARGV) { printf "%-24s ", $host; my $hisiaddr = inet_aton($host) || die "unknown host"; my $hispaddr = sockaddr_in($port, $hisiaddr); socket(SOCKET, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; connect(SOCKET, $hispaddr) || die "bind: $!"; my $rtime = ' '; read(SOCKET, $rtime, 4); close(SOCKET); my $histime = unpack("N", $rtime) - $SECS_of_70_YEARS ; printf "%8d %s\n", $histime - time, ctime($histime); }=head2 Unix-Domain TCP Clients and ServersThat's fine for Internet-domain clients and servers, but what about localcommunications? While you can use the same setup, sometimes you don'twant to. Unix-domain sockets are local to the current host, and are oftenused internally to implement pipes. Unlike Internet domain sockets, Unixdomain sockets can show up in the file system with an ls(1) listing. % ls -l /dev/log srw-rw-rw- 1 root 0 Oct 31 07:23 /dev/logYou can test for these with Perl's B<-S> file test: unless ( -S '/dev/log' ) { die "something's wicked with the log system"; }Here's a sample Unix-domain client: #!/usr/bin/perl -w use Socket; use strict; my ($rendezvous, $line); $rendezvous = shift || '/tmp/catsock'; socket(SOCK, PF_UNIX, SOCK_STREAM, 0) || die "socket: $!"; connect(SOCK, sockaddr_un($rendezvous)) || die "connect: $!"; while (defined($line = <SOCK>)) { print $line; } exit;And here's a corresponding server. You don't have to worry about sillynetwork terminators here because Unix domain sockets are guaranteedto be on the localhost, and thus everything works right. #!/usr/bin/perl -Tw use strict; use Socket; use Carp; BEGIN { $ENV{PATH} = '/usr/ucb:/bin' } sub spawn; # forward declaration sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\n" } my $NAME = '/tmp/catsock'; my $uaddr = sockaddr_un($NAME); my $proto = getprotobyname('tcp'); socket(Server,PF_UNIX,SOCK_STREAM,0) || die "socket: $!"; unlink($NAME); bind (Server, $uaddr) || die "bind: $!"; listen(Server,SOMAXCONN) || die "listen: $!"; logmsg "server started on $NAME"; my $waitedpid; sub REAPER { $waitedpid = wait; $SIG{CHLD} = \&REAPER; # loathe sysV logmsg "reaped $waitedpid" . ($? ? " with exit $?" : ''); } $SIG{CHLD} = \&REAPER; for ( $waitedpid = 0; accept(Client,Server) || $waitedpid; $waitedpid = 0, close Client) { next if $waitedpid; logmsg "connection on $NAME"; spawn sub { print "Hello there, it's now ", scalar localtime, "\n"; exec '/usr/games/fortune' or die "can't exec fortune: $!"; }; } sub spawn { my $coderef = shift; unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') { confess "usage: spawn CODEREF"; } my $pid; if (!defined($pid = fork)) { logmsg "cannot fork: $!"; return; } elsif ($pid) { logmsg "begat $pid"; return; # I'm the parent } # else I'm the child -- go spawn open(STDIN, "<&Client") || die "can't dup client to stdin"; open(STDOUT, ">&Client") || die "can't dup client to stdout"; ## open(STDERR, ">&STDOUT") || die "can't dup stdout to stderr"; exit &$coderef(); }As you see, it's remarkably similar to the Internet domain TCP server, somuch so, in fact, that we've omitted several duplicate functions--spawn(),logmsg(), ctime(), and REAPER()--which are exactly the same as in theother server.So why would you ever want to use a Unix domain socket instead of asimpler named pipe? Because a named pipe doesn't give you sessions. Youcan't tell one process's data from another's. With socket programming,you get a separate session for each client: that's why accept() takes twoarguments.For example, let's say that you have a long running database server daemonthat you want folks from the World Wide Web to be able to access, but onlyif they go through a CGI interface. You'd have a small, simple CGIprogram that does whatever checks and logging you feel like, and then actsas a Unix-domain client and connects to your private server.=head1 TCP Clients with IO::SocketFor those preferring a higher-level interface to socket programming, theIO::Socket module provides an object-oriented approach. IO::Socket isincluded as part of the standard Perl distribution as of the 5.004release. If you're running an earlier version of Perl, just fetchIO::Socket from CPAN, where you'll also find modules providing easyinterfaces to the following systems: DNS, FTP, Ident (RFC 931), NIS andNISPlus, NNTP, Ping, POP3, SMTP, SNMP, SSLeay, Telnet, and Time--justto name a few.=head2 A Simple ClientHere's a client that creates a TCP connection to the "daytime"service at port 13 of the host name "localhost" and prints out everythingthat the server there cares to provide. #!/usr/bin/perl -w use IO::Socket; $remote = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "localhost", PeerPort => "daytime(13)", ) or die "cannot connect to daytime port at localhost"; while ( <$remote> ) { print }When you run this program, you should get something back thatlooks like this: Wed May 14 08:40:46 MDT 1997Here are what those parameters to the C<new> constructor mean:=over 4=item C<Proto>This is which protocol to use. In this case, the socket handle returnedwill be connected to a TCP socket, because we want a stream-orientedconnection, that is, one that acts pretty much like a plain old file.Not all sockets are this of this type. For example, the UDP protocolcan be used to make a datagram socket, used for message-passing.=item C<PeerAddr>This is the name or Internet address of the remote host the server isrunning on. We could have specified a longer name like C<"www.perl.com">,or an address like C<"204.148.40.9">. For demonstration purposes, we'veused the special hostname C<"localhost">, which should always mean thecurrent machine you're running on. The corresponding Internet addressfor localhost is C<"127.1">, if you'd rather use that.=item C<PeerPort>This is the service name or port number we'd like to connect to.We could have gotten away with using just C<"daytime"> on systems with awell-configured system services file,[FOOTNOTE: The system services fileis in I</etc/services> under Unix] but just in case, we've specified theport number (13) in parentheses. Using just the number would also haveworked, but constant numbers make careful programmers nervous.=backNotice how the return value from the C<new> constructor is used asa filehandle in the C<while> loop? That's what's called an indirectfilehandle, a scalar variable containing a filehandle. You can useit the same way you would a normal filehandle. For example, youcan read one line from it this way: $line = <$handle>;all remaining lines from is this way: @lines = <$handle>;and send a line of data to it this way: print $handle "some data\n";=head2 A Webget ClientHere's a simple client that takes a remote host to fetch a documentfrom, and then a list of documents to get from that host. This is amore interesting client than the previous one because it first sendssomething to the server before fetching the server's response. #!/usr/bin/perl -w use IO::Socket; unless (@ARGV > 1) { die "usage: $0 host document ..." } $host = shift(@ARGV); $EOL = "\015\012"; $BLANK = $EOL x 2; foreach $document ( @ARGV ) { $remote = IO::Socket::INET->new( Proto => "tcp", PeerAddr => $host, PeerPort => "http(80)", ); unless ($remote) { die "cannot connect to http daemon on $host" } $remote->autoflush(1); print $remote "GET $document HTTP/1.0" . $BLANK; while ( <$remote> ) { print } close $remote; }The web server handing the "http" service, which is assumed to be atits standard port, number 80. If the web server you're trying toconnect to is at a different port (like 1080 or 8080), you should specifyas the named-parameter pair, C<< PeerPort => 8080 >>. The C<autoflush>method is used on the socket because otherwise the system would bufferup the output we sent it. (If you're on a Mac, you'll also need tochange every C<"\n"> in your code that sends data over the network tobe a C<"\015\012"> instead.)Connecting to the server is only the first part of the process: once youhave the connection, you have to use the server's language. Each serveron the network has its own little command language that it expects asinput. The string that we send to the server starting with "GET" is inHTTP syntax. In this case, we simply request each specified document.Yes, we really are making a new connection for each document, even thoughit's the same host. That's the way you always used to have to speak HTTP.Recent versions of web browsers may request that the remote server leavethe connection open a little while, but the server doesn't have to honorsuch a request.Here's an example of running that program, which we'll call I<webget>: % webget www.perl.com /guanaco.html HTTP/1.1 404 File Not Found Date: Thu, 08 May 1997 18:02:32 GMT Server: Apache/1.2b6 Connection: close Content-type: text/html <HEAD><TITLE>404 File Not Found</TITLE></HEAD> <BODY><H1>File Not Found</H1> The requested URL /guanaco.html was not found on this server.<P> </BODY>Ok, so that's not very interesting, because it didn't find thatparticular document. But a long response wouldn't have fit on this page.For a more fully-featured version of this program, you should look tothe I<lwp-request> program included with the LWP modules from CPAN.=head2 Interactive Client with IO::SocketWell, that's all fine if you want to send one command and get one answer,but what about setting up something fully interactive, somewhat likethe way I<telnet> works? That way you can type a line, get the answer,type a line, get the answer, etc.This client is more complicated than the two we've done so far, but ifyou're on a system that supports the powerful C<fork> call, the solutionisn't that rough. Once you've made the connection to whatever serviceyou'd like to chat with, call C<fork> to clone your process. Each ofthese two identical process has a very simple job to do: the parentcopies everything from the socket to standard output, while the childsimultaneously copies everything from standard input to the socket.To accomplish the same thing using just one process would be I<much>harder, because it's easier to code two processes to do one thing than itis to code one process to do two things. (This keep-it-simple principlea cornerstones of the Unix philosophy, and good software engineering aswell, which is probably why it's spread to other systems.)Here's the code: #!/usr/bin/perl -w use strict; use IO::Socket; my ($host, $port, $kidpid, $handle, $line); unless (@ARGV == 2) { die "usage: $0 host port" } ($host, $port) = @ARGV; # create a tcp connection to the specified host and port $handle = IO::Socket::INET->new(Proto => "tcp", PeerAddr => $host, PeerPort => $port) or die "can't connect to port $port on $host: $!"; $handle->autoflush(1); # so output gets there right away print STDERR "[Connected to $host:$port]\n"; # split the program into two processes, identical twins die "can't fork: $!" unless defined($kidpid = fork()); # the if{} block runs only in the parent process if ($kidpid) { # copy the socket to standard output
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -