View
241
Download
5
Embed Size (px)
Citation preview
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)
my $host = shift || 'localhost';$/ = CRLF;my $socket = IO::Socket::INET->new("$host:daytime");
port numberfrom /etc/services
time_of_day_tcp2.pl
#!/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"
get network vrersionof \newline
page 31
tcp_echo_cli2.pl
#!/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 theerror message of the last eval()
/usr/lib/perl5/5.8.5/i386-linux-thread-multi/IO/Socket.pm
autoflush(true) is the defaultfor IO::Socket objects
IO::Socket Hierarchy:
IO::Handle
IO::File IO::Pipe IO::Socket
IO::Socket::INET IO::Socket::UNIX
never create an IO::Socket object; always a subobject
IO::Socket::INET->new():
• $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().
$socket = IO::Socket::INET->new(@args);
Parameter passing styles (1):
• Parameters can be passed in two styles:
wuarchive.wustl.edu:echowuarchive.wustl.edu:7wuarchive.wustl.edu:echo(7)128.252.120.8:echo128.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():
PeerAddr Remote host address <hname | IPAddr>[:<port>]
PeerHost Same as PeerAddr ditto
PeerPort Remote port or service <service or number>
LocalAddr Local host bind address <hname | IPAddr>[:<port>]
LocalHost Same as LocalAddr ditto
LocalPort Local host bind port <number>
Proto Protocol name (number) <name or number>
Type Socket type SOCK_STREAM | SOCK_DGRAM
Listen Queue size for listen <number>
Reuse Set SO_REUSEAADDR
before binding
true | false
Timeout Timeout Value <number>
Multihomed Try all addresses on multihomed hosts.
true | false
Argument Description Value
client
server
requiredforservers
handy toshortenconnect()block
uses DNSand triesall IPAddrs
Parameter Passing Examples:
my $sock = IO::Socket::INET->new(Proto => ‘tcp’, PeerAddr => ‘www.yahoo.com’, PeerPort => ‘http(80)’);
my $sock = IO::Socket::INET->new(Proto => ‘tcp’, LocalPort => 2007, Listen => 128);
my $udp = IO::Socket::INET->new(Proto => ‘udp’);
client example
server example
client/server example
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):
• 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();
$protocol = $sock->protocol();$type = $sock->socktype();$domain = $sock->sockdomain();
IO::Socket Methods (6):
• 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.
$value = $sock->sockopt($option [,value]);
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):
• 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.
$value = $sock->timeout([$timeout]);
use IO::Socket; . . .$sock->timeout(5);while (1) { &housekeeping(); next unless $session = $sock->accept(); # process $session;}
if the server has noclients then it calls housekeeping() onceevery 5 seconds
accept() will timeoutafter 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 secondsand 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):
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 notregistered with DNS?
what makes us quit gracefully
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.
http://hostname[:port][/path/to/document[#fragment]]
optional
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
HTTP/1.1 200 OK CRLFDate: Tue, 14 Mar 2006 16:53:58 GMT CRLFServer: Apache/2.0.52 (Red Hat) CRLFLast-Modified: Tue, 07 Mar 2006 23:02:09 GMT CRLFETag: "e24123-1ab2-40e6fa2015e40“ CRLFAccept-Ranges: bytes CRLFContent-Length: 6834 CRLFConnection: close CRLFContent-Type: text/html; charset=UTF-8 CRLFCRLF<TITLE> Andrew Pletch </TITLE> …
header:consumed by browser,format fixed
desired content:displayed by browser;format varies
Web Client (code):
#!/usr/bin/perl# file: web_fetch.pl# Figure 5.5: Simple web page fetcheruse 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; # sendmy $header = <$socket>; # read the entire header$header =~ s/$CRLF/\n/g; # replace CRLF with logical newlineprint $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:
• What does it all mean?
my ($host,$path) = $url =~ m!^http://([^/]+)(/[^\#]*)!
patterns surrounded by ( ) arecaptured and returned. If no variableis specified then they are returned to $1, $2, …
pattern matching operator; $url is inputand it this is substitution, also output
use m if delimiter is changed (to ! since normal delimiter is /)
beginning of line anchor
([^/]+) 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):
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’
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.
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 21220 (vsFTPd 2.0.1)user pletcha331 Please specify the password.PASS xxxxxxx230 Login successful.
gab1.pl (alternative):
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;}
this fails; after it gets thefirst line from the serverit continues to wait for more from the server andnever gets to read what you typing at the keyboard. Thisis called deadlock.
None of the easy fixes fixesthe problem. This is because when connecting to an ftpserver, 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.
Closing a connection in a forked client:
time
parent
child
parent
child
parent
child
parent
child
parent
server
server
server
user_to_host()
host_to_user()
shutdown(1):FIN
sleep()
EOF:FIN
sleep()
CHLD
exit()
exit()
no data lost, all arrives before EOF.
notice we use shutdown(1) andnot close(). why?
What happens if the server sends the first FIN?
time
parent
child
parent
child
parent
child
parent
server
server
server
user_to_host()
host_to_user()
EOF:FIN
CHLD
exit()
exit()
shutdown(1):FIN
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"; }}
Homework 5: Problem 7 Diagram:
gab2.plcopy 2
child 1
child 2
gab2.plcopy 1
child 1
child 2
chat serverchild 1
child 2stdin
stdout
stdin
stdout
single tcp connection
single tcp connection