1 / 47

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.

Télécharger la présentation

Perl and UNIX Network Programming

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

  2. 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

  3. Agenda • UNIX network programming basics with Perl • I/O multiplexing • Perl libraries for modern network programming

  4. UNIX network programming basics with Perl

  5. BSD Socket API with C int 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); } }

  6. BSD Socket API • socket() • struct sockaddr_in • bind() • listen() • accept() • read() / write() • close()

  7. Perl Network Programming • TMTOWTDI • less code • CPAN • performance is good enough • right design >> ... >> language advantage

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

  9. 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;

  10. 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

  11. 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 PID WIDE-WCHAN-COLUMN TIME COMMAND S+ 8671 read_chan 00:00:00 perl

  12. 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 Kernel-Mode Hardware Interruption. Hardware (HDD) ref: 『Linux カーネル2.6解読室』 p.32

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

  14. We need parallel processing • fork() • threads • Signal I/O • I/O Multiplexing • Asynchronous I/O

  15. I/O multiplexing

  16. 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.

  17. select(2) accepted connection #2 listening socket accepted connection #1 1. ready! select(2) 3. ok, I'll try to accept() 2. now listening socket is ready to accept a new connection. caller

  18. 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

  19. IO::Select SYNOPSYS use IO::Select; $s = IO::Select->new(); $s->add(\*STDIN); $s->add($some_handle); @ready = $s->can_read($timeout); # block

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

  21. And more things we must think... • blocking when syswrite() • use non-blocking socket • Line-based I/O • select(2) disadvantage

  22. non-blocking socket + Line-based I/O use 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

  23. 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

  24. select(2) internals process FD_ISSET select(2) select(2) fd fd fd fd fd fd fd fd fd copy copy fd fd fd fd fd fd I/O event kernel ref: http://osdn.jp/event/kernel2003/pdf/C06.pdf

  25. Modern UNIX APIs • epoll • Linux 2.6 • /dev/kqueue • BSD • devpoll • Solaris

  26. 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

  27. epoll internals process epoll_create() epoll_wait() epoll_ctl(ADD) epoll_ctl(ADD) epoll_ctl(ADD) fd table fd fd fd fd fd fd I/O event kernel ref: http://osdn.jp/event/kernel2003/pdf/C06.pdf

  28. epoll on perl • Sys::Syscall • epoll • sendfile • IO::Epoll • use IO::Epoll qw/:compat/

  29. Perl libraries for modern network programming

  30. Libraries for Perl Network Programming • TMTOWTDI • POE • Event::Lib • Danga::Socket • Event • Stem • Coro ...

  31. They provides: • Event-based programming for parallel processing • system call abstraction • select(2) / poll(2) / epoll / kqueue(2) / devpoll

  32. POE • "POE is a framework for cooperative, event driven multitasking in Perl. " • POE has many "components" on CPAN • I'm lovin' it :)

  33. 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;

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

  35. Results % perl hello_poe2.pl input> naoya Hello, naoya input> hatena Hello, hatena input> foo bar Hello, foo bar input>

  36. 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}

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

  38. 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 {}

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

  40. POE has many components on CPAN • PoCo::IRC • PoCo::Client::HTTP • PoCo::Server::HTTP • PoCo::EasyDBI • PoCo::Cron • PoCo::Client::MSN • PoCo::Client::Linger ...

  41. using POE with epoll • just use POE::Loop::Epoll • use POE qw/Loop::Epoll/;

  42. 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

  43. 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 { ... }

  44. 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

  45. Danga::Socket • by Brad Fitzpatrick - welcome to Japan :) • It also provides event-driven programming and epoll abstraction • Perlbal, MogileFS

  46. 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.

  47. Thank you!

More Related