低水準サーバソケット
■この章で解説する組み込み関数
bind, listen, accept
■この章でできること
・低水準サーバソケット

 ここではPerlの組み込み関数の低水準なソケットを使って、サーバソケット作る方法を解説します。Perlでのソケットの使用方法や、クライアントについては、低水準クライアントソケットをご覧ください。

 では、早速サンプルソースを見てみましょう。

#!/usr/bin/perl

#############################################
# 低水準サーバソケットのサンプル
# Author: "Perl Programming Tips"
#############################################

use Socket;

# TCPのプロトコル番号を取得
$tcpProtoNo = getprotobyname('tcp');

# SOCKET作成
socket(HSOCK, PF_INET, SOCK_STREAM,
    $tcpProtoNo) || die("socket() fail:$!\n");

# パックされたネットワークアドレスを作成
$address = sockaddr_in(3000, INADDR_ANY);

# ローカルマシン上のポートにバインド
bind(HSOCK, $address) || die("bind() fail:$!\n");

# OSへの通知と待ち行列の最大数設定
listen(HSOCK, 1);

# クライアントの接続待ち
accept(HCNCT, HSOCK) || die("accept() fail:$!\n");

# メッセージ受信
if (recv(HCNCT, $message, 30, MSG_WAITALL) == undef)
{
    die("recv() fail\n");
}
print "$message\n";

# メッセージ送信
if (send(HCNCT, 'Message from server to client.', 0) == undef)
{
    die("send() fail:$!\n");
}

# SOCKETクローズ
close(HSOCK);

 まずはSocketモジュールをインポートし、TCPのプロトコル番号を取得、socket関数でソケットを作成します。ここまではクライアントと同じなので説明は省略します。

 次にネットワークアドレスを作成します。sockaddr_inに渡すポート番号は、クライアントとの通信で使用するポート番号(クライアント側の指定と同じ番号)を指定します。サーバの場合はアドレスにローカルホストを指定することになります。このため、sockaddr_inの第2引数にはINADDR_ANYを指定します。これを指定すると適切なアドレスを選択してくれます。

 次はbind関数で、作成したソケットをローカルマシン上のポートにバインドします。指定したポートがすでに使われていた場合はここでエラーになります。

bind
ソケットにアドレス、ポート番号をバインドする。
書式
bind socket, address
引数
socket:
ファイルハンドル
address:
ソケットタイプ別にパックされたネットワークアドレス
戻り値
成功した場合は真、失敗した場合は偽を返し$!をセットする。

 次はlisten関数で、サーバプロセスをlisten状態に移行させます。listen関数の第2引数には、複数のクライアントが同時に接続要求をしてきた時の、待ち行列の最大数を指定します。ここではクライアントは1つだけなので1を指定しています。複数のクライアントが接続する可能性がある場合は、サーバの負荷などを考慮して適切な数を設定します。

listen
指定したソケットでコネクションを受け付けることをOSに伝える。同時に接続待ち行列の最大数を指定する。
書式
listen socket, queuesize
引数
socket:
ファイルハンドル
queuesize:
コネクションの待ち行列の最大数
戻り値
なし

 以上で準備ができたので、accept関数でクライアントの接続を受け付けます。accept関数はクライアントの接続要求があるまでブロックします。つまりクライアントが接続要求を出すまで関数は制御を戻しません。

 accept関数の第1引数には、クライアントとの通信に使用する新しいファイルハンドルを指定します。これは1クライアントについて1つずつ使用するので、複数のクライアントが同時に接続する場合は、acceptごとに別のファイルハンドルを指定する必要があります。

accept
クライアントからのソケット接続を受け付ける。
書式
accept newsocket, genericsocket
引数
newsocket:
クライアントとの接続に使用するファイルハンドル
genericsocket:
サーバのネットワークアドレスの1つにバインドされたファイルハンドル
戻り値
成功した場合は接続した相手のアドレス、失敗した場合は偽を返し$!をセットする。

 これで、クライアントとの接続が確立されたので、あとは自由にデータの送受信ができるようになります。送受信の方法については、クライアントと同じなので説明は省略します。

 ここで、サーバ特有の実装について触れておきます。クライアント/サーバモデルの場合は、通常1つのサーバプロセスに対して複数のクライアントがコネクションを張る形になります。このような作りにするためには、サーバプロセスはマルチスレッドで実装する必要があります。上のサンプルプログラムではaccept関数を抜けた後はもうaccept関数は呼ばれないので、別のクライアントは接続できません。

 マルチ接続をサポートする場合は、accept関数でクライアントと接続をしたら、そのクライアントとの通信用にスレッドを1つ起動し、メインのスレッドは再びaccept関数で接続待ち状態に戻るようにします。このようにすると、複数のクライアントが同時に通信でき、さらに常時新しい接続も受け付けられるようになります。

 最後にclose関数でソケットのクローズ処理を行います。ここで、クライアントとの接続をクローズしていない(close(HCNCT)を実行していない)ところにも注目する必要があります。クライアント側とサーバ側のどちらから切断するのかというのは、クライアント/サーバ間の通信仕様によるので一概には言えませんが、一般的にはクライアント側からコネクションを切断します。ただし、サーバプロセスをシャットダウンする場合などは、サーバ側から切断することもあります。