サーバーの作り方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位ならともかく、チャットやオンラインゲームなど、根本的に複数人が接続し続けるタイプのサービスは、そのままでは作ることができません。
それに対処するためには、いくつか方法があります。

  1. ポーリングする(select,poll,epoll,kqueue等)
  2. 接続を子プロセスに投げる(fork)
  3. 接続をスレッドに投げる(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を処理するあたりを書き換えるだけで、どんなサーバーにでもなります。
ね?簡単でしょ?

基本的な考え方

ここでは一般的なTCPを使ったサーバーを扱います。
サーバーの動作を簡略化すると、

  1. 起動
  2. 接続を受け付ける準備(ソケット作成、ポートと結びつけ等)
  3. クライアントの接続を受け付け
  4. なにか処理(送信→受信→送信→....を繰り返すだけです)
  5. クライアント切断
  6. 3にもどる

これだけです。
サーバーによって違うのは4の所だけで、それ以外はほとんど同じ構造をしています。