6.TCP/IP ソケットの利用

この章では TCP/IP を利用したプログラミングについて解説する。
ACE ツールキットではプロセス間コミュニケーションのためにラッパーファサードが用意されている。これらはインターフェースが統一されているため、後からコードを変更する場合でも比較的簡単に行うことができる。
この置換えに関連するラッパーの種類は以下のようになる。

この章ではそれぞれ以下のクラスを利用することになる。

6.1 シンプルなクライアント

BSD ソケットプログラミングにおいては、socket() や connect() のような低レベルなシステムコールを使わなければならない。直接これらを使ってプログラムするには次のような理由でトラブルが多い。
ACE では、これらの問題をオブジェクト指向なアプローチで解決し、より簡単に、より明快に、よりポータブルにしている。

まずは UNIX network Programming から簡単なクライアントのコードを抜粋してみる。p.125 にそれが記載されている。
これは移植性に欠けたコードである。最大の問題は windows では利用できない(おそらくコンパイルすら通らない)ことだ。
ACE では以下のようにしてプログラミングする。まずは接続先アドレスの指定方法だが、次の一行でよい。
ACE_INET_Addr srvr(50000, ACE_LOCALHOST);
ACE_INET_Addr クラスは ACE_Addr ファミリのメンバーであり、この他に ACE_UNIX_Addr、ACE_SPIPE_Addr、ACE_FILE_Addr などが存在する。どれも自分が取り扱うリソースについての知識を持っており、ACE_Addr クラスメンバを通して利用される。
この例の場合は2つの引数を伴うコンストラクタを用いて、unsigned short によるポート番号(訳注:htons() によるバイトオーダー調整が不要なことに注意)と char[] によるホスト名を受け取って、自動的に内部で必要な sockaddr_in もしくは sockaddr_in6(IPv6の場合)を準備する。
その他の有用なコンストラクタについては ace/INET_Addr.h に記述されており、ACE_INET_Addr のクラスリファレンスから参照できる。
続いてサーバへの接続を行う。ここで利用するのは実際の通信に利用する ACE_SOCK_Stream と接続の確立時に利用する ACE_SOCK_Connector である。
ACE_SOCK_Connector connector; ACE_SOCK_Stream peer; if( 1- == connector.connect(peer, srvr)) { ACE_OS::exit(1); }
上の connect() メソッドで接続が確立できると、 peer が利用可能になる。次のようにして実際の送受信ができる。
peer.send_n( "uptime\n", 7 ); bc = peer.recv( buf, sizeof(buf) ); peer.close();
送受信メソッドについて詳しくはリファレンスを参照してほしい。上で使われている send_n() メソッドは指定したバイト数をきっちりと送信する。ソケットを直接利用した場合、ネットワークバッファの都合やら何やらで予定よりも少ないバイト数しか送られないことがある。この時、プログラマはデータポインタを進め、残ったデータについて再度送信を行わなければならない。ACE はこの繰り返しを内部的に処理し、全データを送信するか、途中で失敗するまで処理を行ってくれる。
受信には recv() メソッドを利用している。これは指定したバッファに指定サイズ以内のデータを受信して書き込む。受信バイト数が事前に分かっている場合には送信時と同様に short read を回避するための recv_n() メソッドがある。
p.127-129 に伝統的な版と ACE 版の全コードが掲載されている。

6.2 クライアントへの安定性の付加

ACE_INET_Addr はコンストラクタだけでなく set() メソッドによっても設定することができる。また、一つのオブジェクトを複数アドレスの指定に使い回すこともできる。
set() メソッドが -1 を返した場合にはエラー内容を ACE_OS::last_error() によって取得できる。ポータビリティのためにも、この関数を使ってエラーコードを取得するようにしよう。
続いて ACE_SOCK_Connector クラスについてだが、このクラスの connect() メソッドは ACE の標準に従い、成功時には 0、失敗時には -1 を返す。失敗した場合には前述の ACE_OS::last_error() でエラーコードを取得して適切に処理しよう。
また、ACE_SOCK_Connector コンストラクタに引数を渡すことで connect() メソッドの実行を省略することもできる。この場合にも失敗時のエラーチェックは必ず入れること。特に、コンストラクトの前に ACE_OS::last_error(0) でエラーコードをリセットしておかないと、接続ができたとしても以前の(誤った)エラーコードを受け取ってしまう場合がある。
接続時にタイムアウトを指定しておくこともできる。例は p.130-131 に載っている。
ほとんどの場合、クライアント側のポートは OS に自動的に決めてもらうことになる。この時のポート番号は ACE_Addr::sap_any を利用する。しかしながらクライアント側でもポート番号を指定したい場合もある。この場合には ACE_INET_Addr をもう一つ用意し、connect() の第4パラメータとして渡せばよい。
最後に ACE_SOCK_Stream であるが、これを用いた送受信時にもタイムアウトを設定することができる。方法は先程と同様で ACE_Time_Value オブジェクトに時間を設定し、メソッドに渡すだけである。
低レベルのソケットAPIを利用していた場合、readv/writev システムコールを使うことがあったかと思う。これも ACE では ACE_SOCK_Stream::sendv/recvv として用意されており、iovec クラスを準備してメソッドを呼べばよい。この例については p.133-134 に載っている。
また、もう一つの recvv() の使い方として、引数に iovec オブジェクトを渡すことで、自動的に読み込みバッファを割り当てて受信してくれるものがある。この手法は受信バイト数が不明な場合などにちょうど良いサイズのバッファを確保してくれるということで便利である。ただし、利用後にはバッファの領域を解放する責任を負うことになるので注意すること。

6.3 サーバの作り方

まずは ACE_INET_Addr オブジェクトに待ち受けるポート番号を設定する。そして ACE_SOCK_Acceptor の open() メソッドで実際の待ち受けを開始する。
ACE_INET_Addr port_to_listen("HAStatus"); ACE_SOCK_Acceptor acceptor; if( acceptor.open(port_to_listen, 1) == -1 ) { ACE_OS::exit(1); }
この時、二つ目に渡されている 1 は reuse_addr 引数で、一般的にはこれを 1(有効) にして、最近同じポートが使用されていても再利用するようにする。
続いて、実際にクライアントから接続要求が来るまで待つために accept() メソッドを呼ぶ。タイムアウトの指定も可能である。
ACE_SOCK_Stream peer; if( acceptor.accept(peer) == -1 ) { ACE_OS::exit(1); }
また、ACE_INET_Addr からは接続先のアドレスを文字列で取得することができる。
ACE_TCHAR peer_name[MAXHOSTNAMELEN]; peer_addr.addr_to_string(peer_name, MAXHOSTNAMELEN);
この peer_to_string() メソッドは三番目の引数を取ることができ、0 ならば IP-name:Port-Number を、1 ならば IP-Number:Port-Number を返すことになる。
この後の通信については、クライアントとサーバのプロトコルの問題となるので、プログラマが考慮することになる。この例ではクライアントから届いた文字列をそのまま送り返す。
シンプルなサーバのソースコード全文を p.138-139 に掲載してある。以降、このソースを元に更なる改善を行っていくこととする。

Index
Top