Home » Web » WebSocketサーバを作る(1)

WebSocketサーバを作る(1)

WebSocketとは

HTTPはインターネットの基盤となるプロトコルですが、文書のやりとりを主旨とするため、かならずしも現在のニーズに応え切れているとは言えません。
例えば、プッシュ通信ができないことは、今まで多くの技術者を悩ませてきました。

WebSocketは、こうしたHTTPの不足を補い、サーバ・ブラウザ間でより安定的にデータをやり取りするための仕組みです。
HTTPと違い、接続を永続的に維持するため、プッシュ通信が可能です。
ただし、新しいプロトコルなので、サーバ側にもブラウザ側にも、専用の仕組みが必要となります。

なぜWebSocketが必要か

Webサービスでチャットを作ることを考えてみましょう。
HTTPにはプッシュ通信がありません。
Aさんの発言を、Aさんの発言したタイミングでBさんとCさんに送信するにはどうしたら良いでしょう。

この問題には、古来よりさまざまな対策手法が編み出されてきました。

良く使われるのは、再読み込みボタンをつけたり、F5キーを押してもらったりして、サーバの情報を見に来てもらうというものです。
このタイプのもっとも良くできたサイトでは、Ajaxが30秒おきにサーバを見に行って、画面が自動的に最新に更新されます。

でも、自動更新の間隔を30秒から10秒に縮めることはできても、リアルタイムにすることはできません。
プッシュ通信ではないからです。

もっとも、チャットくらいならこれでも十分でしょう。
Aさんの「おはよう」が届くのが、10秒後でも30秒後でも、大した問題にはならないことがほとんどだからです。

では、ゲームならどうでしょうか。
対戦相手のいる艦隊ゲームです。
敵の場所が10秒おきにしか更新されないのでは、レーダーなしでUボートを相手にするようなものです。
次の10秒後にお知らせされるのは、敵の位置ではなく、あなたの艦隊の目も当てられないような被害状況かも知れません。

こうした用途にはFlashを使うのが一般的ですが、高性能化するスマートフォンでもFlashはやはり重く、iOSに至っては未実装です。

WebSocketはほぼすべてのモダンブラウザで動作します。
iOSが採用しているモバイルSafariでも動くのです。
実装も簡単です。

長くなりましたが

本題です。

WebSocketのサーバ側の実装は、HTTPで言うところのApacheやIISといったメジャーなサーバプログラムやフレームワークは無いのですが、WebSocket自体がそれほど大がかりな仕組みではないので、それならいっそ作ってしまえというのが今回の趣旨です。

もっとも、node.jsやPHPを使って作られたものがすでにありますので、手っ取り早くやるにはこうしたオープンソースを利用するのが良いでしょう。

 

全体の流れ

RFCを参考に作っていきます。

http://tools.ietf.org/html/rfc6455

WebSocket通信の、おおまかな流れは、以下のようになります。

  1. ブラウザからハンドシェイク要求
  2. サーバがハンドシェイク応答
  3. 接続確立
  4. 以後、双方から自由にデータを送り合う

ハンドシェイクパートは、HTTP通信のリクエスト・レスポンスに似たやり取りです。
接続の確立後はWebSocket独自形式に切り替わり、フレームデータをお互いに送り合う形となります。

ハンドシェイク

通信は、ブラウザからの以下のようなハンドシェイク要求で始まります。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

サーバはこれに応え、下記のような応答を返します。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

まずは通信の基礎となる、ソケット通信部分の実装から作っていきます。
ここはWebSocketというよりはいわゆるソケット通信のとこなんで、あまり深追いはしません。
ちなみにソースコードはC++です。

新規接続を親プロセスで待ち受け、接続が確立したら子プロセスにフォークする、という仕組みにします。
新規接続用のcurr_sock、子プロセス用のfork_sock、2つのソケットを使いますので、これらを初期化します。

また、curr_sockは待ち受け用なので、IPアドレスにバインドしておきます。

次に、先ほど作った待ち受け用のソケットを接続待ち状態にします。 定期的にメッセージの到着を調べるため、select()関数をwhileループで回します。 メッセージが到着している場合は、この接続を子プロセスにforkします。 メッセージの解析などの後続処理は子プロセスで行います。 親プロセスはまたwhileループの最初に戻り、メッセージの到着を調べる作業に戻るというわけです。   

接続が確立されて子プロセスに処理が回ってきました。
まずハンドシェイク要求の受付を行います。

ハンドシェイクは最初に接続してきたときだけで、接続確立後は行いませんので、子プロセス内で最初の1回だけ通るように書いておきます。

ブラウザからのハンドシェイク要求は、HTTPのGETリクエストに似ています。
GETでリクエストされている”/”以降の部分を、WebSocketではプロトコルと呼んでいて、クライアントがどんなサービスを要求しているかを知ります。
サーバアプリ側では、これを判別することで、要求に応じた処理や応答を返す仕組みが作れるわけです。

通常のWebサービスでも”/chat”をリクエストすればチャット画面が表示され、”/poker”をリクエストすればポーカーのプレイ画面が表示され、ということを期待しますね。
それと同じような振る舞いを、WebSocketでも想定しているということです。

そして、”Sec-WebSocket-Key”パラメータの値が、ハンドシェイクでやり取りされるキーとなります。
この値はブラウザがランダムに生成してきたものです。
サーバ側では、この文字列にGUID(”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″)を付加して、SHA1でハッシュ化し、base64エンコードして、ブラウザに応答キーとして返します。

たとえば、ブラウザから送られてきたキーが”dGhlIHNhbXBsZSBub25jZQ==”であった場合、”dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11″をSHA1のハッシュ関数にかけます。
結果は 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea です。
そして、これをbase64エンコーディングすると、”s3pPLMBiTxaQ9kYGzzhZRbK+xOo=”となります。
したがって、レスポンスパラメータ”Sec-WebSocket-Accept”には、”s3pPLMBiTxaQ9kYGzzhZRbK+xOo=”をセットします。

レスポンスパラメータの”Sec-WebSocket-Protocol”はオプションで、プロトコルが複数あって指定する必要がある場合に、セットするようです。
これは良く分からないのですが、ブラウザから”/chat”と要求されたら、レスポンスにも”/chat”と返す、という利用方法では無いようです。
実際、ブラウザからのリクエスト通りにレスポンスをセットしたら、ブラウザ側(Chrome)でハンドシェイク失敗になってしまいました。
なので、今回のソースではこのパラメータ自体を返さないようにしています。

※ハンドシェイクの処理は80行目までです。

長くなってしまったので、その2へ続きます。


                        
Post Tag With :

One Response so far.

  1. Eldora より:

    I’m impressed by your writing. Are you a professional or just very knwlaedgelboe?

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です