ラベル Python の投稿を表示しています。 すべての投稿を表示
ラベル Python の投稿を表示しています。 すべての投稿を表示

2020/05/10

python-socketioでAttributeError: 'module' object has no attribute 'main_thread'が出る場合の対処法

背景


Python2.7でpython-socketioを使用したとき時、下記のエラーが発生した。
$ python ./test_socketio_client.py 
Traceback (most recent call last):
  File "./test_socketio_client.py", line 123, in 
    sio_client = SocketIOClient('127.0.0.1', 10000, 'test', 'secret')
  File "./test_socketio_client.py", line 76, in __init__
    self.sio_        = socketio.Client()
  File "/home/emptyset/.local/lib/python2.7/site-packages/socketio/client.py", line 88, in __init__
    threading.current_thread() == threading.main_thread():
AttributeError: 'module' object has no attribute 'main_thread'

記事の目的


python-socketioのAttributeErrorを解決する

原因と対策


エラー原因

threadingモジュールのバージョン問題である。threadingの説明によると、main_thread関数は、バージョン 3.4以降で追加されたメソッドであるため、Python2系では使用できない。

対策

該当モジュールのmain_thread関数を修正する。
  1. python-socketioの該当モジュール(例/home/emptyset/.local/lib/python2.7/site-packages/socketio/client.py)を修正する
  2. 修正前
            global original_signal_handler
            if original_signal_handler is None and \
                    threading.current_thread() == threading.main_thread():
                original_signal_handler = signal.signal(signal.SIGINT,
                                                        signal_handler)
    修正後
            global original_signal_handler
            if original_signal_handler is None and\
                    threading.current_thread().isDaemon() is False:
                original_signal_handler = signal.signal(signal.SIGINT,
                                                        signal_handler)
    Clientのインスタンス生成がmainスレッドでのみ可能になってるが、この部分を削除した。代わりに、スレッドがデーモンかの判定を追加した。(mainスレッド以外がデーモンであると仮定)
  3. python-engineioの該当モジュール(例/home/emptyset/.local/lib/python2.7/site-packages/engineio/client.py)を修正する
  4. 修正前
            global original_signal_handler
            if original_signal_handler is None and \
                    threading.current_thread() == threading.main_thread():
                original_signal_handler = signal.signal(signal.SIGINT,
                                                        signal_handler)
    修正後
            global original_signal_handler
            if original_signal_handler is None and \
                    threading.current_thread().isDaemon() is False:
                original_signal_handler = signal.signal(signal.SIGINT,
                                                        signal_handler)
    Clientのインスタンス生成がmainスレッドでのみ可能になってるが、この部分を削除した。代わりに、スレッドがデーモンかの判定を追加した。(mainスレッド以外がデーモンであると仮定)
  5. 動作確認を行う
  6. $ python ./test_socketio_client.py 
    Try to connect to server(localhost:10000, namespace="/test", query="secret")
    Namespace may be invalid.(namespace="/test")
    Connected to server (localhost:10000, namespace="/test", query="secret")
    Send message {'test_data': 'send_from_client'} (namespace="/test")
    Emit message {'test_data': 'emit_from_client'} (namespace="/test")
    Received message {u'test_ack': u'send_from_server'}
    Received message {u'test_ack': u'emit_from_server'}
    ...

まとめ


  • python-socketioでAttributeError: 'module' object has no attribute 'main_thread'が出る場合の対処法について調査、記載した

参考文献



変更履歴


  1. 2020/05/10: 新規作成

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 python-socketio

テンプレート

python-socketioの使用テンプレートは、下記の通りである。
# socketioライブラリのインポート
import socketio
# ログ出力用ライブラリのインポート
from logging import getLogger, StreamHandler, DEBUG, INFO, ERROR
# 時間関係ライブラリのインポート
import time
# 終了シグナルをキャッチするライブラリのインポート
import signal
# マルチスレッドライブラリのインポート
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 SocketIOClient:
    # namespaceの設定用クラス
    class NamespaceClass(socketio.ClientNamespace):
        def on_connect(self):
            pass
        def on_disconnect(self):
            pass
        def on_message(self, data):
            pass
        def on_server_to_client(self, data):
            pass
    # 接続時に呼ばれるイベント
    def on_connect(self):
        logger.info('Connected to server (%s:%d, namespace="%s", query="%s")',\
                     self.ip_,self.port_,self.namespace_,self.query_)
        self.is_connect_ = True
    # 切断時に呼ばれるイベント
    def on_disconnect(self):
        logger.info('Disconnected from server (%s:%d, namespace="%s", query="%s")',\
                     self.ip_,self.port_,self.namespace_,self.query_)
        self.is_connect_ = False
    def on_message(self, data):
        logger.info('Received message %s', str(data)) 
    # サーバーからイベント名「server_to_client」でデータがemitされた時に呼ばれる
    def on_server_to_client(self, data):
        logger.info('Received message %s', str(data)) 
    # 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_server_to_client = self.on_server_to_client
    # 初期化
    def __init__(self,ip,port,namespace,query):
        self.ip_         = ip
        self.port_       = port
        self.namespace_  = namespace
        self.query_      = query
        self.is_connect_ = False
        self.sio_        = socketio.Client()
        self.Namespace   = self.NamespaceClass(self.namespace_)
        self.overload_event()
        self.sio_.register_namespace(self.Namespace)
    # 接続確認
    def isConnect(self):
        return self.is_connect_
    # 接続
    def connect(self):
        # 接続先のURLとqueryの設定
        url = 'ws://'+self.ip_+':'+str(self.port_)+'?query='+self.query_
        logger.info('Try to connect to server(%s:%d, namespace="%s", query="%s")',\
                     self.ip_,self.port_,self.namespace_,self.query_)
        try:
            self.sio_.connect(url, namespaces=self.namespace_)            
        except: 
            logger.error('Cannot connect to server(%s:%d, namespace="%s", query="%s")',\
                          self.ip_,self.port_,self.namespace_,self.query_)
        else: 
            if not self.is_connect_:
                logger.error('Namespace may be invalid.(namespace="%s")',\
                              self.namespace_)   
    # 切断
    def disconnect(self):
        try:
            self.sio_.disconnect()
        except:
            logger.error('Cannot disconnect from server(%s:%d, namespace="%s", query="%s")',\
                          self.ip_,self.port_,self.namespace_,self.query_)
        else:
            self.is_connect_ = False
    # 再接続
    def reconnect(self):
        self.disconnect()
        time.sleep(5)
        self.connect()
    # クライアントからデータ送信(send)する
    def sendData(self, data):
        try:
            self.sio_.send(data, namespace=self.namespace_)
        except:
            logger.error('Has not connected to server(%s:%d, namespace="%s", query="%s")',\
                          self.ip_,self.port_,self.namespace_,self.query_)
        else:
            logger.info('Send message %s (namespace="%s")', str(data), self.namespace_) 
    # 独自定義のイベント名「client_to_server」で、クライアントからデータ送信(emit)する
    def emitData(self, data):
        try:
            self.sio_.emit('client_to_server', data, namespace=self.namespace_)
        except:
            logger.error('Has not connected to server(%s:%d, namespace="%s", query="%s")',\
                          self.ip_,self.port_,self.namespace_,self.query_)
        else:
            logger.info('Emit message %s (namespace="%s")', \
                         str(data), self.namespace_)  
    # メインの処理
    def run(self):
        while True:
            self.connect()
            time.sleep(1)
            if self.is_connect_:
                break
        p = threading.Thread(target=self.sio_.wait)
        p.setDaemon(True)
        p.start()
    
if __name__ == '__main__':
    # Ctrl + C (SIGINT) で終了
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    # SocketIO Client インスタンスを生成
    sio_client = SocketIOClient('localhost', 10000, '/test', 'secret')
    # SocketIO Client インスタンスを実行
    sio_client.run()
    # データを送信
    for i in range(10):
        sio_client.sendData({'test_data': 'send_from_client'})
        sio_client.emitData({'test_data': 'emit_from_client'})
        time.sleep(1)
    # 切断
    sio_client.disconnect()
    logger.info('Finish')
    # 終了
    exit()

備考

  • namespaceは、必ず「/」(スラッシュ)から始まる必要がある
  • 上記テンプレートは、サーバーと通信テストができる

まとめ


  • Python-SocketIOを利用したクライアントモジュールを作成した

参考文献



変更履歴


  1. 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: 新規作成

MQTTの導入

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