何でもかんでもSocketを使うな

Tcp_state_diagram_fixed_new.svg

これがうちの会社だけなのか、それとも、業界全体でそうなのかよくわからないけれども、レガシーなシステムでSocket通信をするような場面をよく見る。サーバとサーバの間での通信で10~20個、サーバー内のプロセス間通信で、ループバックで30個くらい。そんなシステムをよく見る。意味がわからない。

システムが完成してから「ペネトレーションテストをするから不要なポートを全部閉じろ」とか言われた時は、言ったやつをぶん殴りそうになった。これらのっ!ポートをっ!誰がどうやって使ってるかどうか判断するんじゃ!ドキュメント化もされてねーのに!!!その時俺はNmapとかを駆使して調査した気がする。

というわけで、他にもいろいろ理由があり、何でもかんでもsocketでやられると困るのだ。なぜ困るのか。俺はどうして欲しいのか。今日はそれを伝えたい。

なぜSocketがいかんのか

生のTCP/IP通信は、本来、複雑なシステムである。どう複雑なのかは、TCPの状態遷移図を見ればひと目で分かるだろう。

Tcp_state_diagram_fixed_new.svg
Transmission Control Protocol - Wikipedia

よっぽど詳しい人でない限り、普通はこんな遷移図は覚えていられない。私だって覚えていられない。覚えていられないが、「ちゃんと」TCP/IPを使うためにはこれを頭に叩き込んでおかなくてはならない。

ネットでちゃちゃっと検索したコードで、簡易的な通信をするくらいだったら誰でも出来るだろう。しかし、何かトラブルがあったとき。Socketをcloseしてるはずなのに何故かnetstatするとCLOSE_WAITになったまま動かず、ディスクリプタが枯渇するとか、ちゃんとポートを開けていて、pingも通るのに何故かconnectできないとか、そういう時には表面的な知識では対応できない。ちゃんんとした基礎からの知識が必要である。

さらに輪をかけて難しくしているのが、そもそもTCP/IPはストリーム型の通信方式で、データの終わりという定義が無いということである。しかし、多くの場面で人間が必要としているのは、始まりと終わりが明確に定義されているひと固まりのメッセージである。

終わりの定義がないので、「ひと固まりのメッセージ」を自分で定義しなければならない。メッセージ長をヘッダにつけて、そのメッセージ長まで受信したらそれを一つのメッセージをみなす。もしくは、始まりと終端を表す特殊な記号を定義して、データ中には一切それを使用しない、などということをしなければならない。

こういう作業は素人がコーディングするとバグを混入させやすいし、そもそもシステムの目的とは関係ないので非生産的な作業である。じゃあUDPつかったらいいじゃん、ってなるのだけど、それも微妙だ。UDPは信頼性を犠牲にして効率を追求したプロトコルだからだ。データが届かない場合があることも考慮し無くてはならない。

ちなみに、このような背景があるため、UDPメッセージにチェックサムをつけたり、シーケンス番号と再送の手続きを実装したりしているコードを見かけたことがあるが、これはTCPの再発明である。考えたやつはバカである。

と、長々と書いてきたが、簡単に言うと、「なぜSocketがいかんのか?」それは、本質的にTCP/IP通信が難しいからである。手を出さなくていいなら手を出さないほうが賢明なのである。

WebSocketはどうなんだ

WebScoketは、生のSocketライブラリを使うよりはずっと安全だ。こういう小難しい処理は隠蔽されていて、プログラマ側には最小限のインタフェースのみ公開されている。イベントで要求を受信する。メッセージは小分割されること無く届き、データの終わりは明確になっている。つまり、先に述べた危険性が排除されている。これならガシガシ使っていいんじゃないか。

いや、私はそうは思わない。なぜか。

そもそも、WebSocketは、現在のAjaxを置換することを目的として仕様策定されたわけではない。WebSocketが提供するのは、低いレイテンシで、双方向(サーバからpushできる)なコネクションだ。このようなメリットが必要でない場合にWebSocketを採用すると、すでに存在するAjax関係のライブラリやツール、設計パターンが使えなくなる。このデメリットが大きいと思うのだ。

すでにAjaxというのは、Ajaxという言葉自体が死語になる程度にはこなれている技術なので、その恩恵が受けれる場面では素直に享受したほうが楽なことは間違いない。

具体例を示そう。伝票の種類、起草者、日付の範囲を入力し、「検索」ボタンを押すと、クエリにマッチする伝票の一覧が画面に表示される。

このような単純なアプリケーションを作るとき、サーバとクライアントの間の通信をWebSocketで実装する必要性があるだろうか?私は無いと思う。クエリをGETパラメータとして送信したほうが、ずっと明確で分かりやすい。

まとめ

方法が複数あるときは、最も適した方法を使うべき。どんなことにも対応できる方法というのは、何かに特化した方法を使うよりもずっと設計をわかりにくくさせるという副作用に気をつけたほうがいいと思う。

ただし、技術の進歩や潮流の変化により、何でもかんでもWebSocketに処理させ、また、そのためのフレームワークや方法論が発展してきたのならば、その限りではない。