サーバーの作り方1 (基本Perl)
某プロジェクトとかのためにサーバーの作り方を書こうと思ったんですが、割と一般的な知識で難しい内容でもないので、公開でやります。
正直細かいことは知りませんが、逆に細かいことを考えなくてもサーバーくらい作れるよという感じで書いていきます。
基本的なサーバーの書き方
Perlでサーバーを書くのは難しくありません。
ソケットを作り、ポートに結びつけ(bind)、接続を待ち(listen)、接続を受け付け(accept)、来たら適当に処理するだけです。
このときlistenまでは一回だけやればよく、acceptをwhileでループして、何度も接続をうけることができます。
実際のechoサーバー(=入力された内容を反射するだけのサーバー)は以下のようになります。(たったこれだけです)
use Socket; #ソケット作成 socket(SERVER, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or die; #ポートに結びつけ 6666番 bind(SERVER, pack_sockaddr_in(6666, INADDR_ANY)) or die; #接続を待つ listen(SERVER, SOMAXCONN) or die; #接続が来たら中を実行、それまで待機 while(my $sockaddr = accept(CLIENT, SERVER)){ my $org_handle = select(CLIENT); $| = 1; select($org_handle); while(<CLIENT>){ print CLIENT $_; } }
これを起動した後、telnet localhost 6666でサーバーに接続してみてください。
入力した内容がそのまま帰ってくるはずです。
複数の接続を処理できるサーバーの書き方
telnetを同時に起動して試してみれば分かりますが、上のサーバーでは複数の接続を同時に捌くことはできません。
while(accept)のループで一つの接続のみを処理していて、そこを出るまでは次の接続を処理することができないからです。
自鯖のHTTPd位ならともかく、チャットやオンラインゲームなど、根本的に複数人が接続し続けるタイプのサービスは、そのままでは作ることができません。
それに対処するためには、いくつか方法があります。
- ポーリングする(select,poll,epoll,kqueue等)
- 接続を子プロセスに投げる(fork)
- 接続をスレッドに投げる(threads)
2,3も有用ですが、適当に作るとメモリを食ってしまい、まじめに作ると多少面倒なので、とりあえず1のポーリングでの対処を説明します。
これは、新しいプロセスやスレッドを作らず、現在のプロセス一つで複数の接続を扱う方法です。
ソケットを用いた双方向の通信は、ローカルの処理速度と比べるとスカスカです。なので、ソケット1に送信し、レスポンスを待つ間にソケット2にも送信、さらにソケット3から受信して、ソケット1から受信と言う風に、スカスカの間隔を上手く処理すれば、複数の通信を同時にこなすことができます。
これには、オーソドックスにはselectというシステムコールを使います。(ただしこれは遅いので、接続者が増えてきたらepoll/kqueueを用います。これは後述)
while(select)で送受信の準備ができたソケット(というかハンドル)を調べて、順次処理します。こいつらを処理してる間に他のソケットが準備できて、次のループが回るという寸法です。
select関数を生で扱うと大変面倒なので、IO::Selectを用いたサンプルコードを書きます。
ついでにSocketもIO::Socketを用います。この手のモジュールは是非活用しましょう
use IO::Select; use IO::Socket; #さっきのbindやらlistenやらを、下の一行でかけます my $listener = new IO::Socket::INET(Listen => 1, LocalPort => 6666, ReuseAddr => 1); #selectorにリスナーソケットを追加。あとで更に、クライアントのソケットも投げ込みます。 my $selector = new IO::Select( $listener ); #読み込み可能なハンドル@readyを入手。あるまでブロック while(my @ready = $selector->can_read) { foreach my $fh (@ready) { if($fh == $listener) { # 新規接続を受け付け、selectorに追加 my $new = $listener->accept; $selector->add($new); } else { # 入力を処理する。切断もここで処理 my $input = <$fh>; if(defined($input)){ print $fh $input; } else{ $selector->remove($fh); $fh->close; } } } }
telnetl localhost 6666を2,3個立ち上げて試してみてください。同時に処理できているはずです。
これはechoサーバーの例ですが、$inputを処理するあたりを書き換えるだけで、どんなサーバーにでもなります。
ね?簡単でしょ?