ソケットプログラミング HOWTO
****************************

著者:
   Gordon McMillan


概要
^^^^

ソケットはそこかしこで使われているが、最大級に誤解されている技術でもあ
る。この文書はソケットの全体像を俯瞰しており、チュートリアルとしてはあ
まり役に立たない。実際に動くモノを完成させるには、他にもやらなければい
けないことがあるからだ。この文書はソケットの微妙なところ (たくさんある
) まではカバーしていないが、恥ずかしくない使い方ができるようになる程度
の情報は得られるはずだ。


ソケット
========

INET (すなわち IPv4) ソケットのことしか語らないつもりだが、利用率でい
うとソケットの 99% 以上はこれだ。さらに中でも STREAM (すなわち TCP) ソ
ケットに話題を絞ろうと思う - 自分が何をしているのか分かっているのでな
い限り (分かってるならこの HOWTO なんて要らないだろ!)、STREAM ソケット
が一番分かりやすく、一番性能が出るのだ。そうやって謎に包まれたソケット
の正体を明らかにしてゆくと共に、ブロッキングおよびノンブロッキングなソ
ケットの扱いに関するいくつかのヒントを提示しよう。だが、まずはブロッキ
ングソケットから始めることにする。ノンブロッキングを扱うより先に、ブロ
ッキングの仕組みを知っておかなくてはならないのだ。

話を理解しにくくしている要因として、「ソケット」という言葉が文脈によっ
て微妙に違うものを指すことが挙げられる。そこでまず、「クライアント」ソ
ケット - 対話の両端 - と「サーバ」ソケット - 電話交換手みたいなもの -
の区別を付けておこう。クライアントアプリケーション (たとえばブラウザ)
は「クライアント」ソケットだけを使うが、話し相手のウェブサーバは「サー
バ」ソケットと「クライアント」ソケットの両方を使う。


歴史
----

各種 IPC (Inter Process Communication (End of Transfer) (プロセス間通
信) の中でも、ソケットは群を抜いて人気がある。どのプラットフォームにも
、ソケットより速い IPC はあるだろう。だが、プラットフォームをまたぐ通
信はソケットの独擅場だ。

ソケットは BSD Unix の一部としてバークレイで発明され、インターネットの
普及と共に野火のごとく広まった。それももっともなことで、ソケットと
INET のコンビによって世界中どんなマシンとも、信じられないほど簡単 (少
なくとも他のスキームと比べて) に通信できるようになったのだ。


ソケットの作成
==============

あなたがリンクをクリックしてこのページに来たとき、ブラウザは大雑把に言
って次のようなことをしたのである:

   # create an INET, STREAMing socket
   s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   # now connect to the web server on port 80 - the normal http port
   s.connect(("www.python.org", 80))

この "connect" が完了すると、ソケット "s" を使ってこのページ文章への要
求を送ることができるようになる。その同じソケットが返答を読み、そして破
壊される。そう、破壊される。クライアントソケットは通常、一回 (か少数の
) やり取りで使い捨てになるのだ。

ウェブサーバで起こる事柄はもう少し複雑だ。まず「サーバソケット」を作る
:

   # create an INET, STREAMing socket
   serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   # bind the socket to a public host, and a well-known port
   serversocket.bind((socket.gethostname(), 80))
   # become a server socket
   serversocket.listen(5)

ここで注意すべき点がいくつかある: 今回はソケットが外界に見えるよう、
"socket.gethostname()" を使った。 "s.bind(('localhost', 80))" や
"s.bind(('127.0.0.1', 80))" でも「サーバ」ソケットにはなるが、それだと
同じマシン内にしか見えないものになってしまう。 "s.bind(('', 80))" はこ
のマシンが持っている全てのアドレスで接続可能になるようにという指定にな
る。

ふたつめ: 小さな番号のポートは大抵、「ウェルノウン (有名)」なサービス
(HTTP, SNMP 等々) のために取ってある。お遊びで使うのなら適当に大きな数
(4桁) を使おう。

最後に: "listen" の引数はソケットライブラリに、接続要求を 5 個 (通常の
最大値) まで順番待ちさせるように命じている。これ以降の外部接続は拒否す
るのだが、コードが適切に書かれていれば、それで十分すぎるほどだ。

よし、「サーバーソケット」ができて、80 番ポートで耳を澄ましているとこ
ろまで来た。では、ウェブサーバのメインループに入ろう:

   while True:
       # accept connections from outside
       (clientsocket, address) = serversocket.accept()
       # now do something with the clientsocket
       # in this case, we'll pretend this is a threaded server
       ct = client_thread(clientsocket)
       ct.run()

このループには実際のところ、3 通りの一般的な動作方法がある -
"clientsocket" を扱うようにスレッドを割り当てたり、"clientsocket" を扱
う新しいプロセスを作ったり、あるいはノンブロッキングソケットを使うよう
にアプリを作り直して "select" で「サーバ」ソケットとアクティブな
"clientsocket" の間を多重化したりするのだ。最後のについてはまた後にし
よう。ここで理解しておくべき要点はこれだ: 以上が「サーバ」ソケットの仕
事の *すべて* である。データは一切送信しないし、受信しない。「クライア
ント」ソケットを生み出すだけ。我々のバインドされているホストとポートに
"connect()" してくる *他の* 「クライアント」ソケットに応える形で
"clientsocket" を作り、作るや否や、さらなる接続を聞きに戻っていくのだ
。このふたつの「クライアント」は、あとは勝手に喋っていればいい - 使う
ポートは動的に割り当てられ、会話が終わればリサイクルに廻される。


IPC
---

1つのマシン上の2プロセス間で高速なIPCを行いたい場合は、pipe か共有メモ
リを検討するべきです。AF_INET ソケットを使用すると決めた場合、「サーバ
ー」ソケットは "'localhost'" にバインドしましょう。ほとんどのプラット
フォームで、いくつかのネットワークレイヤーをショートカットすることでか
なり高速になります。

参考: "multiprocessing" はクロスプラットフォームなIPCの高級なAPIを提
  供して います。


ソケットの利用
==============

はじめに憶えておくべきなのは、ウェブブラウザの「クライアント」ソケット
とウェブサーバの「クライアント」ソケットがまったく同じ種族だということ
だ。つまり、これは「ピア・トゥ・ピア」(1 対 1) の会話である。別の言い
方をすると、 *設計者として自分で会話のエチケット規則を決めなくてはいけ
ない* ということでもある。通常は、 "connect" してくるソケットが要求あ
るいは宣言をして会話を始める。だが、それはそう設計しただけのことだ -
ソケットの規則ではない。

さて、コミュニケーションに使う動詞は二組ある。 "send" と "recv" を使う
こともできるし、クライアントソケットをファイルっぽい種族に変形して
"read" と "write" を使っても良い。後者は Java のソケットの表現方法だ。
ここで詳しく語るつもりはないが、その場合はソケットも "flush" しなけれ
ばいけない、とだけ言っておく。これはバッファリングした「ファイル」なの
で、何かを "write" してすぐに返答を "read" するというのはよくある間違
いだ。間に "flush" を入れないと、要求がまだ出力バッファにあって永遠に
返事が来ない、という可能性がある。

さあ、ソケットの主要な難関に進もう -  "send" と "recv" はネットワーク
バッファに働きかけるものだ。だから、手渡したもの (や返してもらいたいも
の) を 1 バイトも残さず実際に処理してくれているとは限らない。一般的に
言って、 "send" はバッファが埋まるまで、"recv" はバッファが空になるま
で処理をして、そのバイト数を返す。メッセージが完全に処理されるまで繰り
返し呼び出すのは *自分の* 責任なのだ。

"recv" が 0 バイトを返したときは、向こう側が接続を閉じてしまった (また
は閉じようとしている途中) という意味だ。もうこの接続でデータを受け取る
ことはない。永遠にだ。ただ、データ送信は成功するかもしれない; これにつ
いてはあとで語ることにしよう。

HTTP のようなプロトコルでは、ひとつのソケットを1回の転送にしか使わない
。クライアントは要求を送り、返答を受ける。以上だ。これでソケットは破棄
される。だからこの場合、クライアントは受信 0 バイトの時点で返答の末尾
を検出することができる。

だが、以降の転送にもそのソケットを使い回すつもりなら、ソケットに EOT
(End of Transfer) など *存在しない* ことを認識する必要がある。もう一度
言おう: ソケットの "send" や "recv" が 0 バイト処理で返ってきたなら、
その接続は終わっている。終わって *いない* なら、いつまで "recv" を待て
ばいいかは分からない。ソケットは「もう読むものが (今のところ) ないぜ」
などと *言わない* のだから。このことを少し考えれば、ソケットの真実を悟
ることになるだろう: *メッセージは必ず固定長か* (うげぇ) *区切り文字を
使うか* (やれやれ) *長さ標識を付けておくか* (かなりマシ) *接続を閉じて
終わらせるかのいずれかでなければいけない* のだ。選ぶ権利と責任はまった
くもって自分にある (が、正しさの程度に違いはある)。

毎回接続を終わらせるのはイヤだとして、最も単純な解決策は固定長メッセー
ジだろう:

   class MySocket:
       """demonstration class only
         - coded for clarity, not efficiency
       """

       def __init__(self, sock=None):
           if sock is None:
               self.sock = socket.socket(
                               socket.AF_INET, socket.SOCK_STREAM)
           else:
               self.sock = sock

       def connect(self, host, port):
           self.sock.connect((host, port))

       def mysend(self, msg):
           totalsent = 0
           while totalsent < MSGLEN:
               sent = self.sock.send(msg[totalsent:])
               if sent == 0:
                   raise RuntimeError("socket connection broken")
               totalsent = totalsent + sent

       def myreceive(self):
           chunks = []
           bytes_recd = 0
           while bytes_recd < MSGLEN:
               chunk = self.sock.recv(min(MSGLEN - bytes_recd, 2048))
               if chunk == b'':
                   raise RuntimeError("socket connection broken")
               chunks.append(chunk)
               bytes_recd = bytes_recd + len(chunk)
           return b''.join(chunks)

この送信コードは、ほぼあらゆるメッセージ通信スキームで使える - 文字列
を送るとき、Python なら長さを "len()" で見極めることができる (中に
"\0" が埋め込まれていても大丈夫)。難しくしているのは、おもに受信コード
である。 (なお、C でも事態はあまり悪くならないが、メッセージに "\0" が
埋め込まれていると "strlen" が使えないのは面倒だ。)

最も簡単な改良法は、メッセージの最初の一文字をタイプ標識にして、そのタ
イプで長さを決定するというものだ。この場合ふたつの "recv" があることに
なる - 一番目でその一文字 (だけじゃなくても可) を取って長さを調べ、二
番目でループして残りを取るのだ。あるいはもし区切り方式の道を行くのであ
れば、任意のサイズ (4096 か 8192 がネットワークバッファには最適なこと
が多い) で受信して区切り文字を走査していくことになる。

心に留めておくべき面倒な点がひとつ: 複数メッセージが次々に (何らかの返
事を待たずに) 返ってくることのある会話プロトコルなら、そして任意のサイ
ズを "recv" に渡しているなら、次のメッセージの冒頭部分まで読んでしまう
ことがあるかもしれない。そのときは、必要になるまで脇によけて、大切に保
管しておく必要がある。

メッセージ冒頭に長さを (たとえば 5 桁の数字で) 付けるのは、それよりも
さらに複雑になる。というのも、(信じられないかもしれないが) 一回の
"recv" で 5 文字を全部受け取ることができるとは限らないからだ。お遊びで
やっている間はごまかせても、高負荷ネットワークのもとでは、"recv" ルー
プをふたつ使わないコードは、あっと言う間にダメになってしまう - 一番目
は長さを見定める用で、二番目はデータ部分を受け取る用だ。うーむ、いやら
しい。さらにこのとき、"send" も一発で全部を出し切れるとは限らないこと
に気付くだろう。なお、今こうやって読んでいても、いつか誰もが痛い目を見
るのである!

紙面の都合および教育的配慮 (と著者の地位確保) のため、こうした改良は練
習問題として残しておく。さあ片付けてしまおう。


バイナリデータ
--------------

バイナリデータはまったく問題なくソケットに乗せられる。問題は、すべての
マシンで同じ形式を使っているわけではないことにある。たとえば Motorola
のチップなら 16 ビット整数の 1 という値をふたつの 16 進バイト列 00 01
で表現するが、Intel や DEC は逆バイトだ - 同じ 1 が 01 00 になるのだ。
ソケットライブラリは 16 ビットや 32 ビット整数の変換用コールを持ってい
る - "ntohl, htonl, ntohs, htons" である。"n" は *network*、 "h" は
*host* を意味する。 "s" は *short* で "l" は *long* だ。これらのコール
は、「ネットワーク並び = ホスト並び」なら何もしないが、マシンが逆バイ
トならそれに合わせてぐるっと交換してくれる。

この 32 ビット時代、バイナリデータは ASCII 表現のほうが小さくなること
が多い。というのも、long なのに値が 0 ばっかりでたまに 1 だとかいうこ
とは驚くほど多いからだ。文字列なら "0" は 2 バイトなのに、バイナリは 4
バイトも喰う。もちろんこれは固定長メッセージには合わないが。さあ、どう
する、どうする。


切断
====

厳密には、ソケットを "close" する前には "shutdown" することになってい
る。 "shutdown" は相手ソケットに対する報告であり、渡す引数によって「こ
れ以上こっちからは送らないけど、まだ聞いてるぜ」という意味になったり、
「もう聞かない。せいせいした!」だったりする。しかしほとんどのソケット
ライブラリは、このエチケットを怠るプログラマに慣れてしまって、通常
"close" だけで "shutdown(); close()" と同じことになる。だから大抵はわ
ざわざ "shutdown" しなくてもいい。

"shutdown" の効果的な使い方のひとつは、HTTP 風のやりとりだ。クライアン
トは要求を出してすぐに "shutdown(1)" する。これでサーバに、「クライア
ントは送信完了ですが、まだ受信可能です」と伝わる。サーバは 0 バイト受
信で "EOF" を検出することができる。要求を残さず受け取ったことにして良
いのだ。対してサーバは返答を送る。その "send" が成功したなら、クライア
ントは実際にまだ受信していたことになる。

Python はこの自動 shutdown をもう一歩進めて、ソケットが GC されるとき
に必要なら自動で "close" してくれると言っている。しかしこれに頼るクセ
をつけてはいけない。もしソケットが "close" せずに姿を消せば、相手ソケ
ットはこちらが遅いだけだと思ってハングしてしまうかもしれない。 *お願い
だから* 終わったらちゃんと "close" してくれ。


ソケットが死ぬと
----------------

ブロッキングソケットを使っていて一番いやなのは多分、相手側が意地悪く
("close" せずに) ダウンするときに起こる事柄だ。自分側のソケットは高確
率でハングするだろう。TCP は信頼性の高いプロトコルなので、ずっとずっと
待ち続けて、なかなか見捨てないのだ。スレッドを使っているのであれば、そ
のスレッド全体が根本から死んだ状態になる。こうなると、もう手の施しよう
がない。まあ、ブロッキング読み出しの間ロックし続けるといった馬鹿げたこ
とをしていない限り、リソースの点ではたいして消費にならない。だから *ぜ
ったいに* そのスレッドを殺そうとしてはいけない - プロセスよりスレッド
が効率的である理由のひとつは、自動リソース回収にまつわるオーバヘッドを
避けられるという点にあるのだ。つまり別の言い方をすると、どうにかしてそ
のスレッドを殺したなら、プロセス全体がぐちゃぐちゃになってしまうだろう
ということだ。


ノンブロッキングソケット
========================

ここまで理解してきたなら、もうソケットの仕組みについて必要なことはほと
んど知っていることになる。これからも同じコールを、ほぼ同じように使って
いくだけ、それだけだ。これをちゃんとやっていれば、そのアプリはだいたい
完璧であろう。

Python の場合、ノンブロッキングにするには "socket.setblocking(0)" を使
う。 C ならもっと複雑だ (一例を挙げると、BSD 方式の "O_NONBLOCK" およ
びほぼ違いのない POSIX 方式 "O_NDELAY" のどちらを選ぶか決めなくてはな
らなくて、後者は "TCP_NODELAY" とは全然別物だったりする) が、考え方は
まったく一緒だ。

構造上の大きな違いは、 "send", "recv", "connect", "accept" が何もしな
いで戻ってくるかもしれないという点である。選択肢は (当然ながら) いくつ
かある。返り値とエラーコードをチェックするという方法もある。が、発狂す
ること請け合いだ。信じないなら、いつかやってみるといい。アプリは肥大化
し、バグが増え、CPU を喰い尽くすだろう。だからそんな愚かな解法は飛ばし
て、正解に進もう。

"select" を使え。

C において "select" でコードを書くのはかなり面倒だが、Python なら造作
もない。しかし Python で "select" を理解しておけば C でもほとんど問題
なく書ける、という程度には似ている:

   ready_to_read, ready_to_write, in_error = \
                  select.select(
                     potential_readers,
                     potential_writers,
                     potential_errs,
                     timeout)

"select" に三つのリストを渡しているが、一番目にはあとで読みたくなるか
もしれないソケットすべて、二番目には書き込みたくなるかもしれないソケッ
トすべて、最後に (通常は空のままだが) エラーをチェックしたいソケットが
入っている。ひとつのソケットが複数にまたがってリストされても構わないこ
とを憶えておくと良い。なお、 "select" コールはブロックするが、時間制限
を与えることができる。これは、やっておいて損はない - 特に理由がなけれ
ば、かなり長い (たとえば 1 分とかの) 時間制限を付けておくことだ。

戻り値として、三つのリストが手に入る。それぞれには、実際に読めるソケッ
ト、書けるソケット、エラー中のソケットが入っていて、渡したリストの部分
集合 (空集合かもしれない) になっている。

出力のうち、readable リストにあるソケットについては、 "recv" がとりあ
えず *何か* を返すであろう、ということは史上最高度に確信できる。
writable リストも考え方は同じで、 *何か* は送れる。送りたいもの全体は
無理かもしれないが、 *何も* ないよりはマシだろう。 (実のところ、ふつう
に健康なソケットなら writable で返ってくることができる - それは外向き
ネットワークバッファに空きがあるというだけの意味しかないのだから)

「サーバ」ソケットは potential_readers リストに入れておこう。それが
readable リストに入って出てきたら、 "accept" は (ほぼ) 確実に成功する
はずだ。どこかへ "connect" するために作った新しいソケットは
potential_writers リストに入れる。それが writable リストに現れたら、接
続が成功している可能性は高いと言える。

じつは "select" はブロッキングソケットにも便利に使える。それはブロック
するかどうかを見極める方法のひとつである - バッファに何かがあれば
readable として返ってくるのだ。しかしこれも、相手の用事がもう済んでい
るのか、それとも単に他のことで忙しいだけなのかを見極める役には立たない
。

**非互換警報**: Unix ではソケットにもファイルにも "select" が使える。
これを Windows でやろうとしてはいけない。Windows で "select" はソケッ
トにしか使えない。また C の場合、高度なソケットオプションの多くは、や
り方が Windows では違っている。実際、Windows なら著者は通常、ソケット
にスレッドを使っている (これは実に、実にうまくいく)。
