2010年2月18日木曜日

Inside Web Socket

HTML5関連で、Web Socketが話題になることが増えてきました。

その中で、Web Socketはブラウザで生のTCPが扱えるようになった!いや、違うよ。生のTCPじゃないよ!と言ったことをよく目にします。
lighttpd 1.4.x用のmod_websocketを個人的に作成している為、そういった疑問に対しての参考になりましたら、と。勿論、間違ってたらご指摘ください。(^-^;

  • Web Socketとは
    ブラウザ(上のJavascript Program)で生のTCPを扱えるように策定されたプロトコルです。
    なぜ、そう言うのか、順を追って説明します。(ごめんなさい、やっぱりちょっと修正しておきます)
  • ブラウザがどう動いているのか?
    まず、Web Socketを利用する際、ブラウザがどのように動いているのかを説明します。また、Web Socketには、ws schemeとwss schemeの2種類がありますので、それらを別々に説明しますね。(ちなみに、PROXY裏、と言うような環境に於いても、ブラウザは頑張ってくれてますが、ここでは説明を割愛させていただきます。)


    • ws schemeの場合

      1. var ws = new WebSocket(“ws://ip_addr:port_num/resource”);
      2. ブラウザは今までのhttp connectionとは別のsocket fdを作成します。
      3. そのsocket fdを用いて、ip_addr:port_numへとconnect(2)を行います。なお、port_numが省略された場合は80番が使われます。
      4. connectが成功したら、Web Socketのhandshakeを行います。
      5. handshakeが成功したら、そのsocket fdを利用してdataを送受信します。(http connectionとは全く別物)
      6. 送り出す場合には、ws.send(“hoge”);
      7. 受け取る場合はws.onmessage = function(msg) {}

        これはつまり、http connection/protocolとは全く関係ないTCP Connection上で、data送受信を行っているに過ぎません。

    注:connect(2)など、()内で示されている数字は、unix上でmanualを参照する際のセクション番号です。私の覚書だけで、特に意味はありません。

    また、ブラウザですので、実際のapi名は違います。同等の処理だと思ってください。(以降も同じ)


    • wss schemeの場合

      1. var ws = new WebSocket(“wss://ip_addr:port_num/resource”);
      2. ブラウザは今までのhttp connectionとは別のsocket fdを作成します。
      3. そのsocket fdを用いて、ip_addr:port_numへとSSL_connect(3SSL)を行います。
        なお、port_numが省略された場合は443番が使われます。
      4. SSL handshakeが終わり、SSL_Connectが成功したら、Web Socketのhandshakeを行います。
      5. Handshakeが成功したら、そのsocket fdを利用してSSL_read(3SSL)/SSL_write(3SSL)を行い、dataを送受信します。
      6. 送り出す場合には、ws.send(“hoge”);
      7. “hoge”は、SSL_sendによってブラウザで暗号化され、送信されます。
      8. 受け取る場合はws.onmessage = function(msg) { … }
      9. msgは、SSL_readによってブラウザで復号化され、受信されます。その為、javascript内部では勿論、復号化する必要はありません。

        これもまた、http connection/protocolとは関係ないSSL Connection上で、data送受信を行っているに過ぎません。
  • Web Socketサーバはどう動いているのか?
    では次に、Web Socketサーバはどのような動きをするのでしょうか?まず、単体Web Socketサーバを動かして把握しましょう。


    • ws scheme
      非常に多くの方が、ruby, goなどで実装を公開して下さっています。

      http://blog.liris.org/2009/12/websocketchat.html
      http://github.com/igrigorik/em-websocket
      http://github.com/gimite/web-socket-ruby

      以上の方々のサンプルを動かしてみてください。(他にも多々ありますが、ごめんなさい。私が参考にさせて頂いた方のコードだけ参照させて頂いています)
      では、流れを見てみましょう。


      1. serverは、お好きなportでlisten(2)してください。
      2. listenしているportにconnectが来たら、accept(2)してあげましょう。
      3. acceptが成功したら、新たなsocket fdが出来ているはずです。そのsocket fdを利用して、Web Socketのhandshakeを行います。
      4. handshakeが成功したら、そのsocket fdを利用してdataを送受信します。
      5. 送り出す場合には、send(2)/write(2)などを用います。
      6. 受け取る場合は、recv(2)/read(2)などを用います。

        やはりこれも、純粋なTCP Serverの動きです。

    • wss scheme
      残念ながら単体サーバでwss schemeを正しくサポートしているprogramは、私はその存在を見つけられていません。(あれば教えてください。(^^;)ですので、実際に実装するとどうなるか、を見てみましょう。


      1. serverは、お好きなportでlistenしてください。
      2. listenしているportにSSL_connect(3SSL)が来たら、SSL_accept(3SSL)してあげましょう。(勿論、SSL_do_handshake(3SSL)なども必要です)
      3. SSL_acceptが成功したら、新たなsocket fdが出来ているはずです。そのsocket fdを利用して、Web Socketのhandshakeを行います。
      4. handshakeが成功したら、そのsocket fdを利用してdataを送受信します。
      5. 送り出す場合には、SSL_read(3SSL)などを用います。
      6. 受け取る場合は、SSL_write(3SSL)などを用います。

        はい、純粋なSSL Serverの動きです。

  • Web Socketを利用するには?
    さて、ここまでで大体、Web Socketは通常のTCP/SSL connectionをブラウザで利用することを可能とするプロトコルだと言うことがわかってもらえたのではないでしょうか?
    つまり、ブラウザがサポートするWeb Socketを利用するには、以下の点を抑えれば大丈夫です。


    • port == 80/443以外の通信を利用できる環境であれば、別にhttpサーバは必要ありません。先の単体サーバに、SSL通信を行える機能を追加してあげれば、Web Socketをサポートするブラウザとws/wss schemeでの通信が可能になります。



しかしながら、F/W、NAT、PROXYなど様々な理由で、port == 80/443で通信を行いたい場合。その時に、このプロトコルの策定意義があります。

  • httpサーバ経由でWeb Socket!
    まず、ブラウザ側の動きを思い出してください。


    • ブラウザは今までのhttp connectionとは別のsocket fdを作成します。
    • そのsocket fdを用いて、ip_addr:port_numへとconnectを行います。
      port_numが省略された場合は80番が使われます。
    • connectが成功したら、Web Socketのhandshakeを行います

    そう、つまり、http protocolではないmessageが、httpサーバに届くことになります。
    httpサーバにhttpでは無いprotocolを理解させる機能を追加しなければなりません!!(大変だぁあ!)

そうです。結局このプロトコルの存在意義、と言うのは、

新たな仕様を策定し、ブラウザそしてhttpサーバ双方でhttp以外のプロトコルを用いた通信を可能とし、http/httpsで利用しているポートでメッセージのリアルタイム送受信を可能にしようよ!(勿論http handshake回数の削減とか他にも色々意味合いはあります)

(良いことか悪いことかは別として)と、言うことだと思うわけです。

  • httpサーバにWeb Socket Protocolを話させるために必要な追加機能
    正直、細かい所まで説明しだすと、仕様そのものになっちゃいますので、簡単に。詳細を知りたい方は、http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75を読んでください。
    (随時改版されますので、最新のものを読まれることをお勧めいたします。また、@toshirotさんなど有志の方々が日本語訳もされてるはずですので(ごめんなさい、url失念)、英語が苦手な方は、バージョンが若干古くなると思いますが、そちらから読まれてもよいでしょう。)


    1. WebSocket handshake
      とても簡単です。これからWebSocket通信したいよ、と言う要求がbrowserから届きますので、返事を返してあげる。これだけです。



    2. WebSocket Frame Format
      今のところ、2種類のFrame Formatが規定されています。但し、MLでの議論を見たり、@komasshuさんとお話しさせていただいたりしていると、今後は2だけになりそうな流れだね、と意見が一致してますけれど。(理由としては、security issue/binary dataの送受信など、色々ありそうです)


      1. (0x00 to 0x7f) → Data → (0xFF)
      2. (0x80 to 0xFF) → Length → Data

      handshakeが終わった後のsocket fdには、上述のformatを持ったmessageが流れてきます。上述2種類のFrame Formatを解読し、Data部分だけをserver側applicationに引き渡してあげる必要があります。

  • httpサーバがWeb Socket Protocolを話せるとどうなるのか?
    最後に。
    上述の機能をhttpサーバに追加してあげた場合、いったいどのように動くのでしょうか?これは、言葉で説明するよりも、図の方がわかりやすいでしょう。
    ただし、ここではprocessがどうの、とかsocketの引渡しはどうする、とか言う点には言及しません。また、そこはserver側実装をされる方々が考える(設計する)べき点です。
  • 概念図
    基本的には以下のように動きます。勿論、server側のApplicationでFrame Formatを扱えるようにしてもOKです。(不親切ですが)


  • pywebsocketはどう動いているか
    apacheのmoduleである、pywebsocketは以下のように動いています。



以上、私の理解の範囲で記述させていただきましたが、質問、間違いなどございましたら気軽にご連絡ください。ただし、ご回答が遅れるかもしれない点だけご了承頂きたく。

いつになるかわかりませんが、次回はlighty用のmod_websocketがどう動いているかを書いてみます。
簡単に説明しておきますと、reverse proxy likeな動作をします。と書くと、わかっていただける方も多いのではないでしょうか?

ではでは。

2 件のコメント:

  1. このコメントは投稿者によって削除されました。

    返信削除
  2. Hi Dear, are you genuinely visiting this web page on a regular basis, if so after that you will without doubt take pleasant experience.

    返信削除