mbed に WebSocketサーバーを載せてみた。
WebSocket関連で検索するのがほぼ日課みたいなものとなっています。
そんなある日、mbedというものを見つけました。
54mmx26mmの小さな基盤にARMのCPUが搭載されたものです。
青い基盤のもの(mbed NXP LPC1768)と、黄色い基盤のもの(mbed NXP LPC11U24)ものとがあります。(他にもいくつか種類があるみたいです。)
青い方はARM Cortex-M3を搭載しており、黄色い方はスペックを落として低消費電力化したものとなっており、ARM Cortex-M0を搭載しています。アキバではマルツや千石電商、秋月電子などで入手可能です。 開発用IDEがクラウドで用意されており、いつでもどこでもブラウザ上で開発ができるというのが素晴らしいところです。なお、オフラインでの開発環境もできるみたいです。
その他の詳しい内容はここでは割愛させていただきます。
しかし、ソースを見てみるとhybi-00(hixi-76)の古いプロトコルバージョンを使用して作成されたもので、RFCに対応していないものでした。
ですので、RFCに対応したWebSocketを載せてみようと思います。
@masato_kaさんのブログのコメントにてwebsockets_hello_world_ethernetをインポートすればというコメントが有りましたのでインポートしてみるとRFCに対応したWebSocketライブラリが使用されていました。
ただ、WebSocketクライアントを載せた場合は別途サーバーが必要となります。そこで、WebSocketサーバーをmbedに載せればブラウザーから直接データのやり取りが行えるようになるのではと思い挑戦してみたら出来たというお話です。
私は、C++はまったくといっていいほど触ったことがないため、いろいろとわからないままで組んでおりますのでご理解の程。警告は読んでわからないものは放置しています。
main.cppのソースを以下に掲載します。
なお、今回のプロジェクトファイル一式は下記のリンクからダウンロードできます。
mbed_WebSocketServer.zip
ほとんどコピペなソースなので主要なonLinkSocketEvent関数部分のみ載せます
LANのモジュラ・ジャックは千石電商などでキットとして販売されていますのでこちらを購入したほうがいいでしょう。 また、☆Board Orangeというmbed用のベースボードのも販売されています。このボードはmicroSD、USB(Host)、キャラクターLCDなどのI/Oインターフェイスを簡単に増設できるベースボードとなっています。このベースボードも千石電商などで入手可能です。(mbed用のベースボードは他にもあるようです。)
プログラムのソースは固定IPで接続するように組んでいます。DHCPでIPを自動で割り振る場合は割り振られたmbedのIPアドレスが分かる手段を整えてください。
また、Windowsの方はmbedのシリアルポートドライバーインストールし、Tera Termなどのシリアル通信モニターソフトを起動します。MacやLinuxの方はドライバーは不要なようですので、シリアル通信モニターのみ用意し起動します。
また、1:1での接続のみ対応しています。
なお、ブラウザーはプロトコルバージョンhybi-07以上に対応したChromeまたはFirefox(Windowsの場合)で接続してください。 コンパイルしてできたbinファイルをmbed(USBで接続するとマスストレージとして認識されます)にコピーまたは移動します。
コピーまたは移動したらmbedのリセットボタンを押し実行します。
シリアル通信モニターソフトの画面にListeningと表示されたら、プロジェクトファイル一式内にあるmbedWebSocketTest.htmを開きます。
(なお、Chromeで開く場合は、Chromeが起動している場合はすべて閉じたあとにChromeを --allow-file-access-from-files オプション付きで起動したあとに開いてください。)
開いたら、テキストボックスに半角で文字列を入力しエンターキーを押したら送信されます。
送信されたデータはシリアル通信モニターソフトの画面に
received data:送信データ
と表示されます。
また、mbed側では受診したデータをブラウザーにそのまま返しますので、ブラウザーのテキストボックスの下にも送信された文字列が表示されます。
そんなある日、mbedというものを見つけました。
mbedとは?
私も触り始めたばかりで偉そうに説明できるほどではありませんが、第一印象として「簡単」ということ。54mmx26mmの小さな基盤にARMのCPUが搭載されたものです。
青い基盤のもの(mbed NXP LPC1768)と、黄色い基盤のもの(mbed NXP LPC11U24)ものとがあります。(他にもいくつか種類があるみたいです。)
青い方はARM Cortex-M3を搭載しており、黄色い方はスペックを落として低消費電力化したものとなっており、ARM Cortex-M0を搭載しています。アキバではマルツや千石電商、秋月電子などで入手可能です。 開発用IDEがクラウドで用意されており、いつでもどこでもブラウザ上で開発ができるというのが素晴らしいところです。なお、オフラインでの開発環境もできるみたいです。
その他の詳しい内容はここでは割愛させていただきます。
WebSocketサーバーを載せる
mbed用にWebSocketクライアントが開発されているのが見つけるきっかけでした。@masato_kaさんのブログのコメントにてwebsockets_hello_world_ethernetをインポートすればというコメントが有りましたのでインポートしてみるとRFCに対応したWebSocketライブラリが使用されていました。
ただ、WebSocketクライアントを載せた場合は別途サーバーが必要となります。そこで、WebSocketサーバーをmbedに載せればブラウザーから直接データのやり取りが行えるようになるのではと思い挑戦してみたら出来たというお話です。
ソース
SHA1ハッシュの計算を行うにあたり、NetServiceSourceからsha1.h, sha1config.h, sha1.cのソースを使用させていただいています。私は、C++はまったくといっていいほど触ったことがないため、いろいろとわからないままで組んでおりますのでご理解の程。警告は読んでわからないものは放置しています。
main.cppのソースを以下に掲載します。
なお、今回のプロジェクトファイル一式は下記のリンクからダウンロードできます。
mbed_WebSocketServer.zip
ほとんどコピペなソースなので主要なonLinkSocketEvent関数部分のみ載せます
void onLinkSocketEvent(TCPSocketEvent e) { switch (e) { case TCPSOCKET_CONNECTED: printf("TCP Socket Connected\n"); break; case TCPSOCKET_WRITEABLE: //Can now write some data... printf("TCP Socket Writable\n"); break; case TCPSOCKET_READABLE: //Can now read dome data... printf("TCP Socket Readable\n"); // Read in any available data into the buffer char buff[1024]; while ( int len = link->recv(buff, 1024) ) { // And send straight back out again //link->send(buff, len); if (wsState == 0) { // ハンドシェイクステート buff[len]=0; // make terminater printf("%s\n", (char*)buff); for (int i = 0; i < len; i++) { if (buff[i] == 'K' && buff[i + 1] == 'e' && buff[i + 2] == 'y') { for (int j = i + 1; j < len; j++) { if (buff[j] == '\r') { i += 5; int keyLen = j - i; char strKey[keyLen + 1]; strKey[keyLen] = 0; // Sec-WebSocket-Keyフィールドの値をstrKeyに取得 strncpy(strKey, buff + i, keyLen); // Acceptデータ作成用の固定GUID文字列 char guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // Sec-WebSocket-Acceptデータを作成 strcat(strKey, guid); unsigned char hash[20]; sha1((unsigned char*)strKey,strlen((char*)strKey),hash); string accept = encode64((char*)hash, 20); // ハンドシェイクレスポンスを作成 string hsRes = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "; hsRes += accept; hsRes += "\r\n\r\n"; // ハンドシェイクレスポンスデバッグ出力 printf("%s\n", hsRes.c_str()); // ハンドシェイクレスポンスをクライアント(ブラウザー)に送信 link->send(hsRes.c_str(), hsRes.size()); // データ送受信ステートに移行する wsState = 1; return; } } } } } else { // データ受信ステート // ここでクライアントから送られてきたデータを処理する。 // 今回は送られてきたデータをデバッグ出力し、 // そのままクライアント(ブラウザー)に送信する。 // なお単純にエコーで返す場合は以下の1行だけでいい。 // link->send(buff, len); bool fin = (buff[0] & 0x80) == 0x80; int opcode = buff[0] & 0x0f; if(opcode == 9) { // Ping(opcode = 0x9)が送られた場合は、Pong(opcde = 0xA)を返す buff[0]++; link->send(buff, len); return; } int dataLen = buff[1] & 0x7f; if (!fin || dataLen > 125) { // 今回は、FINフラグが立っていないフレーム、 // または126バイト以上のデータは扱わないことにする link->close(); return; } int i = 0; // アンマスクを行う for (i = 0; i < dataLen; i++) { buff[6 + i] = buff[6 + i] ^ buff[2 + (i % 4)]; } if(opcode == 1) { // 送られてきたフレームがテキストフレームの場合 // 送られてきたテキストデータをデバッグ出力 char dispData[dataLen + 1]; strncpy(dispData, buff + 6, dataLen); dispData[dataLen] = 0; printf("%s", dispData); } // 送信フレームの作成 char sendData[2 + dataLen + 1]; sendData[0] = buff[0]; sendData[1] = buff[1] & 0x7f; for (i = 0; i < dataLen; i++) { sendData[2 + i] = buff[6 + i]; } sendData[2 + dataLen] = 0; // クライアント(ブラウザー)に送信 link->send(sendData, 2 + dataLen); } } break; case TCPSOCKET_CONTIMEOUT: printf("TCP Socket Timeout\n"); break; case TCPSOCKET_CONRST: printf("TCP Socket CONRST\n"); break; case TCPSOCKET_CONABRT: printf("TCP Socket CONABRT\n"); break; case TCPSOCKET_ERROR: printf("TCP Socket Error\n"); link->close(); break; case TCPSOCKET_DISCONNECTED: printf("TCP Socket Disconnected\n"); // wsStateをリセット wsState = 0; link->close(); break; default: printf("DEFAULT\n"); } }
LANのモジュラ・ジャックの増設
mbed単体ではLANケーブルを繋げることは困難ですので(直接ハンダ付けすれば出来なくは無いですが)、LANの(RJ45)モジュラ・ジャックを増設する必要があります。LANのモジュラ・ジャックは千石電商などでキットとして販売されていますのでこちらを購入したほうがいいでしょう。 また、☆Board Orangeというmbed用のベースボードのも販売されています。このボードはmicroSD、USB(Host)、キャラクターLCDなどのI/Oインターフェイスを簡単に増設できるベースボードとなっています。このベースボードも千石電商などで入手可能です。(mbed用のベースボードは他にもあるようです。)
準備
mbedとPCをUSBとLAN(クロス)ケーブルで接続します。プログラムのソースは固定IPで接続するように組んでいます。DHCPでIPを自動で割り振る場合は割り振られたmbedのIPアドレスが分かる手段を整えてください。
また、Windowsの方はmbedのシリアルポートドライバーインストールし、Tera Termなどのシリアル通信モニターソフトを起動します。MacやLinuxの方はドライバーは不要なようですので、シリアル通信モニターのみ用意し起動します。
実行
サンプルプログラムは固定IPで接続するように組んでいます。DHCPで接続したい場合は適宜ソースを修正してください。また、1:1での接続のみ対応しています。
なお、ブラウザーはプロトコルバージョンhybi-07以上に対応したChromeまたはFirefox(Windowsの場合)で接続してください。 コンパイルしてできたbinファイルをmbed(USBで接続するとマスストレージとして認識されます)にコピーまたは移動します。
コピーまたは移動したらmbedのリセットボタンを押し実行します。
シリアル通信モニターソフトの画面にListeningと表示されたら、プロジェクトファイル一式内にあるmbedWebSocketTest.htmを開きます。
(なお、Chromeで開く場合は、Chromeが起動している場合はすべて閉じたあとにChromeを --allow-file-access-from-files オプション付きで起動したあとに開いてください。)
開いたら、テキストボックスに半角で文字列を入力しエンターキーを押したら送信されます。
送信されたデータはシリアル通信モニターソフトの画面に
received data:送信データ
と表示されます。
また、mbed側では受診したデータをブラウザーにそのまま返しますので、ブラウザーのテキストボックスの下にも送信された文字列が表示されます。