WebSocket Serverまとめ

Chrome 14からWebSocketプロトコルのバージョンがdraft 10になる。現在、Chrome 13はdraft 76で実装されておりdraft 76とdraft 10は互換性がない。WebSocketを使った既存のサービスは壊れるのでdraft 10で実装されたWebSocketServerを探しときの情報をまとめた。

New WebSocket Protocol: Secure and Extensible

Please note that the new protocol is incompatible with one which Chromium previously supported (draft-ietf-hybi-thewebsocketprotocol-00),
so existing WebSocket-based services may break.
Please upgrade your servers to ones which support HyBi 10.
Existing JavaScript code still works once the protocol version used by the browser and server match.

新しいプロトコルは非互換でなので既存のWebSocketをベースとしたサービスは壊れる。HyBi 10をサポートするようにサーバをアップグレードしてくれ。既存のJavaScriptコードはプロトコルのバージョンが合致すれば動作するだろう。

WebSocket-Node

A WebSocket Draft -08/-09/-10 Implementation for Node.JS

いけちゃうっぽい!broadcastメソッドが存在しないようだが、以下のようにbroadcastメソッドを実装することにした。

var WebSocketServer = require('websocket').server;
WebSocketServer.prototype.broadcastUTF = function(data){
    this.connections.forEach(function(connection, i, ary){
        connection.sendUTF(data);
    });
};

作者の方にも気に入って頂けたようなので近いうちにモジュールに組み込まれるかもしれない。

インストール

npm install websocket

実装

以下のようにしてWebSocketサーバをセットアップした。

/**
 * extend default WebSocketServer for broadcast
 */
var WebSocketServer = require('websocket').server;
WebSocketServer.prototype.broadcastUTF = function(data){
    this.connections.forEach(function(connection, i, ary){
        connection.sendUTF(data);
    });
};


/**
 * websocket server for end users
 */
var server = require('http').createServer(
    function(request, response) {
        console.log((new Date()) + " Received request for " + request.url);
        response.writeHead(404);
        response.end();
    }
);
server.listen(
    eew.env['port'],
    function() {
        console.log((new Date()) + " Server is listening on port 80");
    }
);

wsServer = new WebSocketServer({
    "httpServer"            : server,
    "autoAcceptConnections" : true
});

wsServer.on(
    'connect',
    function(connection){
        console.log((new Date()) + " Connection accepted.");
        connection.sendUTF('{"status" : "accepted"}');
    }
);

broadcastUTFメソッドを自作した以外は殆どサンプルコードと変わらない。

■Support draft-10 (chrome 14-dev+)

node.jsには何種類かWebSocket Serverの実装がある。

This will either be done
via node-websocket-protocol (preferable), or
via some kind of hack to the core of node-websocket-server, which is the route I really don’t want to take.

どうやらプロトコルだけ外に出してモジュール化することで対応する意向らしい。しかし、レスポンスが遅いのが気になる。

Socket IO

hybi10 incompatibility

軽く読んだ限りでは最新の仕様では不具合が発生するようだ。ロングポールなど代替手段が揃っているので有用な選択肢ではある。但し、今回はクライアント側のコードを変更したくないので選択肢から外した。

追記:8/30動作するようになった模様。

pywebsocket

node.js用のモジュールが存在しなければ、他の言語のモジュールで対応するという選択肢も考えた。pywebsocketではdraft-ietf-hybi-thewebsocketprotocol-07まで対応している。

Jetty

Jetty has supported the various WebSocket drafts in the 7.x and 8.x releases.

どうやら最新の実装には対応していないようだ。

■参考

The WebSocket protocol

draft-ietf-hybi-thewebsocketprotocol-10

Comparison table of websocket server implementations.

WebSocketサーバの実装が比較されているが更新されていないっぽい。

■まとめ

バージョンは適宜アップデートされるだろう。node.jsを選んで良かった。

後述

Socket.IOをインストールする

■node.js

以前の記事にも書いたんだけど以下のようにしてインストールできる。

su
yum install openssl-devel gcc-c++ git make
git clone git://github.com/creationix/nvm.git ~/.nvm
source ~/.nvm/nvm.sh
echo "~/.nvm/nvm.sh" >> ~/.bash_profile
echo "nvm use v0.5.0" >> ~/.bash_profile
nvm sync
nvm install v0.5.0
npm install node-base64

でも使い勝手に慣れなくて結局ソースからビルドしてしまったり。

■Socket.IO

以下のコマンドでインストールできる。

npm install socket.io

まぁ、一瞬で終わる。

使ってみる

サーバ側
var io = require('socket.io').listen(80);
io.sockets.on(
    'connection',// 接続された時
    function(socket){// 引数に接続(socket)をセットしてコールバックが実行される
        socket.on(
            'disconnect',
            function () {
                io.sockets.emit('message', 'hogehoge');// 全員にpushされる
            }
        );
    }
);

socketのbroadcastメソッドは接続している以外の全てのクライアントに対してpushされる。

クライアント側

サーバを起動させるとクライアント側で読み込むべきscriptが特定の位置に展開される。楽といえば楽。

<script type="text/javascript" src="http://127.0.0.1:80/socket.io/socket.io.js"></script>
<script type="text/javascript">
var socket = io.connect('http://127.0.0.1:80');
socket.on(
    'message',
    function (data) {
        console.log(data);
    }
);
</script>

MeCabをインストールする

■インストール

以下のコマンドでインストールできる。

wget https://mecab.googlecode.com/files/mecab-0.994.tar.gz
tar xvzf mecab-0.994.tar.gz
cd mecab-0.994
./configure
make
make install

辞書

utf-8で使用したいので以下のconfigureオプションを使用する。

wget https://mecab.googlecode.com/files/mecab-ipadic-2.7.0-20070801.tar.gz
tar xvzf mecab-ipadic-2.7.0-20070801.tar.gz 
cd mecab-ipadic-2.7.0-20070801
./configure --with-charset=utf8
make
make install

libiconvが必要になる。

macにインストールする

以下のコマンドでmacにインストールできる。

brew install mecab
brew install mecab-ipadic

■実行

以下のようにして使用する。

mecab
にわにはにわにわとりがいる
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
わに	名詞,一般,*,*,*,*,わに,ワニ,ワニ
はにわ	名詞,一般,*,*,*,*,はにわ,ハニワ,ハニワ
にわとり	名詞,一般,*,*,*,*,にわとり,ニワトリ,ニワトリ
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
いる	動詞,自立,*,*,一段,基本形,いる,イル,イル
EOS

ちょっと意地悪すぎたので入力を漢字にする。

庭には二羽鶏がいる
庭	名詞,一般,*,*,*,*,庭,ニワ,ニワ
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
二	名詞,数,*,*,*,*,二,ニ,ニ
羽	名詞,接尾,助数詞,*,*,*,羽,ワ,ワ
鶏	名詞,一般,*,*,*,*,鶏,ニワトリ,ニワトリ
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
いる	動詞,自立,*,*,一段,基本形,いる,イル,イル
EOS

正しく分類できた。

■Pythonで実行

久しぶりのPythonで遊ぶ。

python-devel

セットアップスクリプトを実行するのに必要になる。

yum insatll python-devel

mecab-python

以下のコマンドでインストールできる。

wget https://mecab.googlecode.com/files/mecab-python-0.996.tar.gz
tar xvzf mecab-python-0.98.tar.gz
cd mecab-python-0.98
python setup.py build
python setup.py install

テストスクリプトの文字コードを指定する。

vi test.py

2行目に以下のコードを加える。

# -*- coding: utf-8 -*-

以下のコマンドで実行する。

python test.py

■PHPで実行

php_mecabのインストール

su
pear channel-discover pecl.opendogs.org
pear install opendogs/mecab-beta
vim /private/etc/php.ini

Mountain LionにしたらPEARごと消滅してた

brew install autoconf

mecab.soが無いと言われて何もできなくなるので、php.iniを開いて一旦コメントアウトする。

su
vim /private/etc/php.ini

再度インストールする。

php /usr/lib/php/install-pear-nozlib.phar #PEARのインストール
pear channel-discover pecl.opendogs.org
pear install opendogs/mecab-beta #php-mecabのインストール
vim /private/etc/php.ini

ちなみに以下のようにパスを指定しないと

specify pathname to mecab-config [no] : /usr/local/bin

以下のようなエラーが出る。

checking for mecab-config... configure: error: not found
ERROR: `/var/tmp/mecab/configure --with-mecab=/usr' failed

以下の一行をextensionに付加する。

extension=mecab.so

実行

$mecab = new MeCab_Tagger();
for($node = $mecab->parseToNode($tag); $node; $node = $node->getNext()){
    var_dump($node->getSurface());
    var_dump($node->getFeature());
}

参考

UINavigationControllerを使う

新しい事をやりすぎて古いことを忘れそうなのでメモしておく。

■実装

早速実装する。

AppDelegate.h

#import <UIKit/UIKit.h>
#import "MainViewController.h"

@class MainViewController;

@interface SampleNavigationAppDelegate : NSObject <UIApplicationDelegate> {
@private
    UINavigationController *nav;
    MainViewController *mainViewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) UINavigationController *nav;
@property (nonatomic, retain) MainViewController *mainViewController;
@end

AppDelegate.m

#import "SampleNavigationAppDelegate.h"

@implementation SampleNavigationAppDelegate
@synthesize nav;
@synthesize mainViewController;

@synthesize window=_window;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    mainViewController = [[MainViewController alloc] init];
    nav = [[UINavigationController alloc] initWithRootViewController:mainViewController];
    nav.view.frame = [UIScreen mainScreen].applicationFrame;
    [self.window addSubview:nav.view];
    [self.window makeKeyAndVisible];
    return YES;
}

MainViewController.h

#import <UIKit/UIKit.h>
#import "SecondViewController.h"

@class SecondViewController;

@interface MainViewController : UIViewController {
    SecondViewController *secondViewController;
}
@property (nonatomic, retain) SecondViewController *secondViewController;
@end

MainViewController.m

@implementation MainViewController
@synthesize secondViewController;

-(void)onclick:(UIButton*)sender{
    secondViewController = [[SecondViewController alloc] init];
    [self.navigationController pushViewController:secondViewController animated:YES];
    
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.view setFrame:CGRectMake(0, 0, 320, 480)];
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 50)];
    [label setText:@"first view"];
    [self.view addSubview:label];
    [label release];
    
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [btn setFrame:CGRectMake(250, 0, 50, 30)];
    [btn addTarget:self action:@selector(onclick:) forControlEvents:UIControlEventTouchDown];
    [self.view addSubview:btn];
}

画面は凄い適当だけど・・・