Chapter 5 A IO::Socket API. Socket APIs Chapter 4 used the built-in socket API; very C-like. This...

39
Chapter 5 A IO::Socket API
  • date post

    20-Dec-2015
  • Category

    Documents

  • view

    239
  • download

    5

Transcript of Chapter 5 A IO::Socket API. Socket APIs Chapter 4 used the built-in socket API; very C-like. This...

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)

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

Homework 5: Problem 8 Diagram:

chat server

gab2.plcopy 1

child 1

child 2

stdin

stdout

gab2.plcopy 2

child 1

child 2

stdin

stdout

3WHS

listen ona.b.c.d:n

3WHS

send a.b.c.d:n

3WHS

1:

2:

3:

4:

5:

gathers a.b.c.d:n

single tcp connections