PythonのZeroMQライブラリpyzmqを使って、公開鍵を使った通信をしてみました。
ZeroMQ
- MQTTはBrokerが必要になりますが、ZeroMQはBrokerなしで軽量で高速に1対1通信ができるので便利です。 ZeroはBrokerなし、管理なし、コストなし。
- MQTTは、Brokerを立ててN:N通信や1:Nの時に便利。 AWS IoT CoreもMQTT。
- ZeroMQはデバイス内でデータ交換をする時に、Brokerや管理(Administration)など不要なので軽量にできて便利。
- セキュリティは、MQTTはTLSに対応しているので強固。 ZeroMQは公開鍵を使ったCurveMQ、ZAPがあるけど、2017から更新されていないらしいのでそこまで強固でなさそう。(2024/4/1更新のHiveMQサイトで書かれていました。)
- MQTTはBrokerがServerで他は全てClientになり、ClientはBrokerに接続する。
- ZeroMQTTは一方がServerで他がClient。 Serverが先に立ち上がって、そこにClientが接続する。
CurveMQ
- ZeroMQに追加されたCurveMQを使って公開鍵をmmapで受け取り通信しています。
- zmq_rep.pyがserverとしてbindしています。
- zmpのバージョンは25.1.2。
import os import zmq import mmap context = zmq.Context() socket = context.socket(zmq.REP) def server(): global socket server_public_key, server_secret_key = zmq.curve_keypair() socket.curve_secretkey = server_secret_key socket.curve_publickey = server_public_key _pub_key_size=len(server_public_key) #print(_pub_key_size) file_path = 'example.txt' if not os.path.exists(file_path): with open(file_path, 'wb') as file: file.write(40*b"\0") with open(file_path, 'r+b') as file: mm = mmap.mmap(file.fileno(), _pub_key_size, access=mmap.ACCESS_WRITE) mm[0:_pub_key_size] = server_public_key #print("server pub key",server_public_key) #print("in mmap:",mm.readline()) mm.close() socket.curve_server = True try: socket.bind("tcp://*:50000") except zmq.error.ZMQError as e: print("ZMQError:", e) os._exit(1) while True: message = socket.recv() print("Received request: %s" % message) socket.send(b"World") if __name__ == "__main__": server()
clientの方は鍵ペアを作ってsocket.client_secretekeyとsocket.client_publickeyに格納しておくだけでいいようです。 これをしないと通信しません。
import zmq import mmap import time print(zmq.__version__) context = zmq.Context() socket = context.socket(zmq.REQ) client_secret_key = b"client_secret_key" # クライアントの秘密鍵 client_public_key, client_secret_key = zmq.curve_keypair() # クライアントの公開鍵と秘密鍵を生成 socket.curve_secretkey = client_secret_key socket.curve_publickey = client_public_key file_path='./example.txt' with open(file_path, 'r+b') as file: mm = mmap.mmap(file.fileno(),0, access=mmap.ACCESS_READ) socket.curve_serverkey = mm.readline() mm.close() socket.connect("tcp://localhost:50000") if __name__ == "__main__": for i in range(5): socket.send(b"Hello") message = socket.recv() print("Received reply: %s" % message) #ここから下はdisconnectした時の挙動確認用なので通常不要。 socket.disconnect("tcp://localhost:50000") time.sleep(0.1) socket.connect("tcp://localhost:50000") for i in range(5): socket.send(b"Hello-2") message = socket.recv() print("Received reply2: %s" % message)
close()動作せず
- 何故かsocket.close()してもcontext.term()してもsocketは消えませんでした。 なので上のコードではdisconnectして再度connectしています。
- socket.close()にはlingerがあり、何も指定しないと無限に待つ可能性があるらしいので、100msや1msにしてみましたが結果は同じでcloseしませんでした。
socket.close(linger=100) context.term() if socket: print("Still exists") else: print("Null")
通信方法
- REQ-REP
- 毎回一方からMessage付きでRequestを送り、Requestを受け取った方が、Messageを受け取り、Messageを付けてReplyする。
- socket一つでデータのやり取りができる。
- <<今回は1つのデバイス内のPython間のデータ交換なので、REQ-REPを使っています。>>
- PUB-SUB
- 一方がMessageをトピック付きでPublishして相手のキューに溜まる。
- Publishを受けた側がトピックごとに受け取り処理が出来る。
- 1Serverー多Clientの場合に使用
- 双方向にデータをやり取りするには、異なるポート番号のsocket2個を作る必要がある。
- PUSH-PULL
- 複数のプロセス間でタスクを分散処理する場合に使う
- PUSHでタスクを送信し、PULLでタスクを受信して処理する
- RADIO-DISH(まだ策定中)
- N:N通信に使用
- RADIOをブロードキャスト送信してDISHが受信
- 詳細は理解できていないので、今後必要となったら書き足します。
最大転送量
MQTTはPayloadに最大256Mバイト入れる事ができますが、ZeroMQはどのくらいか分からなかったので色々調べてみました。 どうもLimitを設定してLimitオーバーしたらDisconnectする事は出来るみたいですが、初期値はリミットは無いようでした。
- Small Size MessageはLarger Messageと違い方法で転送される。
- Small Size Messageは30バイト
- Larger Messageは定義なし。
- ZeroMQ Version3で MaxMsgSize が -1 でNo Limitになる。 2024年4月現在、ZeroMQの最新バージョンは4.3.5 (2023年10月リリースのStable版)なので、No Limitは使えるはず。
- ZMQ_MAXMSGSIZEは、int64型なので、18Eバイトになる? なので実際はRAMメモリによる?
- ファイル転送で大きなファイルは送れないので、分割する必要がある。
- 転送データが大きいときはMultipartで転送した方がメモリ消費を少なく出来る。(ZeroMQ.org)
max_vsm_size
max_vsm_size = 29 Virtual Synchronized Machines(VSM)の最大サイズでSmall Size Messageのサイズでもない。 デフォルトは1Mバイト。
実験
30バイトと言っているサイトもあったので482バイト送って試してみましたが、ちゃんと受信できました。 なのでやはりNo Limitと思います。
Received reply: {‘PLC_PRG.VarTimeCur0’: {‘val’: 20000, ‘type’: ‘Int64’, ‘timestamp’: ‘2024-04-18T02:13:19.421000’}, ‘PLC_PRG.VarTimeCur1’: {‘val’: 280, ‘type’: ‘Int64’, ‘timestamp’: ‘2024-04-18T02:13:19.421000’}, ‘GVL.MyVariable’: {‘val’: 6789, ‘type’: ‘Int16’, ‘timestamp’: ‘2024-04-18T02:13:19.421000’}, ‘GVL.MyString’: {‘val’: ‘HELLO ICHIRI’, ‘type’: ‘String’, ‘timestamp’: ‘2024-04-18T02:13:19.421000’}, ‘GV
その他
HTTP
ZeroMQ over HTTPをしようとしたら、ZMQ_STREAMにして自分でHTTPの内容をParseして、返信のHeaderを作って送らないといけないみたい。
send_multipart()
こんな風に使うんだな。
socket.send_multipart([ request_id, b'HTTP/1.1 400 (Bad Request)\r\n\r\n', request_id, b'', ])
コメント