47
Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Embed Size (px)

Citation preview

Page 1: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Perl and UNIX Network Programming

Naoya Itonaoya at hatena.ne.jp

Page 2: 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

Page 3: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Agenda

UNIX network programming basics with Perl

I/O multiplexing Perl libraries for modern

network programming

Page 4: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

UNIX network programming basics with Perl

Page 5: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

BSD Socket API with Cint main (void) { int listenfd, connfd; struct sockaddr_in servaddr; char 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); }}

Page 6: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

BSD Socket API

socket() struct sockaddr_in bind() listen() accept() read() / write() close()

Page 7: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Perl Network Programming

TMTOWTDI less code CPAN performance is good enough

right design >> ... >> language advantage

Page 8: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

BSD Socket API with Perl

#!/usr/local/bin/perluse 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;}

Page 9: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

use IO::Socket

#!/usr/local/bin/perluse 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;

Page 10: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

blocking on Network I/O

while (1) { my $client = $server->accept; while ($client->sysread(my $buffer, 1024)) { # block $client->syswrite($buffer); } $client->close;}

client #1

server

client #2

read(2)

accept(2)

listen queue

I can't do

Page 11: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

busy loop / blocking

while (1) { $i++ }

while (1) { STDIN->getline }

STAT PID WIDE-WCHAN-COLUMN TIME COMMANDS+ 8671 read_chan 00:00:00 perl

% ps -e -o stat,pid,wchan=WIDE-WCHAN-COLUMN,time,comm

STAT PID WIDE-WCHAN-COLUMN TIME COMMANDR+ 18684 - 00:00:38 perl

Page 12: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Linux internals

process

libc.so

Hardware (HDD)

ext3

device driver

ref: 『 Linux カーネル 2.6 解読室』 p.32

read(2)

buffer

fread()

buffer

Kernel-Mode

system call

switch toKernel-Mode.User-processgoes sleep.

Hardware Interruption.

vfs

TASK_RUNNING

TASK_UNINTERRUPTIBLE

Page 13: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Again: blocking

while (1) { my $client = $server->accept; while ($client->sysread(my $buffer, 1024)) { # block $client->syswrite($buffer); } $client->close;}

Page 14: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

We need parallel processing

fork() threads Signal I/O I/O Multiplexing Asynchronous I/O

Page 15: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

I/O multiplexing

Page 16: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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.

Page 17: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

select(2)

listeningsocket

acceptedconnection

#1

acceptedconnection

#2

select(2)

caller

1. ready!

2. now listening

socket is ready to accept a new connection.

3. ok, I'll try to accept()

Page 18: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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

Page 19: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

IO::Select SYNOPSYS

use IO::Select;

$s = IO::Select->new();

$s->add(\*STDIN);$s->add($some_handle);

@ready = $s->can_read($timeout); # block

Page 20: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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; } } }}

Page 21: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

And more things we must think...

blocking when syswrite() use non-blocking socket

Line-based I/O select(2) disadvantage

Page 22: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

non-blocking socket + Line-based I/Ouse POSIX;use IO::Socket;use IO::Select;use Tie::RefHash;

my $server = IO::Socket::INET->new(...);$server->blocking(0);

my (%inbuffer, %outbuffer, %ready);tie %ready, "Tie::RefHash";

my $select = IO::Select->new($server);while (1) { foreach my $client ( $select->can_read(1) ) { handle_read($client); }

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); }}

sub handle_error { my $client = shift;

delete $inbuffer{$client}; delete $outbuffer{$client}; delete $ready{$client};

$select->remove($client); close $client;}

sub handle_read { my $client = shift; if ($client == $server) { my $new_client = $server->accept(); $new_client->blocking(0); $select->add($new_client); return; }

my $data = ""; my $rv = $client->recv($data, POSIX::BUFSIZ, 0);

unless (defined($rv) and length($data)) { handle_error($client); return; }

$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; }

if ($rv == length( $outbuffer{$client}) or $! == POSIX::EWOULDBLOCK) { substr( $outbuffer{$client}, 0, $rv ) = ""; delete $outbuffer{$client} unless length $outbuffer{$client}; return; } handle_error($client);}

oops

Page 23: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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

Page 24: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

select(2) internals

ref: http://osdn.jp/event/kernel2003/pdf/C06.pdf

process

kernel

fd fd fd

select(2)

fd fd fd

copy

fd

I/O event

fdfd

fdfdfd

copy

select(2)

fdfdfd

FD_ISSET

Page 25: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Modern UNIX APIs

epoll Linux 2.6

/dev/kqueue BSD

devpoll Solaris

Page 26: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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

Page 27: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

epoll internals

ref: http://osdn.jp/event/kernel2003/pdf/C06.pdf

epoll_create()

fd table

epoll_ctl(ADD) epoll_ctl(ADD

) epoll_ctl(ADD)

fd fd fd

epoll_wait()

fd

I/O event

fdfd

process

kernel

Page 28: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

epoll on perl

Sys::Syscall epoll sendfile

IO::Epoll use IO::Epoll qw/:compat/

Page 29: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Perl libraries for modern network programming

Page 30: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Libraries for Perl Network Programming

TMTOWTDI POE Event::Lib Danga::Socket Event Stem Coro...

Page 31: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

They provides:

Event-based programming for parallel processing

system call abstraction select(2) / poll(2) / epoll /

kqueue(2) / devpoll

Page 32: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

POE

"POE is a framework for cooperative, event driven multitasking in Perl. "

POE has many "components" on CPAN

I'm lovin' it :)

Page 33: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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'), # async / FIFO }, hello => sub { STDOUT->print("Hello, POE!"); }, },);POE::Kernel->run;

Page 34: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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'); } },);

Page 35: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Results

% perl hello_poe2.plinput> naoyaHello, naoyainput> hatenaHello, hatenainput> foo barHello, foo barinput>

Page 36: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Results of strace

% strace -etrace=select,read,write -p `pgrep perl`Process 8671 attached - interrupt to quitselect(8, [0], [], [], {3570, 620000}) = 1 (in [0], left {3566, 500000})read(0, "naoya\n", 4096) = 6write(1, "Hello, naoya\n", 13) = 13select(8, [0], [], [], {0, 0}) = 0 (Timeout)write(1, "input> ", 7) = 7select(8, [0], [], [], {3600, 0}) = 1 (in [0], left {3595, 410000})read(0, "hatena\n", 4096) = 7write(1, "Hello, hatena\n", 14) = 14select(8, [0], [], [], {0, 0}) = 0 (Timeout)write(1, "input> ", 7) = 7select(8, [0], [], [], {3600, 0}) = 1 (in [0], left {3598, 860000})read(0, "foobar\n", 4096) = 7write(1, "Hello, foobar\n", 14) = 14select(8, [0], [], [], {0, 0}) = 0 (Timeout)write(1, "input> ", 7) = 7select(8, [0], [], [], {3600, 0}

Page 37: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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> '); } },);

...

Page 38: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Parallel echo server using POEPOE::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 {}

Page 39: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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();

Page 40: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

POE has many components on CPAN

PoCo::IRC PoCo::Client::HTTP PoCo::Server::HTTP PoCo::EasyDBI PoCo::Cron PoCo::Client::MSN PoCo::Client::Linger...

Page 41: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

using POE with epoll

just use POE::Loop::Epoll use POE qw/Loop::Epoll/;

Page 42: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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

Page 43: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

echo server using Event::Libmy $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 { ... }

Page 44: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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

Page 45: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Danga::Socket

by Brad Fitzpatrick - welcome to Japan :)

It also provides event-driven programming and epoll abstraction

Perlbal, MogileFS

Page 46: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

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.

Page 47: Perl and UNIX Network Programming Naoya Ito naoya at hatena.ne.jp

Thank you!