読者です 読者をやめる 読者になる 読者になる

タオルケット体操

サツバツいんたーねっと

MQTTとGo言語を使って5分でチャットアプリを作る

自宅改造計画のためにちょいちょい使ってて便利っぽいので一通りのAPIの使い方とかのアレをメモ。

MQTTとは

MQTTとは、1999年にIBM社が中心となって策定したプロトコルです。

主な特徴としては

  • TCP/IPの上で動作し、pub/sub形式を採用しているので一対多や双方向の通信ができる

  • プロトコルのヘッダが最小2バイトと小さい

  • 軽量なので、モバイル(昨今では特にバッテリーの貧弱なIoT用途)などでの要求スペックや電力消費も少ない

などが挙げられます。

詳しいところはこちらなどを参考にしてください: https://sango.shiguredo.jp/mqtt

ここ数年でにわかにIoT(Internet of Things)とかいうバズワードが盛り上がってきてますが、MQTTはその中でもよく使われるプロトコルになるであろうことが予想されます。されるらしいです。
バッテリー消費に関しては、HTTPの1/10という話もあります。

すでにIBMは独自に実装したMQTTブローカーを載せたアプライアンスを販売しており、医療現場などで導入が進んでいるみたいです。

MQTTブローカーとは

Pub/Sub形式で、PublishされたメッセージをSubscriberに配信する役割を追ったサーバのことです。

現存する実装

今のところ、オープンソースの実装は多くありません。その殆どがプロプライエタリになります。
日本では時雨堂という会社が作っています。

OSSではRabbitMQというMessageQueueのソフトのプラグインに存在しています。
その他、Mosquitto、Apolloなどが有名っぽいです。プロトコル自体はシンプルっぽいので、野良の実装自体は多数存在しています。なんなら簡単に作れるとおもいます(風格)。

面白いことに、Erlangという言語を使うところが多いです。先のIBMや、時雨堂、またRabbitMQなどはどれもその言語を使っています。
同時接続100万、秒間1000万件のメッセージングという要件を満たすには、ネットワークプログラミング向きの並列指向言語であるErlangの特性がうってつけなのかもしれません。

あと、この日記を書いてたらAWSがAWS IoTというものを発表してくれました。Thing Shadowの概念がReactのShadow DOMっぽくてかっちょいいとおもいました(小学生並みの感想)

チャットアプリを実装

端的なサンプルとして、超簡単なチャットアプリを作ってみます。5分もかかりません。たぶん。

MQTTブローカーの準備(MQTT as a Service こと Sangoを利用します)

RabbitMQかなんかで適当に構築するか、時雨堂が用意しているSangoというサービスを利用するのが楽でしょう。
Sangoには無料プランがあり、またGithubのアカウントを使って登録できるので僕はこちらを使いました。10秒で準備が終わります。便利ですね。

とはいえTLSなどは有料プランでしか使えないので、本格的に利用するならお金を払うのがいいんじゃあないでしょうか。

クライアントの実装

Go言語を使います。
クライアントの実装には、PahoというOSSライブラリを利用します。

Goのコンパイラはインストールしてあることにします。
Pahoをインストールします。

go get git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git

Pahoに関して、古いAPIの記事が多いみたいですが最近のやつはAPIが変わってるので注意しましょう。サンプルが少ないですが、まぁ公式を見れば問題ないでしょう。

では、次のコードを適当なファイルに保存します。

package main

import (
    "fmt"
    // via: https://eclipse.org/paho/clients/golang/
    MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
    "os"
)

// Sangoを使ってるならダッシュボード、自分で立てたならそのIPです
const MQTT_BROKER = "tcp://uri.to.mqtt:1883"

func createMQTTClient(brokerAddr, clientId, username, password string) *MQTT.Client {
    opts := MQTT.NewClientOptions().AddBroker(brokerAddr)
    opts.SetClientID(clientId)
    opts.SetUsername(username)
    opts.SetPassword(password)
    client := MQTT.NewClient(opts)
    return client
}

func subscribe(client *MQTT.Client, sub chan<- MQTT.Message) {
    fmt.Println("start subscribing...")
        // forループしなくても勝手に中でループしててくれます。なんかそういうのってあんまりGolangっぽくない気がしますけど。
    subToken := client.Subscribe(
        "path/to/topic",  // Sangoのダッシュボードからコピペしましょう
        0,
        func(client *MQTT.Client, msg MQTT.Message) {
            sub <- msg
        })
    if subToken.Wait() && subToken.Error() != nil {
        fmt.Println(subToken.Error())
        os.Exit(1)
    }
}

func publish(client *MQTT.Client, input string) {
    token := client.Publish("path/to/topic/chat", 0, true, input)
    if token.Wait() && token.Error() != nil {
        panic(token.Error())
    }
}

func input(pub chan<- string) {
    for {
        var input string
        fmt.Scanln(&input)
        pub <- input
    }
}

func main() {
    fmt.Print("your id: ")
    var id string
    fmt.Scanln(&id)
    client := createMQTTClient(MQTT_BROKER, id, "YourID", "YouPassword")
    defer client.Disconnect(250)
    if token := client.Connect(); token.Wait() && token.Error() != nil {
        panic(token.Error())
    }
    sub := make(chan MQTT.Message)
    go subscribe(client, sub)
    pub := make(chan string)
    go input(pub)
    for {
        select {
        case s := <-sub:
            msg := string(s.Payload())
            fmt.Printf("\nmsg: %s\n", msg)
        case p := <-pub:
            publish(client, p)
        }
    }
}

シンプルですね。
ユーザidやパスワード、接続するトピックのくだりはそれぞれの環境に合わせて書き換えてください。

読みやすくするためにわざと冗長に書いてますが、それでも100行いってません。
GoroutineとChannelという素晴らしい仕組みを利用しているため、簡単なコードですが非同期、マルチスレッドで動作しています。
もうちょっと真面目に書くならば、Goroutineをグレースフルに終了させるためのchanも必要かも(多分)しれませんが、今回は簡単のために省略してます。

では実行してみましょう。
まずはユーザ1です。

$ go run chat.go
your id: testuser1
start subscribing...

別の端末でもう一人を立ち上げます

$ go run chat.go
your id: testuser2
start subscribing...

この状態で何かメッセージを打ち込むと、相手が msg: hogehoge という格好のメッセージを受信するようになったとおもいます。
お疲れ様でした。

まとめ

MQTTについて

  • 策定自体はすごい古い

  • IoTに関連して、ここ数年で急にMQTTが盛り上がってるっぽい

  • 医療現場、スマートホーム、スマートオフィス、自動車などのデータ収集に、MQTTアプライアンスはあってもいいかも

  • OSSのデファクト的な実装はまだまだ少ない

  • こういう用途でのErlangすごそう(でも今後はElixirか、もっと新しいErlang VM上の言語が盛り上がる気がする)

  • Sango便利(時雨堂さん強そう)

Golangについて

  • Goroutineとchanが便利だよね

  • 良さ

以上です。

WEB+DB PRESS Vol.88

WEB+DB PRESS Vol.88

  • 作者: 佐々木拓郎,高柳怜士,鶴原翔夢,小野侑一,中村俊介,佐藤春旗,長野雅広,佐々木健一,久保達彦,若山史郎,佐藤太一,伊藤直也,道井俊介,佐藤歩,泉水翔吾,坪内佑樹,海野弘成,西尾泰和,中島聡,はまちや2,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2015/08/22
  • メディア: 大型本
  • この商品を含むブログを見る