JavaScript
iOS
Socket.io

socket.io × SIOSocketでマルチルーム対応 iOSチャットアプリを作ってみた

More than 3 years have passed since last update.


経緯

前回slackのライブラリを調べた時にwebソケットライブラリ、rocketsocketが入っていて、面白そうだなと思ったので、早速使ってみようかと。webソケットといえばチャット!チャットアプリを作ってみます。

まずは、そのまんまsocketrocketを見てみたんですが、理解ゼロからチャットやろうとすると、結構大変そうだな...と思い、socket.ioにroomsという機能があるのをみつけました。お、これは使えそうだと調べて行ったら、iOSでもwebViewでsocket.ioのクライアントを書いてくれている人がいました。SIOSocketというやつです。これはいけそうだなということで、やってみました。結果、わりと大変でしたが、なんとか動くものはできたかなーとおもったのでアップしてみます。


できたもの

なんかびみょーな仕様ですが...


  • アプリ起動時に自動的に自分のroomに入る

  • 今のところフォアグラウンドでの動作のみ(バックグラウンドでは気が向いたら...)

  • もし他の人もアプリを立ち上げていれば左側のドロワーにでてくるので、そこから選択してjoinできます。同時に複数のroomにjoinできます。

ナビゲーションバーのタイトルの部分がいわゆる部屋の名前です...便宜上socket idにしてるので、無機質極まりないです。名前とか吹き出しの下に出せればよかったです。追々やってみます。

※ ちなみにこのフキダシは JSQMessagesViewController というものをつかっています!使い方は、Developer.IOさんの記事をご参考に。

iOS Simulator Screen Shot Mar 17, 2015, 07.07.15.png

お部屋一覧がみえます。一応ニックネームの変更もできます。

iOS Simulator Screen Shot Mar 17, 2015, 07.05.34.png


用意したもの


()の中は今回利用した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番ポートを使うのでそこだけ開けるように注意

Screen Shot 2015-03-06 at 16.44.45.png

# 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の中で定義してあげてます。


ChatViewController.h

- (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)
{
/* ~ここ省略~ */
}];
}];
}


他のノードに通知する場合は、


ChatViewController.h

[self.socket emit:@"post" args:@[[param paramStr]]];


こんなふうにかけます。


サーバ側

これだけ!


server.js

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-redissocket.io-emitterを使えばsocket.io以外のプロセスからsocket.ioにデータを送れるので既存のphpとかrailsのAPIからでも連携できるし、スケールアウトもできるのは良いのでは。



参考

Socket.io

SIOSocket

(公式ページ)Rooms and Namespaces

WebSocket 事始め by Node.js + Socket.IO

Node.jsにてsocket.ioのjoinでチャットルーム実装!