TCP/IPプログラムの問題

早いもので今年も残り1ヶ月少々となりました。本書は多くの皆様に手にしていただき本当に感謝しております。本書をきっかけに動き出したことも多く、WIDEプロジェクトへの参加や、プログラマーの仕事の魅力を広めなければという気持ちなど、多くの皆様との意見交換を元に動き始めました。

さて、何度かこのサイトでも書きましたが、私の4冊目の著書である、「C for Linux」の改訂版にあたる本が2007年11月20日に発売になりました。「C言語による実践Linuxシステムプログラミング」です。「C for Linux」の内容をベースに、カーネル2.6対応、IPv6、ラージファイル、日本語処理などを追加し、実践的サンプルはデータ同期システムを取り上げ、全てのソース・説明の見直しを行い、ようやく完成しました。出版契約を結んでから2ヶ月で原稿をまとめ、さらに1ヶ月で校正・見直しを行い、完成となった感じですが、「C for UNIX」「C for Linux」の延長ということで、さすがになかなか気分が乗らず、しかも本業も忙しく、今までの執筆で最も苦しい作業でした。何度も妥協しそうになったのですが、全て動作確認も行い、ソースの質はかなり上がっていると思います。この本の内容が大体使いこなせれば、ほとんどのC言語でのLinuxのプログラム開発を行う仕事ができるのではないかと思います。なお、「C for Linux」は絶版になりました。

これまで7年で7冊本を書きました。我ながらよく書いたものだと思います。なんとなく書くたびに苦しくなってきている気がします。自分自身の仕事の立場が変わってきたことも一因だと思いますし、皆様からのご意見・ご指摘から、もっとしっかり書かねば、と思うようになってきているのも理由でしょう。学歴も所属組織も誇れない私としては、本を書くことで自分自身を公の場にさらけ出し、それと引き換えに信用や評価を少しずつ得られたのではないかと思います。自社での立場も社外の方との関係も、本を書く前後でかなり変わりました。学生時代にサボったマイナスを執筆で何とか取り返しつつあるという感じでしょう。今の年齢になると、積み重ねの大切さを痛感しますし、学生時代からの人脈も大切だと感じます。私はそれらをほとんど放棄した状態で社会に出て、一人でもがきながらずいぶん遠回りをした気がします。自分で痛い目にあわなければわからないという面も多いと思いますが、せめて若い方のヒントになればと、これからもいろいろな情報を提供すべきと思いますし、そうやって自分も高めていければと考えています。

「TCP/IPプログラムの問題」

さて、今回はTCP/IPを使うプログラムの問題について紹介しましょう。TCP/IPは今や最も多く使われるネットワークプロトコルだと思いますが、実はプログラミング上の注意点を理解せずに開発してしまうことが非常に多いのです。私は仕事でネットワークプログラミングをたくさん手がけてきましたが、他社の製品やシステムと接続してみるとびっくりするほど毎回のように問題がありました。

TCP/IPはプログラムではサーバ側が接続受付を行い、クライアント側からコネクトして接続を確立して、その後双方向で通信を行い、使用が終わったらクローズする、という感じなのですが、接続というのが頭の中のイメージとしては安心感のあるものだと捉えてしまいがちなのですが、実際は分断されたパケットのやりとりを何とか信頼性を高めるために制御しているようなもので、仕組みをある程度理解していないと実用的なプログラムは作れないのです。

もっともよくある問題の一つが、1回のsend()で送信すると、1回のrecv()で受信できるという思い込みです。TCP/IPは大きなデータは分断して送信し、受信側も十分に分断したデータの到着が連続していれば、プログラムに渡る際に一度のrecv()で受信できる可能性が高いのですが、インターネットを経由したりして経路が複雑になればなるほどまとめてrecv()できる可能性は低くなります。従って、HTTPやSMTPなどのプロトコルでも、ヘッダ部は一度のsend()で送ること、などという規約は無く、1バイトずつばらばらにsend()しても良いですし、全てをまとめてsend()しても良いのです。正しいrecv()の仕方は、必要なサイズまたは終端記号まで繰り返し受信する、ということになります。

実はHTTPの負荷分散装置によっては、セッションを認識するためにHTTPのヘッダ部まではまとまったパケットで届かないと捨ててしまう製品もあります。これはTCP/IPの特性を無視した実装だといえるでしょう。

もう1つよく問題になるのが、切断の検知です。ネットワークが問題ない状態でclose()を行えばFINパケットのやりとりにより、正しくソケットは切断されます。プログラムを強制終了した場合にもFINかRSTが届くので相手側も切断を検知できます。しかし、ネットワークケーブルを抜いたりHUBの電源を切った場合には切断はわからないのです。全ての切断を検知するためには定期的にパケットのやりとりをして無応答のタイムアウトで検知するしかできません。TCPキープアライブという仕組みも使えますが、これも相当長いスパンでの検知向けですので、自分自身でプロトコルを考えるときには必ず送受信を定期的に行うような仕組みを組み込むべきでしょう。

また、仮にFINやRSTで切断を検知できたとしても、recv()しないとわからないのです。send()するばかりのプログラムは、少なくとも切断した後の1回目のsend()ではエラーになりません。この理由からも送信処理でも必ず相手から応答をもらうようなプロトコルにするのがポイントです。

本来はこれらの問題は、プログラムを作った時点でテスト用に擬似プログラムを用意して、自ら異常系も含めて確認した時点で問題に気がつくと思うのですが、不思議なことに私の経験では実際に接続確認を行った時点で指摘を受けて初めて気がつく、というケースがほとんどでした。立派なテスト仕様書を書くことより、開発を進めながら都度実際に動かし、異常時の確認も含めてしっかり行うことの方が大切です。

2007.11.21

フレームページへ

from 2007/1/13