PythonでWebRTCのシグナリングサーバーを作ってみる #webrtcjp


こんにちは、n0bisukeと申します。

Pythonのアドベントカレンダーの担当日ですが、WebRTCのもくもく会に来ています。

> [アドベントカレンダーを埋めよう！ WebRTCもくもく会](https://webrtc.connpass.com/event/72799/)

せっかくなのでPythonでWebRTCのシグナリングサーバーを作ってみます。

~~現状だとシグナリングサーバーというかシグナリングしてみたって感じなのでアップデートしたい[WIP]~~
↑しました。

普段はPythonを使ってないのでニュアンス違うなぁってのがあれば教えてください

## これまでにやったこと

* [Phoenix Channelを使ってWebRTCのシグナリングをするメモ #beamlangtokyo](https://qiita.com/n0bisuke/items/c8c06b5f1bb545149fcd)
* [WebRTCのシグナリングサーバーにMilkcocoaを使って見るメモ #webrtcjp #mlkcca](https://qiita.com/n0bisuke/items/2d88d5b31b9bb7e7ac1e)

シグナリングだけやってますね()

## 調査

Pythonは普段触らないので調査からスタートです。

* [python-socketio](https://github.com/miguelgrinberg/python-socketio)
* [dpallot/simple-websocket-server](https://github.com/dpallot/simple-websocket-server)
* [websocket-server](https://pypi.python.org/pypi/websocket-server/0.4)

などが見つかりました。

simple-websocket-serverがパッと見でシンプル&分かりやすそうなのでこれを使ってみます。

たぶん最後のリンクが公式感あるやつですけど今回は執筆スピード重視で......笑

### simple-websocket-serverを触ってみる

#### サーバー側

まずはpipでモジュール追加

```
pip install git+https://github.com/dpallot/simple-websocket-server.git
```

サンプルコードを記述

```testapp.py
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket

class SimpleEcho(WebSocket):

    def handleMessage(self):
        # echo message back to client
        self.sendMessage(self.data)

    def handleConnected(self):
        print(self.address, 'connected')

    def handleClose(self):
        print(self.address, 'closed')

server = SimpleWebSocketServer('', 8000, SimpleEcho)
server.serveforever()
```

実行します。

```
python testapp.py
```

何も表示されないけどサーバーは立ち上がった模様。

`localhost:8000`でwsサーバーが起動します。

#### クライアント側

[サンプルコード](https://github.com/dpallot/simple-websocket-server/blob/master/SimpleWebSocketServer/websocket.html)をそのまま利用します。

index.htmlとして8001番ポートでサーバー起動します。

```
python -m SimpleHTTPServer 8001
```

`http://localhost:8001`にブラウザからアクセスするとこんな感じのページになります。 

![](https://i.gyazo.com/62a15c3f5eb8cb53a78310f8908bd31d.png)

`Connect`ボタンを押すと8000番ポートのwsサーバーに接続されます。

![](https://i.gyazo.com/b9f6b62538508161a16766b84ff3b44c.png)

左側がwsサーバー(8000番)、右側がwebサーバー(8001番)です。

## シグナリングしてみる

ここまで出来ればあとは応用ですね

大元のコードは[こちらのハンズオン](https://github.com/yusuke84/webrtc-handson-2016)資料の内容です。

### このままだと今回もこのエラーが出ます

![](https://i.gyazo.com/4abb86e27d4c1393229a1fdef58f54d6.png)

```
setRemoteDescription(answer) ERROR:  DOMException: Failed to set remote answer sdp: Called in wrong state: STATE_INPROGRESS
```

[Node.jsでのシグナリングサーバーサンプル](https://github.com/yusuke84/webrtc-handson-2016/blob/handson_step4/server/signaling.js)ではオブジェクト比較をしてwebsocketクライアントが同じかを判定していますが、

```
function isSame(ws1, ws2) {
    // -- compare object --
    return (ws1 === ws2);
}
```

~~Python側で比較をするよりクライアント側でUUID発行のほうが早そうだったのでクライアント側で判定するようにします。~~

このロジックをPythonで書いてみます。

### サーバー側

ほぼ`testapp.py`の内容と同じですが`signaling.py`を作成しました。

```signaling.py
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket

clients = []

class SimpleEcho(WebSocket):

    def handleMessage(self):
        for client in clients:
            if client != self: #judge
                print('-----on----')
                client.sendMessage(self.data)
            else:
                print('----skip-----')                

    def handleConnected(self):
        print(self.address, 'connected')
        clients.append(self) #add client

    def handleClose(self):
        print(self.address, 'closed')

server = SimpleWebSocketServer('', 8000, SimpleEcho)
server.serveforever()
```

解説すると、

```
    def handleConnected(self):
        print(self.address, 'connected')
        clients.append(self) #add client
```

ここで新規アクセスがあるたびにクライアント情報を記録します。

```
def handleMessage(self):
        for client in clients:
            if client != self: #judge
                print('-----on----')
                client.sendMessage(self.data)
            else:
                print('----skip-----')
```

ここで前述したエラーに対応するために、同じクライアントからの情報はスキップさせます。

### クライアント側

[こちらのハンズオンの内容](https://github.com/yusuke84/webrtc-handson-2016/tree/handson_step4/client)をそのまま利用です。

## 終わりに

これでシグナリングが出来ました。 コードは[こちら](https://github.com/n0bisuke/python-webrtc-signaling)です。

~~結果、全くPython触らずに出来てしまったので、せめてサーバーサイドで同一クライアントの情報をスキップする処理を書けるかためしてアップデートしたいと思っています。~~

Pythonでシグナリングサーバーを作ることができました。簡単ですね。
Node.jsのコードと比較してもシンプルに書けるなぁと感動しています。

![](https://i.gyazo.com/26afada9ba56e7066477a737643b1d16.gif)

もう少し時間つくってMicro Pythonさわりたい...

現場からは以上です！