低水準クライアントソケット
■この章で解説する組み込み関数
connect, getprotobyname, send, recv, socket, close
■この章で解説するモジュール
Socket (sockaddr_in, inet_aton)
■この章でできること
・低水準クライアントソケット

 Perlでソケット通信をする場合、C言語やUNIXのシステムコールとよく似たインターフェースの低水準なソケットを使う方法と、よりインターフェースを単純化したIO::Socketモジュールを使う方法があります。ここでは前者の低水準なソケットを使って、クライアントソケット作る方法を解説します。

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

#!/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, inet_aton('localhost'));

# SOCKET接続
connect(HSOCK, $address) || die("connect() fail:$!\n");

# メッセージ送信
send(HSOCK, 'Message from client to server.', 0);

# メッセージ受信
recv(HSOCK, $message, 30, MSG_WAITALL);
print "$message\n";

# SOCKET切断
close(HSOCK);

 まずはuse SocketでSocketモジュールをインポートしておきます。Perlの標準関数に組み込まれているソケット関連の関数は、connectやrecv、sendなどのプリミティブなものだけです。Socketモジュールはその他の補助的な関数や定義などが含まれています。なので、低水準なソケットを実装する場合もSocketモジュールの使用はほぼ必須になります。

 最初にsocket関数に渡すためのTCPのプロトコル番号を取得しておきます。ソケットにはストリームソケット、ダイアグラムソケットがあり、それぞれTCP、UDPプロトコルを使います。ここではストリームソケットを使うので、getprotobynameでTCPのプロトコル番号を取得しています。

getprotobyname
プロトコル名を対応するプロトコル番号に変換する。
書式
getprotobyname name
引数
name:
プロトコル名
戻り値
リストコンテキストでは($name, $aliases, $protocol_number)、スカラーコンテキストではプロトコル番号

 次はsocket関数でソケットをオープンし、ファイルハンドルに結びつけます。以降のソケット操作はこのファイルハンドルを指定することになります。socket関数の第2引数で指定しているのはソケットのネットワークドメインです。これはインターネットドメイン、UNIXドメインがあり、それぞれPF_INET、PF_UNIXを指定します。ここではPF_INETを指定しています。第3引数はストリームソケットの場合SOCK_STREAM、ダイアグラムソケットの場合SOCK_DGRAMを指定します。

socket
ソケットをオープンし、ファイルハンドルに結びつける。
書式
socket socket, domain, type, protocol
引数
socket:
ファイルハンドル
domain:
ソケットのネットワークドメイン(インターネットドメインの場合PF_INET、UNIXドメインの場合PF_UNIX)
type:
ソケットのタイプ(ストリームソケットの場合SOCK_STREAM、ダイアグラムソケットの場合SOCK_DGRAM)
protocol:
プロトコル番号
戻り値
成功した場合は真、失敗した場合は偽

 この後はconect関数でソケットを接続しますが、その前にSocketモジュールの機能を使って接続先のアドレス、ポート番号の情報を作成しておきます。これはこれはインターネットドメインの場合sockaddr_in関数、UNIXドメインの場合sockaddr_ux関数を使用します。ポート番号はあらかじめサーバ、クライアント間でどの番号を使うか決めておきましょう。

Socket::sockaddr_in
指定したポート番号、アドレスをpackし、AF_INETをセットしたソケットアドレス構造体を返す。
書式
sockaddr_in port, addr_string
引数
port:
ポート番号
addr_string:
4バイトのアドレス文字列(inet_atonが返すもの)
戻り値
packされたソケットアドレス構造体

Socket::inet_aton
指定したアドレスを4バイトのアドレス文字列に変換する。
書式
inet_aton hostname
引数
hostname:
www.g-ishihara.comや192.168.11.1のようなホスト名
戻り値
4バイトのアドレス文字列、ホスト名が解決できない場合undef

 これで準備ができたので、connect関数でサーバに接続します。当然、この時点でサーバ側がクライアントの接続待ち状態になっていないと接続できません。

connect
ソケットを接続し、コネクションを確立する。
書式
connect socket, address
引数
socket:
ファイルハンドル
address:
ソケットタイプ別にパックされたネットワークアドレス
戻り値
成功した場合は真、失敗した場合は偽

 コネクションが確立された後は、クライアント、サーバ間で自由にデータの送受信ができます。これにはいくつか方法がありますが、send、recv関数を使うのが簡単です。

send
ソケットに対してメッセージを送信する。
書式
send socket, msg, flag, [dest]
引数
socket:
ファイルハンドル
msg:
送信するメッセージ
flag:
sendシステムコールと同様のフラグ(manページなどを参照)
dest:
接続していないソケットにsendする場合に送り先を指定する。
戻り値
成功した場合は送信したバイト数、失敗した場合は未定義値を返し$!をセットする。

recv
ソケットからメッセージを受信する。
書式
recv socket, $var, len, flag
引数
socket:
ファイルハンドル
$var:
受信バッファ
len:
受信するバイト数
flag:
recvシステムコールと同様のフラグ(manページなどを参照)
戻り値
成功した場合は送信元のアドレス、失敗した場合は未定義値

 ここで、ソケットのブロッキングについて触れておきましょう。ソケットはストリームソケットの場合、クライアント/サーバ間でsend、recvを使ってメッセージの送受信をしますが、これは同時に実行されるとは限りません。送信の方が早かった場合は、受信側のマシンの受信バッファに一時的にメッセージが置かれるので、たいていの場合(バッファがあふれたりしなければ)問題はありません。しかし逆に、まだsendで送信していないうちにrecvで受信しようとすると、失敗します。

 これを避けるために、受信待ちをする仕組みが必要になってきます。つまり相手が送信してくるまで受信の口を開けて待っているということです。このように待ち状態にして同期されることをブロッキングといいます。

 ソケットのメッセージ送受信でこれを実現する簡単な方法は、recv関数でMSG_WAITALLを指定することです。これを指定した場合、recv関数は指定したバイト数を受信し終わるまでは制御を戻しません。

 ただし、この方法は何らかの原因で指定したバイト数を受信できなかった場合は、永久にブロックすることになるのでとても危険です。あくまで簡易的な方法だと考えたほうがいいでしょう。では安全に作るにはどうすればいいかというと、ここでは解説しませんが、select関数などを使ってブロッキングにタイムアウト処理を入れるのが一般的です。

 では、最後にclose関数でソケットのクローズ処理を行います。

close
ファイルハンドルに結び付けられたファイル、ソケットなどをクローズする
書式
close filehandle
引数
filehandle:
ファイルハンドル
戻り値
なし