LoginSignup
82
84

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-03-16

経緯

前回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でチャットルーム実装!

82
84
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
82
84