2019/08/17

PythonでSocketIO(サーバー編)

背景


PythonでSocketIO通信を行う必要があったため、今後の開発の効率化のためにSocketIO通信を行うクラスのテンプレートを作成する。

記事の目的


PythonでSocketIOサーバーを作成する

python-socketio


python-socketioを使用したSocketIOサーバーを作成する

選定理由

python-socketioの選定理由は、下記の通りである。

導入方法

python-socketioの導入手順は、下記の通りである。
  1. pipを利用してインストールする
  2. $ pip install requests eventlet python-socketio

テンプレート

python-socketioの使用テンプレートは、下記の通りである。
# socketioライブラリのインポート
import socketio
# ログ出力用ライブラリのインポート
from logging import getLogger, StreamHandler, DEBUG, INFO, ERROR
# 時間関係ライブラリのインポート
import time
# 終了シグナルをキャッチするライブラリのインポート
import signal
# eventletライブラリのインポート
import eventlet
# マルチスレッドライブラリをインポート
import threading

# ロガーのインスタンス作成
logger         = getLogger(__name__)
stream_handler = StreamHandler()

# ログレベルを設定
log_level = INFO
logger.setLevel(log_level)
stream_handler.setLevel(log_level)

# ロガーにハンドラーをセット
logger.addHandler(stream_handler)

# SocketIOのテンプレート
class SocketIOServer:
    # namespaceの設定用クラス
    class NamespaceClass(socketio.Namespace):
        def on_connect(self, sid, environ):
            pass
        def on_disconnect(self, sid):
            pass
        def on_message(self, sid, data):
            pass
        def on_client_to_server(self, sid, data):
            pass
    # 接続時に呼ばれるイベント
    def on_connect(self, sid, environ):
        logger.info('Connected to client (sid: %s, environ: %s)',\
                     str(sid), str(environ))
        self.is_connect_ = True
    # 切断時に呼ばれるイベント
    def on_disconnect(self, sid):
        logger.info('Disconnected from client (sid: %s)',str(sid))
        self.is_connect_ = False
    # サーバーからデータ送信(send)する
    def send_data(self, data):
        try:
            self.sio_.send(data, namespace=self.namespace_) 
        except:
            logger.error('Has not connected to client')
        else:
            logger.info('Send message %s (namespace="%s")',\
                         str(data), self.namespace_) 
    # 独自定義のイベント名「server_to_client」で、サーバーからデータ送信(emit)する
    def emit_data(self, data):
        try:
            self.sio_.emit('server_to_client', data, namespace=self.namespace_)
        except:
            logger.error('Has not connected to client')
        else:
            logger.info('Emit message %s (namespace="%s")',\
                         str(data), self.namespace_) 
    def on_message(self, sid, data):
        logger.info('Received message %s', str(data)) 
        self.send_data({'test_ack': 'send_from_server'})
    # クライアントからイベント名「on_client_to_server」でデータがemitされた時に呼ばれる
    def on_client_to_server(self, sid, data):
        logger.info('Received message %s', str(data))
        self.emit_data({'test_ack': 'emit_from_server'})
    # Namespaceクラス内の各イベントをオーバーライド
    def overload_event(self):
        self.Namespace.on_connect          = self.on_connect
        self.Namespace.on_disconnect       = self.on_disconnect
        self.Namespace.on_message          = self.on_message
        self.Namespace.on_client_to_server = self.on_client_to_server
    # 初期化
    def __init__(self,ip,port,namespace):
        self.ip_          = ip
        self.port_        = port
        self.namespace_   = namespace
        self.is_connect_  = False
        self.sio_         = socketio.Server(async_mode='eventlet')
        self.app_         = socketio.WSGIApp(self.sio_)
        self.Namespace    = self.NamespaceClass(self.namespace_)
        self.overload_event()
        self.sio_.register_namespace(self.Namespace)
    # 接続確認
    def isConnect(self):
        return self.is_connect_
    # 切断
    def disconnect(self,sid):
        try:
            self.sio_.disconnect(sid,namespace=self.namespace_)
        except:
            logger.error('Cannot disconnect from Client(sid="%s", namespace="%s")',\
                          namespace=self.namespace_)
        else:
            self.is_connect_ = False
    # 開始
    def start(self):
        try:
            logger.info('Start listening(%s:%d, namespace="%s")',\
                         self.ip_,self.port_,self.namespace_)
            eventlet.wsgi.server(eventlet.listen((self.ip_, self.port_)), self.app_)
        except: 
            logger.error('Cannot start listening(%s:%d, namespace="%s")',\
                          self.ip_,self.port_,self.namespace_)
    # メインの処理
    def run(self):
        p = threading.Thread(target=self.start)
        p.setDaemon(True)
        p.start()

if __name__ == '__main__':
    # Ctrl + C (SIGINT) で終了
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    # SocketIO Server インスタンスを生成
    sio_server = SocketIOServer('localhost', 10000, '/test')
    # SocketIO Server インスタンスを実行
    sio_server.run()
    # 接続待ち
    while not sio_server.isConnect():
        time.sleep(1)
    # 切断待ち
    while sio_server.isConnect():
        time.sleep(1)
    logger.info('Finish')
    # 終了
    exit()

備考

  • namespaceは、必ず「/」(スラッシュ)から始まる必要がある
  • eventletを使用した場合、他の作業スレッドから、SocketIO内のEmitやSendを呼び出せない
  • 上記テンプレートは、クライアントと通信テストができる

まとめ


  • Python-SocketIOを利用したサーバーモジュールを作成した

参考文献



変更履歴


  1. 2019/08/17: 新規作成

0 件のコメント:

コメントを投稿

MQTTの導入

背景 IoTデバイスの接続環境構築のため、MQTT(mosquitto)の導入を行った。 記事の目的 MQTT(mosquitto)をUbuntuに導入する mosquitto ここではmosquittoについて記載する。 MQTT MQTT(Message Qu...