Perl and UNIX Network Programming

Download Report

Transcript Perl and UNIX Network Programming

Perl and UNIX Network
Programming
Naoya Ito
naoya at hatena.ne.jp
Why now network programming?
 httpd is boring
 Some recent web application have
special feature of networking.
 Comet
 Socket API of ActionScript 3
 mini server for development, like
Catalyst's server.pl
Agenda
 UNIX network programming basics
with Perl
 I/O multiplexing
 Perl libraries for modern network
programming
UNIX network programming
basics with Perl
BSD Socket API with C
int main (void) {
int
struct sockaddr_in
char
listenfd, connfd;
servaddr;
buf[1024];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listenfd, 5);
for (;;) {
connfd = accept(listenfd, NULL, NULL) ;
while (read(connfd, buf, sizeof(buf)) > 0) {
write(connfd, buf, strlen(buf));
}
close(connfd);
}
}
BSD Socket API







socket()
struct sockaddr_in
bind()
listen()
accept()
read() / write()
close()
Perl Network Programming




TMTOWTDI
less code
CPAN
performance is good enough

right design >> ... >> language advantage
BSD Socket API with Perl
#!/usr/local/bin/perl
use strict;
use warnings;
use Socket;
socket LISTEN_SOCK, AF_INET, SOCK_STREAM, scalar getprotobyname('tcp');
bind LISTEN_SOCK, pack_sockaddr_in(9999, INADDR_ANY);
listen LISTEN_SOCK, SOMAXCONN;
while (1) {
accept CONN_SOCK, LISTEN_SOCK;
while (sysread(CONN_SOCK, my $buffer, 1024)) {
syswrite CONN_SOCK, $buffer;
}
close CONN_SOCK;
}
use IO::Socket
#!/usr/local/bin/perl
use strict;
use warnings;
use IO::Socket;
my $server = IO::Socket::INET->new(
Listen
=> 20,
LocalPort => 9999,
Reuse
=> 1,
) or die $!;
while (1) {
my $client = $server->accept;
while ($client->sysread(my $buffer, 1024)) {
$client->syswrite($buffer);
}
$client->close;
}
$server->close;
blocking on Network I/O
while (1) {
my $client = $server->accept;
while ($client->sysread(my $buffer, 1024)) { # block
$client->syswrite($buffer);
}
$client->close;
}
accept(2)
server
listen queue
read(2)
client #1
I can't do
client #2
busy loop / blocking
% ps -e -o stat,pid,wchan=WIDE-WCHAN-COLUMN,time,comm
while (1) { $i++ }
STAT
PID WIDE-WCHAN-COLUMN
TIME COMMAND
R+
18684 00:00:38 perl
while (1) { STDIN->getline }
STAT
S+
PID WIDE-WCHAN-COLUMN
8671 read_chan
TIME COMMAND
00:00:00 perl
Linux internals
process
buffer
fread()
TASK_RUNNING
libc.so
buffer
read(2)
system call
vfs
TASK_UNINTERRUPTIBLE
ext3
switch to
Kernel-Mode.
User-process
goes sleep.
device driver
Hardware (HDD)
ref: 『Linux カーネル2.6解読室』 p.32
Kernel-Mode
Hardware
Interruption.
Again: blocking
while (1) {
my $client = $server->accept;
while ($client->sysread(my $buffer, 1024)) { # block
$client->syswrite($buffer);
}
$client->close;
}
We need parallel processing





fork()
threads
Signal I/O
I/O Multiplexing
Asynchronous I/O
I/O multiplexing
I/O Multiplexing
 Parallel I/O in single thread,
watching I/O event of file
descripters
 less resource than fork/threads
 select(2) / poll(2)
 wait for a number of file descriptors
to change status.
select(2)
listening
socket
1.
ready!
accepted
connection
#1
accepted
connection
#2
select(2)
3. ok, I'll try
to accept()
2. now listening
socket is ready
to accept a new
connection.
caller
select(2) on Perl
 select(@args)
 number of @args is not 1 but 4.
 difficult interface
 IO::Select
 OO interface to select(2)
 easy interface
IO::Select SYNOPSYS
use IO::Select;
$s = IO::Select->new();
$s->add(\*STDIN);
$s->add($some_handle);
@ready = $s->can_read($timeout); # block
use IO::Select
my $listen_socket = IO::Socket::INET->new(...) or die $@;
my $select = IO::Select->new or die $!;
$select->add($listen_socket);
while (1) {
my @ready = $select->can_read; # block
for my $handle (@ready) {
if ($handle eq $listen_socket) {
my $connection = $listen_socket->accept;
$select->add($connection);
} else {
my $bytes = $handle->sysread(my $buffer, 1024);
$bytes > 0 ? $handle->syswrite($buffer) : do {
$select->remove($handle);
$handle->close;
}
}
}
}
And more things we must think...
 blocking when syswrite()
 use non-blocking socket
 Line-based I/O
 select(2) disadvantage
non-blocking socket + Line-based I/O
use
use
use
use
sub handle_read {
my $client = shift;
if ($client == $server) {
my $new_client = $server->accept();
$new_client->blocking(0);
$select->add($new_client);
return;
}
POSIX;
IO::Socket;
IO::Select;
Tie::RefHash;
my $server = IO::Socket::INET->new(...);
$server->blocking(0);
my (%inbuffer, %outbuffer, %ready);
tie %ready, "Tie::RefHash";
my $data = "";
my $rv
= $client->recv($data, POSIX::BUFSIZ, 0);
my $select = IO::Select->new($server);
while (1) {
foreach my $client ( $select->can_read(1) ) {
handle_read($client);
}
unless (defined($rv) and length($data)) {
handle_error($client);
return;
}
oops
foreach my $client ( keys %ready ) {
foreach my $request ( @{ $ready{$client} } ) {
$outbuffer{$client} .= $request;
}
delete $ready{$client};
}
foreach my $client ( $select->can_write(1) ) {
handle_write($client);
}
$inbuffer{$client} .= $data;
while ( $inbuffer{$client} =~ s/(.*\n)// ) {
push @{$ready{$client}}, $1;
}
}
sub handle_write {
my $client = shift;
return unless exists $outbuffer{$client};
my $rv = $client->send($outbuffer{$client}, 0);
unless (defined $rv) {
warn "I was told I could write, but I can't.\n";
return;
}
}
sub handle_error {
my $client = shift;
delete $inbuffer{$client};
delete $outbuffer{$client};
delete $ready{$client};
if ($rv == length( $outbuffer{$client}) or $! == POSIX::EWOULDBLOCK) {
substr( $outbuffer{$client}, 0, $rv ) = "";
delete $outbuffer{$client} unless length $outbuffer{$client};
return;
}
handle_error($client);
$select->remove($client);
close $client;
}
}
select(2) disadvantage
 FD_SETSIZE limitation
 not good for C10K
 Inefficient processing
 coping list of fds to the kernel
 You must scan list of fds in UserLand
select(2) internals
process
select(2)
select(2)
fd
fd
fd
fd
copy
fd
fd
fd
fd
fd
FD_ISSET
fd
fd
fd
copy
fd
fd
fd
I/O event
kernel
ref: http://osdn.jp/event/kernel2003/pdf/C06.pdf
Modern UNIX APIs
 epoll
 Linux 2.6
 /dev/kqueue
 BSD
 devpoll
 Solaris
epoll(4)
 better than select(2), poll(2)
 no limitation of numbers of fds
 O(1) scallability
 needless to copy list of fds
 epoll_wait(2) returns only fds that has
new event
epoll internals
process
epoll_create()
fd table
epoll_ctl(ADD)
epoll_ctl(ADD)
epoll_ctl(ADD)
fd
fd
fd
epoll_wait()
fd
fd
fd
I/O event
kernel
ref: http://osdn.jp/event/kernel2003/pdf/C06.pdf
epoll on perl
 Sys::Syscall
 epoll
 sendfile
 IO::Epoll
 use IO::Epoll qw/:compat/
Perl libraries for modern
network programming
Libraries for Perl Network Programming
 TMTOWTDI
 POE
 Event::Lib
 Danga::Socket
 Event
 Stem
 Coro
...
They provides:
 Event-based programming for
parallel processing
 system call abstraction
 select(2) / poll(2) / epoll /
kqueue(2) / devpoll
POE
 "POE is a framework for
cooperative, event driven
multitasking in Perl. "
 POE has many "components" on CPAN
 I'm lovin' it :)
Hello, POE
use strict;
use warnings;
use POE qw/Sugar::Args/;
POE::Session->create(
inline_states => {
_start => sub {
my $poe = sweet_args;
$poe->kernel->yield('hello'),
},
hello => sub {
STDOUT->print("Hello, POE!");
},
},
);
POE::Kernel->run;
# async / FIFO
Watching handles in Event loop
POE::Session->create(
inline_states => {
_start => sub {
my $poe = sweet_args;
$poe->kernel->yield('readline'),
},
readline => sub {
my $poe = sweet_args;
STDOUT->syswrite("input> ");
$poe->kernel->select_read(\*STDIN, 'handle_input');
},
handle_input => sub {
my $poe = sweet_args;
my $stdin = $poe->args->[0];
STDOUT->syswrite(sprintf "Hello, %s", $stdin->getline);
$poe->kernel->yield('readline');
}
},
);
Results
% perl
input>
Hello,
input>
Hello,
input>
Hello,
input>
hello_poe2.pl
naoya
naoya
hatena
hatena
foo bar
foo bar
Results of strace
% strace -etrace=select,read,write -p `pgrep perl`
Process 8671 attached - interrupt to quit
select(8, [0], [], [], {3570, 620000}) = 1 (in [0], left {3566, 500000})
read(0, "naoya\n", 4096)
= 6
write(1, "Hello, naoya\n", 13)
= 13
select(8, [0], [], [], {0, 0})
= 0 (Timeout)
write(1, "input> ", 7)
= 7
select(8, [0], [], [], {3600, 0})
= 1 (in [0], left {3595, 410000})
read(0, "hatena\n", 4096)
= 7
write(1, "Hello, hatena\n", 14)
= 14
select(8, [0], [], [], {0, 0})
= 0 (Timeout)
write(1, "input> ", 7)
= 7
select(8, [0], [], [], {3600, 0})
= 1 (in [0], left {3598, 860000})
read(0, "foobar\n", 4096)
= 7
write(1, "Hello, foobar\n", 14)
= 14
select(8, [0], [], [], {0, 0})
= 0 (Timeout)
write(1, "input> ", 7)
= 7
select(8, [0], [], [], {3600, 0}
use POE::Wheel::ReadLine
POE::Session->create(
inline_states => {
...
readline => sub {
my $poe = sweet_args;
$poe->heap->{wheel} = POE::Wheel::ReadLine->new(
InputEvent => 'handle_input',
);
$poe->heap->{wheel}->get('input> ');
},
handle_input => sub {
my $poe = sweet_args;
$poe->heap->{wheel}->put(sprintf "Hello, %s", $poe->args->[0]);
$poe->heap->{wheel}->get('input> ');
}
},
);
...
Parallel echo server using POE
POE::Session->create(
inline_states => {
_start => \&server_start,
},
package_states => [
main => [qw/
accept_new_client
accept_failed
client_input
/],
]
);
POE::Kernel->run;
sub server_start {
my $poe = sweet_args;
$poe->heap->{listener}
= POE::Wheel::SocketFactory->new(
BindPort
=> 9999,
Reuse
=> 'on',
SuccessEvent => 'accept_new_client',
FailureEvent => 'accept_failed',
);
}
sub accept_new_client {
my $poe = sweet_args;
my $wheel = POE::Wheel::ReadWrite->new(
Handle
=> $poe->args->[0],
InputEvent => 'client_input',
);
$poe->heap->{wheel}->{$wheel->ID} = $wheel;
}
sub client_input {
my $poe = sweet_args;
my $line
= $poe->args->[0];
my $wheel_id = $poe->args->[1];
$poe->heap->{wheel}->{$wheel_id}->put($line);
}
sub accept_failed {}
Again, Parallel echo server using POE
use POE qw/Sugar::Args Component::Server::TCP/;
POE::Component::Server::TCP->new(
Port => 9999,
ClientInput => sub {
my $poe = sweet_args;
my $input = sweet_args->args->[0];
$poe->heap->{client}->put($input);
},
);
POE::Kernel->run();
POE has many components on CPAN
 PoCo::IRC
 PoCo::Client::HTTP
 PoCo::Server::HTTP
 PoCo::EasyDBI
 PoCo::Cron
 PoCo::Client::MSN
 PoCo::Client::Linger
...
using POE with epoll
 just use POE::Loop::Epoll
 use POE qw/Loop::Epoll/;
Event::Lib
 libevent(3) wrapper
 libevent is used by memcached
 libevent provides:
 event-based programming
 devpoll, kqueue, epoll, select, poll
abstraction
 Similar to Event.pm
 Simple
echo server using Event::Lib
my $server = IO::Socket::INET->new(...) or die $!;
$server->blocking(0);
event_new($server, EV_READ|EV_PERSIST, \&event_accepted)->add;
event_mainloop;
sub event_accepted {
my $event = shift;
my $server = $event->fh;
my $client = $server->accept;
$client->blocking(0);
event_new($client, EV_READ|EV_PERSIST, \&event_client_input)->add;
}
sub event_client_input {
my $event = shift;
my $client = $event->fh;
$client->sysread(my $buffer, 1024);
event_new($client, EV_WRITE, \&event_client_output, $buffer)->add;
}
sub event_client_output { ... }
Result of strace on Linux 2.6
epoll_wait(4, {{EPOLLIN, {u32=135917448, u64=135917448}}},
1023, 5000) = 1
gettimeofday({1167127923, 189763}, NULL) = 0
read(7, "gho\r\n", 1024)
= 5
epoll_ctl(4, EPOLL_CTL_MOD, 7, {EPOLLIN|EPOLLOUT,
{u32=135917448, u64=135917448}}) = 0
Danga::Socket
 by Brad Fitzpatrick - welcome to
Japan :)
 It also provides event-driven
programming and epoll abstraction
 Perlbal, MogileFS
Summary
 For Network programming, need a little
knowledge about OS, especially process
scheduling, I/O and implementation of
TCP/IP.
 Use modern libraries/frameworks to keep
your codes simple.
 Perl has many good libraries for UNIX
Network Programming.
Thank you!