Академический Документы
Профессиональный Документы
Культура Документы
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
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
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;
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) I can't
do
client #1 client #2
busy loop / blocking
% ps -e -o stat,pid,wchan=WIDE-WCHAN-COLUMN,time,comm
system call
vfs
ext3 TASK_UNINTERRUPTIBLE
fork()
threads
Signal I/O
I/O Multiplexing
Asynchronous I/O
I/O multiplexing
I/O Multiplexing
accepted accepted
listening connection
connection
socket #1 #2
1.
ready! select(2
) 3. ok, I'll try
to accept()
2. now
listening caller
socket is ready
to accept a new
connection.
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);
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
sub handle_read {
use POSIX;
my $client = shift;
use IO::Socket;
if ($client == $server) {
use IO::Select;
my $new_client = $server->accept();
use Tie::RefHash;
$new_client->blocking(0);
$select->add($new_client);
my $server = IO::Socket::INET->new(...);
return;
$server->blocking(0);
}
my $select = IO::Select->new($server);
unless (defined($rv) and length($data)) {
while (1) {
handle_error($client);
foreach my $client ( $select->can_read(1) ) {
return;
oops
handle_read($client);
}
}
$inbuffer{$client} .= $data;
foreach my $client ( keys %ready ) {
while ( $inbuffer{$client} =~ s/(.*\n)// ) {
foreach my $request ( @{ $ready{$client} } ) {
push @{$ready{$client}}, $1;
$outbuffer{$client} .= $request;
}
}
}
delete $ready{$client};
}
sub handle_write {
my $client = shift;
foreach my $client ( $select->can_write(1) ) {
return unless exists $outbuffer{$client};
handle_write($client);
}
my $rv = $client->send($outbuffer{$client}, 0);
}
unless (defined $rv) {
warn "I was told I could write, but I can't.\n";
sub handle_error {
return;
my $client = shift;
}
delete $inbuffer{$client};
if ($rv == length( $outbuffer{$client}) or $! == POSIX::EWOULDBLOCK)
delete $outbuffer{$client}; {
delete $ready{$client}; substr( $outbuffer{$client}, 0, $rv ) = "";
delete $outbuffer{$client} unless length $outbuffer{$client};
$select->remove($client); return;
close $client; }
} handle_error($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 User-
Land
select(2) internals
process
copy copy
fd fd fd fd fd fd
I/O event
kerne
l
ref: http://osdn.jp/event/kernel2003/pdf/C06.pdf
Modern UNIX APIs
epoll
Linux 2.6
/dev/kqueue
BSD
devpoll
Solaris
epoll(4)
proces
s
epoll_ctl(ADD
) epoll_ctl(ADD
epoll_create() ) epoll_ctl(ADD epoll_wait()
)
fd table fd fd fd fd fd fd
I/O event
kerne
l
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:
POE::Session->create(
inline_states => {
_start => sub {
my $poe = sweet_args;
$poe->kernel->yield('hello'), # async / FIFO
},
hello => sub {
STDOUT->print("Hello, POE!");
},
},
);
POE::Kernel->run;
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 hello_poe2.pl
input> naoya
Hello, naoya
input> hatena
Hello, hatena
input> foo bar
Hello, foo bar
input>
Results of strace
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( sub accept_new_client {
inline_states => { my $poe = sweet_args;
_start => \&server_start, my $wheel = POE::Wheel::ReadWrite->new(
}, Handle => $poe->args->[0],
package_states => [ InputEvent => 'client_input',
main => [qw/ );
accept_new_client $poe->heap->{wheel}->{$wheel->ID} = $wheel;
accept_failed }
client_input
/],
] sub client_input {
); my $poe = sweet_args;
POE::Kernel->run; my $line = $poe->args->[0];
my $wheel_id = $poe->args->[1];
sub server_start { $poe->heap->{wheel}->{$wheel_id}-
>put($line);
my $poe = sweet_args; }
$poe->heap->{listener}
= POE::Wheel::SocketFactory->new( sub accept_failed {}
BindPort => 9999,
Reuse => 'on',
SuccessEvent => 'accept_new_client',
FailureEvent => '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
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);
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;
}