390 likes | 515 Views
Chapter 5. A IO::Socket API. Socket APIs. Chapter 4 used the built-in socket API; very C-like. This chapter uses the IO::Socket API; easier interface. Built-in Socket API: IO::Socket API. my $address = shift || DEFAULT_ADDR; my $packed_addr = inet_aton($address);
E N D
Chapter 5 A IO::Socket API
Socket APIs • Chapter 4 used the built-in socket API; very C-like. • This chapter uses the IO::Socket API; easier interface. • Built-in Socket API: • IO::Socket API my $address = shift || DEFAULT_ADDR; my $packed_addr = inet_aton($address); my $destination = sockaddr_in(PORT,$packed_addr); socket(SOCK,PF_INET,SOCK_STREAM,IPPROTO_TCP); connect(SOCK,$destination) port number from /etc/services my $host = shift || 'localhost'; $/ = CRLF; my $socket = IO::Socket::INET->new("$host:daytime");
time_of_day_tcp2.pl get network vrersion of \newline #!/usr/bin/perl # file: time_of_day_tcp2.pl # Figure 5.1 Time of day client using IO::Socket use strict; use IO::Socket qw(:DEFAULT :crlf); my $host = shift || 'localhost'; $/ = CRLF; # affects all file handles my $socket = IO::Socket::INET->new("$host:daytime") or die "Can't connect to daytime service at $host: $!\n"; chomp(my $time = $socket->getline); print $time,"\n" page 31
tcp_echo_cli2.pl /usr/lib/perl5/5.8.5/i386-linux-thread-multi/IO/Socket.pm #!/usr/bin/perl # file: tcp_echo_cli2.pl # Figure 5.2: TCP echo client using IO::Socket # usage: tcp_echo_cli2.pl [host] [port] use strict; use IO::Socket; my ($bytes_out,$bytes_in) = (0,0); my $host = shift || 'localhost'; my $port = shift || 'echo'; my $socket = IO::Socket::INET->new("$host:$port") or die $@; while (defined(my $msg_out = STDIN->getline)) { print $socket $msg_out; my $msg_in = <$socket>; print $msg_in; $bytes_out += length($msg_out); $bytes_in += length($msg_in); } $socket->close or warn $@; print STDERR "bytes_sent = $bytes_out, bytes_received = $bytes_in\n"; supposedly the error message of the last eval() autoflush(true) is the default for IO::Socket objects
IO::Socket Hierarchy: never create an IO::Socket object; always a subobject
IO::Socket::INET->new(): $socket = IO::Socket::INET->new(@args); • $socket inherits all IO::Handle funtionality but also includes accept(), connect(), bind() and sockopt(). • Returns a new connection object or undef and $!. $@ contains a more verbose error message. • Combines socket(), connect() and sockaddr_in().
Parameter passing styles (1): • Parameters can be passed in two styles: wuarchive.wustl.edu:echo wuarchive.wustl.edu:7 wuarchive.wustl.edu:echo(7) 128.252.120.8:echo 128.252.120.8:echo(7) 128.252.120.8:7 fallback
Parameter passing styles (2): • You can specify many additional parameters my $echo = IO::Socket::INET->new( PeerAddr => ‘wuarchive.wustl.edu’, PeerPort => ‘echo(7)’, Type => SOCK_STREAM, Proto => ‘tcp’);
Parameter passing styles (3): • Complete list of parameters for new(): Argument Description Value client server required for servers handy to shorten connect() block uses DNS and tries all IPAddrs
Parameter Passing Examples: client example my $sock = IO::Socket::INET->new(Proto => ‘tcp’, PeerAddr => ‘www.yahoo.com’, PeerPort => ‘http(80)’); server example my $sock = IO::Socket::INET->new(Proto => ‘tcp’, LocalPort => 2007, Listen => 128); client/server example my $udp = IO::Socket::INET->new(Proto => ‘udp’);
IO::Socket Methods: • accept() works like the built-in function. The new socket inherits all the listener parameters. In the list context it also returns a packed address. • These three functions are used if you do not supply all arguments to IO::Socket::INET->new(). $connected_socket = $listen_socket->accept(); ($connected_socket,$remote_addr) = $listen_socket->accept(); $rtn_val = $sock->connect($dest_addr); $rtn_val = $sock->bind($my_addr); $rtn_val = $sock->listen($ax_queue); $sock = IO::Socket::INET->new(Proto => ‘tcp’); $dest_addr = sockaddr_in(…); $sock->connect($dest_addr);
IO::Socket Methods (2): • For convenience sake, versions of connect() and bind() exist that take unpacked values. • Like the function-oriented call, this shuts down all copies of a socket, even in forked children. $return_val = $sock->connect($port,$host); $return_val = $ sock- >bind($port,$host); $rtn_val = $sock->shutdown($how);
IO::Socket Methods (3): • These are simple wrappers around the function-oriented equivalents. They return packed addresses, unpack them with sockaddr_in(). • All these functions unpack their results. However, the results of sockaddr() and peeraddr() are still binary and need to be processed by inet_ntoa(). $my_addr = $sock->sockname(); $her_addr = $ sock- > peername(); $result = $sock->sockport(); $result = $sock->peerport(); $result = $sock->sockaddr(); $result = $sock->peerport();
IO::Socket Methods (4): • These go one step beyond sockname() and peername() and return IP addresses in dotted decimal (a.b.c.d). • This example recovers the DNS name of the peer connection or else its IP address if it is not registered with DNS. $my_name = $sock->sockhost(); $her_name = $ sock- > peerhost(); $peer = gethostbyaddr($sock->peeraddr(), AF_INET) || $sock->peerhost();
IO::Socket Methods (5): $protocol = $sock->protocol(); $type = $sock->socktype(); $domain = $sock->sockdomain(); • These three functions return integers. These are “getters” only. • Returns true if connected to a remote host; false otherwise. It works by calling peername(). $result = $sock->connected();
IO::Socket Methods (6): $value = $sock->sockopt($option [,value]); • Used to “get” or “set” an option. It wraps both getsockopt() and setsockopt(). It is more user friendly than getsockopt() since it assumes SOL_SOCKET level. It is also user friendly in that it unpacks binary results into integers. SO_LINGER has an 8-byte binary result and this is not unpacked for some reason.
sockopt() Example: • This function, if we looked at its source code, would need to look something like sub sockopt { my ($sock,$op,$v) = @_; my $R; . . . if ( $op == SO_KEEPALIVE) { if ( $v eq “” ) { # get $R = getsockopt($sock,SOL_SOCKET,SO_KEEPALIVE); if (defined $R ) { return unpack(“I”,$R); } else { return undef; } } else { # set } } . . . }
IO::Socket Methods (7): $value = $sock->timeout([$timeout]); • Used to “get” or “set” timeout. This value is used by connect() and accept(). Called with a numeric value it sets the timeout value and returns the old value. Called with no value it returns the current value. accept() will timeout after 5 seconds use IO::Socket; . . . $sock->timeout(5); while (1) { &housekeeping(); next unless $session = $sock->accept(); # process $session; } if the server has no clients then it calls housekeeping() once every 5 seconds
IO::Socket Methods (8): • The front-ends for send() and recv() and will be discussed when we look at UDP. • Can’t use timeout() on these functions. Instead we can use the eval{ } trick. $bytes = $sock->send($data [, $flags, $destination]); $address = $sock->recv($buffer, $length [, $flags]); eval { local $SIG{ALRM} = {exit 1;}; alarm(5); $address = $sock->recv($buffer,$length); if (defined $address) { return ($address, $buffer); } else { return undef; } } alarm(0); ($Addr,$Buf) = alarm fires in 5 seconds and exits the eval { } block.
timeout() observation: • The text says that unless timeout(n) is called then you can not interrupt connect() or accept() with a signal. I have not found this to be true. • Even with no timeout specified by me the INT signal still interrupts accept().
Echo server Revisited: #!/usr/bin/perl # file: tcp_echo_serv2.pl # Figure 5.4: The reverse echo server, using IO::Socket # usage: tcp_echo_serv2.pl [port] use strict; use IO::Socket qw(:DEFAULT :crlf); use constant MY_ECHO_PORT => 2007; $/ = CRLF; my ($bytes_out,$bytes_in) = (0,0); my $quit = 0; $SIG{INT} = sub { $quit++ }; my $port = shift || MY_ECHO_PORT; my $sock = IO::Socket::INET->new( Listen => 20, LocalPort => $port, Timeout => 60*60, Reuse => 1) or die "Can't create listening socket: $!\n"; 1 hour timeout
Echo server Revisited (2): what makes us quit gracefully warn "waiting for incoming connections on port $port...\n"; while (!$quit) { next unless my $session = $sock->accept(); my $peer = gethostbyaddr($session->peeraddr,AF_INET) || $session->peerhost; my $port = $session->peerport; warn "Connection from [$peer,$port]\n"; while (<$session>) { $bytes_in += length($_); chomp; my $msg_out = (scalar reverse $_) . CRLF; print $session $msg_out; $bytes_out += length($msg_out); } warn "Connection from [$peer,$port] finished\n"; close $session; } print STDERR "bytes_sent = $bytes_out, bytes_received = $bytes_in\n"; close $sock; returns undef if timeout expires what if remote host not registered with DNS?
Web Client: • Reads web server output as raw text; prints without making it pretty. • URL syntax: • Our program must parse a URL to connect to the desired webserver. This is the hard part. optional http://hostname[:port][/path/to/document[#fragment]] port missing, 80 is assumed path missing; look for index.html fragment missing; entire document
Web Client (2); • After 3WHS you send the following message to a webserver. • The reply that comes back looks like: GET /path/to/document HTTP/1.0 CRLF CRLF header: consumed by browser, format fixed HTTP/1.1 200 OK CRLF Date: Tue, 14 Mar 2006 16:53:58 GMT CRLF Server: Apache/2.0.52 (Red Hat) CRLF Last-Modified: Tue, 07 Mar 2006 23:02:09 GMT CRLF ETag: "e24123-1ab2-40e6fa2015e40“ CRLF Accept-Ranges: bytes CRLF Content-Length: 6834 CRLF Connection: close CRLF Content-Type: text/html; charset=UTF-8 CRLF CRLF <TITLE> Andrew Pletch </TITLE> … desired content: displayed by browser; format varies
Web Client (code): #!/usr/bin/perl # file: web_fetch.pl # Figure 5.5: Simple web page fetcher use strict; use IO::Socket qw(:DEFAULT :crlf); $/ = CRLF . CRLF; my $data; my $url = shift or die "Usage: web_fetch.pl <URL>\n"; my ($host,$path) = $url =~ m!^http://([^/]+)(/[^\#]*)! or die "Invalid URL.\n"; my $socket = IO::Socket::INET->new(PeerAddr => $host, PeerPort => 'http(80)') or die "Can't connect: $!"; print $socket "GET $path HTTP/1.0",CRLF,CRLF; # send my $header = <$socket>; # read the entire header $header =~ s/$CRLF/\n/g; # replace CRLF with logical newline print $header; print $data while read($socket,$data,1024) > 0; Using <>, a single read goes until it sees a pair of CRLF characters. Does not affect definition of ‘\n’ see next page read() pays no attention to $/
Search Patterns: pattern matching operator; $url is input and it this is substitution, also output use m if delimiter is changed (to ! since normal delimiter is /) • What does it all mean? my ($host,$path) = $url =~ m!^http://([^/]+)(/[^\#]*)! beginning of line anchor patterns surrounded by ( ) are captured and returned. If no variable is specified then they are returned to $1, $2, … ([^/]+) means a pattern of one or more characters, excluding / (/[^\#]*) means a pattern that begins with a / and is followed by one or more characters that are not # (escaped)
IO::Socket performance: • Usually adds to memory usage – 880KB to 1.5MB. • Loads more slowly but runs with same speed. • Some IO::Socket functions are just wrappers. • Some IO::Socket functions are a big imporvement – IO::Socket::accept() returns a connected socket; accept() returns the address of socket and a file handle. $socket->syswrite(“A man, a plan, a canal, panama!”); syswrite($socket, “A man, a plan, a canal, panama!”);
Concurrency: • We want our application to do two things at once – read from the keyboard or network connection. • The following example of a chat client doesn’t do this. #!/usr/bin/perl # file: gab1.pl # Figure 5.6: An incorrect implementation of a gab client # warning: this doesn't really work use strict; use IO::Socket qw(:DEFAULT :crlf); my $host = shift or die "Usage: gab1.pl host [port]\n"; my $port = shift || 'echo'; my $socket = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port) or die "Can't connect: $!";
gab1.pl (cont): this example normally fails since it tries to read a single line from the server before it tries to read a single line from stdin. my ($from_server,$from_user); LOOP: while (1) { { # localize change to $/ local $/ = CRLF; last LOOP unless $from_server = <$socket>; chomp $from_server; } print $from_server,"\n"; last unless $from_user = <>; chomp($from_user); print $socket $from_user,CRLF; } here, $/ is just ‘\n’
gab1.pl works! • However, if gab1.pl connects to an ftp server it works, for a while. • Thus ends the single line exchange and now it won’t work since the command HELP, for example, receives multiple output lines but only if we continue to type something. We are out of synch. ./gab1.pl wyvern.cs.newpaltz.edu 21 220 (vsFTPd 2.0.1) user pletcha 331 Please specify the password. PASS xxxxxxx 230 Login successful.
gab1.pl (alternative): this fails; after it gets the first line from the server it continues to wait for more from the server and never gets to read what you typing at the keyboard. This is called deadlock. my ($from_server,$from_user); LOOP: while (1) { { # localize change to $/ local $/ = CRLF; while ( $from_server = <$socket> ) { chomp $from_server; print $from_server,"\n"; } last unless $from_user = <>; chomp($from_user); print $socket $from_user,CRLF; } None of the easy fixes fixes the problem. This is because when connecting to an ftp server, the server sends the first data.
gab1.pl Solution: • Decouple the read from the connection and the read from STDIN. • Isolate the reading tasks in two independent and concurrent processes that won’t block each other. • At this point we only have fork() that will allow us to do this. • The parent is responsible for copying data from STDIN to the network connection. • The child is responsible for the opposite direction. • Two closing scenarios: • server closes, child reads EOF, exits and must notify parent • user calls it quits, parent reads EOF and must notify child
gab2.pl: • Child notifies parent. This is easy, when the child exits a CHLD signal is sent to the parent. The parent just needs to install a CHLD signal handler. • Once the user closes STDIN the parent could kill() the child. This may be premature. There may be data still coming back to the server. • A cleaner way is for the parent, once it gets an EOF from the user, closes its end of the connection. • The server sees this and closes its end. • The child gets the message from the server.
parent parent parent parent parent server server server child child Closing a connection in a forked client: user_to_host() host_to_user() notice we use shutdown(1) and not close(). why? shutdown(1):FIN sleep() EOF:FIN child no data lost, all arrives before EOF. sleep() CHLD child exit() time exit()
parent parent parent parent server server server child child child What happens if the server sends the first FIN? user_to_host() host_to_user() EOF:FIN CHLD exit() shutdown(1):FIN exit() time
gab2.pl (code): #!/usr/bin/perl # file: gab2.pl # Figure 5.8: A working implementation of a gab client # usage: gab2.pl [host] [port] # Forking TCP network client use strict; use IO::Socket qw(:DEFAULT :crlf); my $host = shift or die "Usage: gab2.pl host [port]\n"; my $port = shift || 'echo'; my $socket = IO::Socket::INET->new("$host:$port") or die $@; my $child = fork(); die "Can't fork: $!" unless defined $child;
gab2.pl (code): if ($child) { $SIG{CHLD} = sub { exit 0 }; user_to_host($socket); $socket->shutdown(1); sleep; } else { host_to_user($socket); warn "Connection closed by foreign host.\n"; } sub user_to_host { my $s = shift; while (<>) { chomp; print $s $_,CRLF; } } sub host_to_user { my $s = shift; $/ = CRLF; while (<$s>) { chomp; print $_,"\n"; } }
child 1 child 2 child 1 child 2 Homework 5: Problem 7 Diagram: single tcp connection stdin gab2.pl copy 1 child 1 chat server stdout stdin child 2 gab2.pl copy 2 stdout single tcp connection
stdin child 1 gab2.pl copy 1 child 2 chat server stdout child 1 child 2 Homework 5: Problem 8 Diagram: 4: gathers a.b.c.d:n send a.b.c.d:n 1: 3WHS 3: stdin 3WHS gab2.pl copy 2 stdout 2: listen on a.b.c.d:n 3WHS 5: single tcp connections