経緯
前回slackのライブラリを調べた時にwebソケットライブラリ、rocketsocketが入っていて、面白そうだなと思ったので、早速使ってみようかと。webソケットといえばチャット!チャットアプリを作ってみます。
まずは、そのまんまsocketrocketを見てみたんですが、理解ゼロからチャットやろうとすると、結構大変そうだな...と思い、socket.ioにroomsという機能があるのをみつけました。お、これは使えそうだと調べて行ったら、iOSでもwebViewでsocket.ioのクライアントを書いてくれている人がいました。SIOSocketというやつです。これはいけそうだなということで、やってみました。結果、わりと大変でしたが、なんとか動くものはできたかなーとおもったのでアップしてみます。
できたもの
なんかびみょーな仕様ですが...
- アプリ起動時に自動的に自分のroomに入る
- 今のところフォアグラウンドでの動作のみ(バックグラウンドでは気が向いたら...)
- もし他の人もアプリを立ち上げていれば左側のドロワーにでてくるので、そこから選択してjoinできます。同時に複数のroomにjoinできます。
ナビゲーションバーのタイトルの部分がいわゆる部屋の名前です...便宜上socket idにしてるので、無機質極まりないです。名前とか吹き出しの下に出せればよかったです。追々やってみます。
※ ちなみにこのフキダシは JSQMessagesViewController というものをつかっています!使い方は、Developer.IOさんの記事をご参考に。
用意したもの
()の中は今回利用したversionなど
- ec2インスタンス (Amazon Linux AMI release 2014.09)
- node.js (v0.12.0)
- socket.io (1.3.5)
構成
今回は、iOSアプリ上のSIOSocket <-> ec2上のnodeサーバ with socket.io って感じでやってみます。vagrantだと感動が多少減るので、ec2に置いてリアルインターネットで。
手順
1. nodeサーバを用意
websocket通信ではtcp3000番ポートを使うのでそこだけ開けるように注意
# nodebrewをインストール
wget git.io/nodebrew
perl nodebrew setup
# nodebrewのパスを通す
vim ~/.bashrc
PATH=$HOME/.nodebrew/current/bin:$PATH
# nodeの最新安定版(バイナリ)をインストール
nodebrew install-binary stable
# lsして出てきたものをuse
nodebrew ls
nodebrew use v0.12.0
# 上記versionが出てきたら成功
node -v
# 適当なディレクトリに移動
mkdir ~/SIOChatServer
cd ~/SIOChatServer
# socket.ioのインストール
npm install socket.io
node用サーバにscpでserver.jsを配置
scp -i path/to/your.pem server.js ec2-user@yourhostname:/home/ec2-user/SIOChatServer
あとは実行するだけ。のはず。
# nodeサーバ実行 (デバッグしたい場合は DEBUG*= node server 使うといいかも)
node server
2. アプリのソースを準備
https://github.com/mitolog/SIOChat
こちらにソースを上げたので、cloneして使って下さい。その際、AppConst.hファイルのkWebSocketHostNameを適宜ご自身のものに変えてください。その際、スキーマをws://
にしといたほうがいいと思います。あとポート指定も。
WebSocket通信を担っている箇所
アプリ側
WebSocketの受け側は、SIOSocketのメソッドで一発です。コールバックをblocksの中で定義してあげてます。
- (void)connectWs
{
// Close existing connection if needed
if(self.socket){ [self.socket close];}
[SIOSocket socketWithHost: kWebSocketHostName response: ^(SIOSocket *socket){
self.socket = socket;
__weak typeof(self) weakSelf = self;
self.socket.onConnect = ^()
{
/* ~ここ省略~ */
[weakSelf.socket emit: @"init" args: @[[param paramStr]]];
};
[self.socket on: @"join" callback: ^(SIOParameterArray *args)
{
/* ~ここ省略~ */
}];
[self.socket on: @"update" callback: ^(SIOParameterArray *args)
{
/* ~ここ省略~ */
}];
[self.socket on: @"rooms" callback: ^(SIOParameterArray *args)
{
/* ~ここ省略~ */
}];
[self.socket on: @"disappear" callback: ^(SIOParameterArray *args)
{
/* ~ここ省略~ */
}];
}];
}
他のノードに通知する場合は、
[self.socket emit:@"post" args:@[[param paramStr]]];
こんなふうにかけます。
サーバ側
これだけ!
var io = require('socket.io')(3000);
var sepStr = ',';
var roomIdx = 0;
var isParentIdx = 1;
var nicknameIdx = 3;
var joinedRooms = [];
var nicknames = [];
io.on('connection', function (socket) {
console.log(socket['id'] + ' has connected!');
// join the specific room everytime socket connected
socket.on('init', function(data){
var params = data.split(sepStr);
var isParent = params[isParentIdx];
var room = params[roomIdx]
var output = data;
if(isParent == '1'){
room = socket['id'];
output = room;
output += data;
nicknames[socket['id']] = params[nicknameIdx];
}else{
socket.join(room);
}
joinedRooms[socket['id']] = socket.rooms;
io.emit('rooms', io.sockets.adapter.rooms);
io.to(room).emit('join', output);
console.log('joined room ' + room);
});
// bradcast the specific room everytime someone posted some
socket.on('post', function (data) {
console.log(socket['id'] + ' ' + data);
var room = data.split(sepStr)[roomIdx];
io.to(room).emit('update', data);
});
// clients are disconnected automatically everytime they going background
socket.on('disconnect', function (data) {
io.emit('rooms', io.sockets.adapter.rooms);
var message = nicknames[socket['id']];
message += ' left room';
message += sepStr;
var prevJoinedRooms = joinedRooms[socket['id']];
for(var i=0; i<prevJoinedRooms.length; i++){
var room = prevJoinedRooms[i];
io.to(room).emit('disappear', message + room);
console.log(message + room);
}
console.log(socket['id'] + ' has disconnected!');
});
});
感想
-
アプリもサーバも、まだまだ無駄なソースあるんですが、それでも結構少ないコードですんだと思っていて、nodeすごいなーsocket.ioすごいなーと感動しました。
-
リアルタイムにチャットが飛んできた時はちょっと感動しました。
-
slackってやっぱりすげーなー...
-
socket.io-redisとsocket.io-emitterを使えばsocket.io以外のプロセスからsocket.ioにデータを送れるので既存のphpとかrailsのAPIからでも連携できるし、スケールアウトもできるのは良いのでは。