mqttxx 2.0.3__py3-none-any.whl → 3.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,931 @@
1
+ Metadata-Version: 2.1
2
+ Name: mqttxx
3
+ Version: 3.2.1
4
+ Summary: 基于 aiomqtt 的高级 MQTT 客户端和 RPC 框架
5
+ Author: MQTTX Team
6
+ License: MIT
7
+ Keywords: mqtt,rpc,async,iot,messaging
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: aiomqtt <3.0.0,>=2.0.0
11
+ Requires-Dist: loguru >=0.7.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest ; extra == 'dev'
14
+ Requires-Dist: pytest-asyncio ; extra == 'dev'
15
+ Requires-Dist: pytest-rich ; extra == 'dev'
16
+ Requires-Dist: ruff ; extra == 'dev'
17
+ Requires-Dist: build ; extra == 'dev'
18
+ Requires-Dist: twine ; extra == 'dev'
19
+
20
+ # MQTTX
21
+
22
+ [![PyPI version](https://img.shields.io/badge/version-3.2.0-blue.svg)](https://pypi.org/project/mqttxx/)
23
+ [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
24
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
25
+
26
+ 基于 [aiomqtt](https://github.com/sbtinstruments/aiomqtt) 的高级 MQTT 客户端和 RPC 框架。
27
+
28
+ ## 核心特性
29
+
30
+ - ✅ **纯 async/await** - 无回调,代码清晰
31
+ - ✅ **高性能消息处理** - Queue + Worker 模式,可控并发,背压机制
32
+ - ✅ **自动重连** - 订阅队列化,断线重连
33
+ - ✅ **双向对等 RPC** - 带权限控制、超时管理
34
+ - ✅ **约定式 RPC** - 零配置,自动订阅、自动注入 reply_to
35
+ - ✅ **Event Channel** - 高吞吐事件广播,支持通配符订阅
36
+ - ✅ **TLS/SSL 支持** - 安全连接、双向认证
37
+ - ✅ **完善异常系统** - 统一错误码、清晰的异常层次
38
+ - ✅ **生产级可靠性** - 修复所有 P0 缺陷(任务泄漏、资源泄漏、并发竞态)
39
+
40
+ ---
41
+
42
+ ## 目录
43
+
44
+ - [架构原则](#架构原则)
45
+ - [安装](#安装)
46
+ - [快速开始](#快速开始)
47
+ - [MQTT 基础用法](#1-mqtt-基础用法)
48
+ - [约定式 RPC(推荐)](#2-约定式-rpc零配置)
49
+ - [Event Channel(事件广播)](#3-event-channel事件广播)
50
+ - [传统 RPC](#4-rpc-基础用法传统模式)
51
+ - [RPC 权限控制](#5-rpc-权限控制)
52
+ - [TLS/SSL 和认证](#6-tlsssl-和认证)
53
+ - [架构与实现](#架构与实现)
54
+ - [API 速查](#api-速查)
55
+ - [配置对象](#配置对象)
56
+ - [异常系统](#异常系统)
57
+ - [RPC 消息协议](#rpc-消息协议)
58
+ - [版本变更](#版本变更)
59
+ - [开发](#开发)
60
+ - [示例项目](#示例项目)
61
+ - [常见问题](#常见问题)
62
+ - [贡献](#贡献)
63
+
64
+ ---
65
+
66
+ ## 架构原则
67
+
68
+ MQTTX 遵循严格的分层架构,确保职责清晰、代码简洁、易于扩展:
69
+
70
+ ### 1. MQTTClient(传输层)
71
+ - **职责**:只负责 bytes 传输
72
+ - **约束**:
73
+ - 不导入 `json` 或 `protocol` 模块
74
+ - 不理解消息内容
75
+ - `publish()` 只接受 `bytes`
76
+ - `subscribe()` handler 接收 `bytes`
77
+
78
+ ### 2. Protocol(协议层)
79
+ - **职责**:定义消息格式和 JSON 编解码
80
+ - **核心**:
81
+ - `encode()` 方法:object → bytes(使用 JSON)
82
+ - `decode()` 方法:bytes → object(使用 JSON)
83
+ - 所有 JSON 操作只在这一层
84
+
85
+ ### 3. RPCManager/EventChannelManager(应用层)
86
+ - **职责**:业务逻辑处理
87
+ - **约束**:
88
+ - 内部永远使用类型化对象(RPCRequest/RPCResponse/EventMessage)
89
+ - 调用 `encode()`/`decode()` 处理编解码
90
+ - 不直接使用 `json.dumps()`/`json.loads()`
91
+
92
+ **设计理念**:固定使用 JSON 编解码,简化架构,降低复杂度。如需自定义协议,可在传输层直接使用 `MQTTClient.subscribe()` 处理 bytes。
93
+
94
+ ---
95
+
96
+ ## 安装
97
+
98
+ ```bash
99
+ pip install mqttxx
100
+ ```
101
+
102
+ **要求**:
103
+ - Python >= 3.10
104
+ - aiomqtt >= 2.0.0
105
+ - loguru >= 0.7.0
106
+
107
+ ---
108
+
109
+ ## 快速开始
110
+
111
+ ### 1. MQTT 基础用法
112
+
113
+ ```python
114
+ import asyncio
115
+ from mqttxx import MQTTClient, MQTTConfig
116
+
117
+ async def main():
118
+ config = MQTTConfig(
119
+ broker_host="localhost",
120
+ broker_port=1883,
121
+ client_id="device_123"
122
+ )
123
+
124
+ async with MQTTClient(config) as client:
125
+ # 订阅主题
126
+ def on_message(topic, message):
127
+ print(f"{topic}: {message}")
128
+
129
+ client.subscribe("sensors/#", on_message)
130
+
131
+ # 发布消息
132
+ await client.publish("sensors/temperature", "25.5", qos=1)
133
+
134
+ await asyncio.sleep(60)
135
+
136
+ asyncio.run(main())
137
+ ```
138
+
139
+ ---
140
+
141
+ ### 2. 约定式 RPC(零配置)
142
+
143
+ **推荐**:使用 `RPCManager` 的 `my_topic` 参数,自动订阅 + 自动注入 reply_to。
144
+
145
+ ```python
146
+ from mqttxx import MQTTClient, MQTTConfig, RPCManager
147
+
148
+ # 边缘设备
149
+ async def edge_device():
150
+ client_id = "device_123"
151
+ config = MQTTConfig(
152
+ broker_host="localhost",
153
+ client_id=client_id,
154
+ )
155
+
156
+ async with MQTTClient(config) as client:
157
+ # 自动订阅 edge/device_123
158
+ rpc = RPCManager(client, my_topic=f"edge/{client_id}")
159
+
160
+ @rpc.register("get_status")
161
+ async def get_status(params):
162
+ return {"status": "online"}
163
+
164
+ # 调用云端(自动注入 reply_to="edge/device_123")
165
+ config = await rpc.call("cloud/config-service", "get_device_config")
166
+ print(config)
167
+
168
+ await asyncio.sleep(60)
169
+
170
+ # 云端服务
171
+ async def cloud_service():
172
+ client_id = "config-service"
173
+ config = MQTTConfig(
174
+ broker_host="localhost",
175
+ client_id=client_id,
176
+ )
177
+
178
+ async with MQTTClient(config) as client:
179
+ # 自动订阅 cloud/config-service
180
+ rpc = RPCManager(client, my_topic=f"cloud/{client_id}")
181
+
182
+ @rpc.register("get_device_config")
183
+ async def get_device_config(params):
184
+ return {"update_interval": 60, "servers": ["s1", "s2"]}
185
+
186
+ # 调用边缘设备(自动注入 reply_to="cloud/config-service")
187
+ status = await rpc.call("edge/device_123", "execute_command", params={"cmd": "restart"})
188
+ print(status)
189
+
190
+ await asyncio.sleep(60)
191
+
192
+ # 运行边缘设备或云端
193
+ asyncio.run(edge_device()) # 或 asyncio.run(cloud_service())
194
+ ```
195
+
196
+ **对比传统 RPC:**
197
+
198
+ | 场景 | 传统 RPC | 约定式 RPC |
199
+ |-----|---------|-----------|
200
+ | 初始化 | `rpc = RPCManager(client)`<br>`client.subscribe("edge/123", rpc.handle_rpc_message)` | `rpc = RPCManager(client, my_topic="edge/123")`<br>→ 自动订阅 |
201
+ | 调用 | `await rpc.call(topic="cloud/svc", method="get", reply_to="edge/123")` | `await rpc.call("cloud/svc", "get")` |
202
+ | 代码量 | 100% | **60%** ↓ |
203
+
204
+ ---
205
+
206
+ ### 3. Event Channel(事件广播)
207
+
208
+ **Event Channel 是高吞吐、低耦合、无返回值的事件广播通道**,适用于:
209
+ - 传感器数据流(温度、湿度、位置)
210
+ - 系统监控指标(CPU、内存、网络)
211
+ - 设备状态心跳
212
+ - 日志流
213
+
214
+ **关键特性**:
215
+ - ✅ **单向广播** - 发布即忘,无返回值(与 RPC 形成对比)
216
+ - ✅ **通配符订阅** - 支持 MQTT 通配符(`+` 单级,`#` 多级)
217
+ - ✅ **混合模式** - 支持结构化事件(EventMessage)和原始 dict
218
+ - ✅ **装饰器订阅** - 简洁的 API
219
+
220
+ ```python
221
+ from mqttxx import MQTTClient, MQTTConfig, EventChannelManager, EventMessage
222
+
223
+ async def main():
224
+ config = MQTTConfig(broker_host="localhost", client_id="device_001")
225
+
226
+ async with MQTTClient(config) as client:
227
+ events = EventChannelManager(client)
228
+
229
+ # 订阅事件(支持通配符)
230
+ @events.subscribe("sensors/+/temperature")
231
+ async def on_temperature(topic, message):
232
+ print(f"[温度] {topic}: {message}")
233
+
234
+ @events.subscribe("sensors/#")
235
+ async def on_all_sensors(topic, message):
236
+ print(f"[所有传感器] {topic}")
237
+
238
+ # 发布结构化事件
239
+ await events.publish(
240
+ "sensors/room1/temperature",
241
+ EventMessage(
242
+ event_type="temperature.changed",
243
+ data={"value": 25.5, "unit": "C"},
244
+ source="sensor_001"
245
+ )
246
+ )
247
+
248
+ # 发布原始消息(零开销)
249
+ await events.publish(
250
+ "sensors/room1/humidity",
251
+ {"value": 60.2, "unit": "%"}
252
+ )
253
+
254
+ await asyncio.sleep(60)
255
+
256
+ asyncio.run(main())
257
+ ```
258
+
259
+ **Event Channel vs RPC**:
260
+
261
+ | 特性 | Event Channel | RPC |
262
+ |------|--------------|-----|
263
+ | 模式 | 发布-订阅(Pub-Sub) | 请求-响应(Request-Response) |
264
+ | 通信 | 单向,一对多广播 | 双向,点对点 |
265
+ | 返回值 | ❌ 无返回值 | ✅ 等待返回结果 |
266
+ | 用途 | 高频事件流、监控数据 | 远程方法调用、配置查询 |
267
+ | 通配符 | ✅ 支持 `+/#` | ❌ 精确匹配 |
268
+
269
+ **混合使用 RPC + Event**:
270
+
271
+ ```python
272
+ # 同时使用 RPC 和 Event Channel
273
+ rpc = RPCManager(client, my_topic="device/device_001")
274
+ events = EventChannelManager(client)
275
+
276
+ # RPC: 获取配置(需要返回值)
277
+ config = await rpc.call("server/config", "get_device_config")
278
+
279
+ # Event: 发布心跳(无返回值)
280
+ await events.publish(
281
+ "device/device_001/heartbeat",
282
+ EventMessage(
283
+ event_type="heartbeat",
284
+ data={"cpu": 45.2, "memory": 78.5}
285
+ )
286
+ )
287
+ ```
288
+
289
+ ---
290
+
291
+ ### 4. RPC 基础用法(传统模式)
292
+
293
+ 需要手动订阅和传递 `reply_to`,适用于需要精细控制的场景。
294
+
295
+ ```python
296
+ from mqttxx import MQTTClient, MQTTConfig, RPCManager
297
+
298
+ async def main():
299
+ config = MQTTConfig(broker_host="localhost", client_id="device_001")
300
+
301
+ async with MQTTClient(config) as client:
302
+ rpc = RPCManager(client)
303
+
304
+ # 注册本地方法
305
+ @rpc.register("get_status")
306
+ async def get_status(params):
307
+ return {"status": "online", "cpu": 45.2}
308
+
309
+ # 订阅 RPC 主题
310
+ client.subscribe(
311
+ "server/device_001",
312
+ rpc.handle_rpc_message
313
+ )
314
+
315
+ # 调用远程方法
316
+ result = await rpc.call(
317
+ topic="bots/device_002",
318
+ method="get_data",
319
+ reply_to="server/device_001",
320
+ timeout=5
321
+ )
322
+ print(result) # {"data": [1, 2, 3]}
323
+
324
+ await asyncio.sleep(60)
325
+
326
+ asyncio.run(main())
327
+ ```
328
+
329
+ ---
330
+
331
+ ### 5. RPC 权限控制
332
+
333
+ ```python
334
+ from mqttxx import RPCManager, RPCRequest
335
+
336
+ async def auth_check(caller_id: str, method: str, request: RPCRequest) -> bool:
337
+ # 敏感方法只允许管理员
338
+ if method in ["delete_user", "reset_system"]:
339
+ return caller_id in ["admin_001", "admin_002"]
340
+ return True
341
+
342
+ rpc = RPCManager(client, auth_callback=auth_check)
343
+
344
+ @rpc.register("delete_user")
345
+ async def delete_user(params):
346
+ return {"result": "user deleted"}
347
+
348
+ # 未授权调用会返回 "Permission denied"
349
+ ```
350
+
351
+ ---
352
+
353
+ ### 6. TLS/SSL 和认证
354
+
355
+ ```python
356
+ from mqttxx import MQTTConfig, TLSConfig, AuthConfig
357
+ from pathlib import Path
358
+
359
+ config = MQTTConfig(
360
+ broker_host="secure.mqtt.example.com",
361
+ broker_port=8883,
362
+ tls=TLSConfig(
363
+ enabled=True,
364
+ ca_certs=Path("ca.crt"),
365
+ certfile=Path("client.crt"),
366
+ keyfile=Path("client.key"),
367
+ ),
368
+ auth=AuthConfig(
369
+ username="mqtt_user",
370
+ password="mqtt_password",
371
+ ),
372
+ )
373
+
374
+ async with MQTTClient(config) as client:
375
+ await client.publish("secure/topic", "encrypted message")
376
+ ```
377
+
378
+ ---
379
+
380
+ ## 架构与实现
381
+
382
+ 深入了解 MQTTX 的技术实现细节:
383
+
384
+ - **[消息路由实现逻辑](docs/routing_flow.md)** - 使用 Mermaid 图表说明从 MQTT Broker 到最终处理器的完整消息流程、订阅注册机制、通配符匹配算法,以及关键代码位置
385
+ - **[约定式 RPC 用法详解](docs/约定式RPC用法.md)** - 深入解析零配置 RPC 调用的设计原理、自动订阅机制、reply_to 自动注入,以及与传统 RPC 的对比
386
+
387
+ **推荐阅读顺序**:先通过上方的"快速开始"掌握基本用法,再深入技术文档了解底层实现。
388
+
389
+ ---
390
+
391
+ ## API 速查
392
+
393
+ ### MQTTClient
394
+
395
+ ```python
396
+ class MQTTClient:
397
+ def __init__(self, config: MQTTConfig)
398
+ async def connect(self) -> None
399
+ async def disconnect(self) -> None
400
+ def subscribe(self, topic: str, handler: Callable) -> None
401
+ async def publish(self, topic: str, payload: str, qos: int = 0) -> None
402
+
403
+ @property
404
+ def is_connected(self) -> bool
405
+ ```
406
+
407
+ ---
408
+
409
+ ### RPCManager(传统 RPC)
410
+
411
+ ```python
412
+ class RPCManager:
413
+ def __init__(
414
+ self,
415
+ client: MQTTClient,
416
+ my_topic: Optional[str] = None,
417
+ config: RPCConfig = None,
418
+ auth_callback: AuthCallback = None
419
+ )
420
+
421
+ def register(self, method_name: str) # 装饰器
422
+ def unregister(self, method_name: str) -> None
423
+ def handle_rpc_message(self, topic: str, message: RPCRequest | RPCResponse) -> None
424
+
425
+ async def call(
426
+ self,
427
+ topic: str,
428
+ method: str,
429
+ params: Any = None,
430
+ reply_to: str = None, # 必填
431
+ timeout: float = None,
432
+ ) -> Any
433
+ ```
434
+
435
+ ---
436
+
437
+ ### RPCManager(约定式用法)
438
+
439
+ ```python
440
+ class RPCManager:
441
+ def __init__(
442
+ self,
443
+ client: MQTTClient,
444
+ my_topic: Optional[str] = None, # 本节点 topic(自动订阅,自动注入到 reply_to)
445
+ config: RPCConfig = None,
446
+ auth_callback: AuthCallback = None,
447
+ )
448
+
449
+ async def call(
450
+ self,
451
+ topic: str, # 对方的 topic
452
+ method: str,
453
+ params: Any = None,
454
+ timeout: float = None,
455
+ reply_to: Optional[str] = None, # 可选,默认使用 my_topic
456
+ ) -> Any
457
+
458
+ # 属性
459
+ my_topic: Optional[str] # 当前 topic(只读)
460
+ ```
461
+
462
+ **使用示例:**
463
+
464
+ ```python
465
+ # 边缘设备
466
+ rpc = RPCManager(client, my_topic="edge/device_123")
467
+ config = await rpc.call("cloud/config-service", "get_config")
468
+
469
+ # 云端服务
470
+ rpc = RPCManager(client, my_topic="cloud/config-service")
471
+ status = await rpc.call("edge/device_123", "execute_command")
472
+
473
+ # 微服务
474
+ rpc = RPCManager(client, my_topic="auth-service")
475
+ user = await rpc.call("user-service", "get_user", params={"id": 123})
476
+ ```
477
+
478
+ ---
479
+
480
+ ### EventChannelManager(Event Channel)
481
+
482
+ ```python
483
+ class EventChannelManager:
484
+ def __init__(self, client: MQTTClient)
485
+
486
+ def subscribe(
487
+ self,
488
+ pattern: str, # MQTT topic 模式(支持通配符 +/#)
489
+ handler: Optional[EventHandler] = None
490
+ ) -> Callable # 装饰器或直接注册
491
+
492
+ def unsubscribe(self, pattern: str, handler: EventHandler) -> None
493
+
494
+ async def publish(
495
+ self,
496
+ topic: str, # MQTT topic
497
+ message: EventMessage | dict | Any, # 消息(支持多种格式)
498
+ qos: int = 0
499
+ ) -> None # 无返回值(单向广播)
500
+ ```
501
+
502
+ **EventMessage(可选的结构化格式)**:
503
+
504
+ ```python
505
+ @dataclass
506
+ class EventMessage:
507
+ type: str = "event"
508
+ event_type: str # 事件类型(如 "temperature.changed")
509
+ data: Any # 事件数据
510
+ timestamp: float # 时间戳(自动生成)
511
+ source: str = "" # 事件源
512
+
513
+ def to_dict(self) -> dict
514
+ @staticmethod
515
+ def from_dict(data: dict) -> EventMessage
516
+ ```
517
+
518
+ **使用示例:**
519
+
520
+ ```python
521
+ events = EventChannelManager(client)
522
+
523
+ # 订阅(装饰器模式)
524
+ @events.subscribe("sensors/+/temperature")
525
+ async def on_temp(topic, message):
526
+ print(f"{topic}: {message}")
527
+
528
+ # 发布结构化事件
529
+ await events.publish(
530
+ "sensors/room1/temperature",
531
+ EventMessage(
532
+ event_type="temperature.changed",
533
+ data={"value": 25.5}
534
+ )
535
+ )
536
+
537
+ # 发布原始消息(零开销)
538
+ await events.publish("sensors/room1/humidity", {"value": 60.2})
539
+ ```
540
+
541
+ ---
542
+
543
+ ## 配置对象
544
+
545
+ ### MQTTConfig
546
+
547
+ ```python
548
+ @dataclass
549
+ class MQTTConfig:
550
+ broker_host: str
551
+ broker_port: int = 1883
552
+ client_id: str = "" # 空字符串 = 自动生成
553
+ keepalive: int = 60
554
+ clean_session: bool = False
555
+ tls: TLSConfig = field(default_factory=TLSConfig)
556
+ auth: AuthConfig = field(default_factory=AuthConfig)
557
+ reconnect: ReconnectConfig = field(default_factory=ReconnectConfig)
558
+ max_queued_messages: int = 0 # 0 = 无限
559
+ max_payload_size: int = 1024 * 1024 # 1MB
560
+
561
+ # 高性能消息处理(v3.0+)
562
+ message_queue_maxsize: int = 100_000 # 消息队列大小(保险丝,防止 OOM)
563
+ num_workers: Optional[int] = None # Worker 数量(None = CPU核数×2,适合 IO-bound)
564
+ ```
565
+
566
+ **高性能配置说明**:
567
+
568
+ - **message_queue_maxsize**:消息队列容量限制
569
+ - 默认 100,000("几乎无限",仅作保险丝)
570
+ - 队列满时阻塞等待(背压信号)
571
+ - 触发背压时:CPU/延迟升高 → 扩容信号
572
+ - Python 字面量分隔符:`100_000 = 100000`(下划线仅用于可读性)
573
+
574
+ - **num_workers**:消息处理 Worker 数量
575
+ - `None`(默认):CPU核数 × 2(适合 IO-bound 负载)
576
+ - 自定义值:根据 handler 类型调整
577
+ - CPU-bound handler:设为 CPU核数
578
+ - IO-bound handler:设为 CPU核数 × 2~4
579
+
580
+ ### TLSConfig
581
+
582
+ ```python
583
+ @dataclass
584
+ class TLSConfig:
585
+ enabled: bool = False
586
+ ca_certs: Optional[Path] = None
587
+ certfile: Optional[Path] = None
588
+ keyfile: Optional[Path] = None
589
+ verify_mode: str = "CERT_REQUIRED" # CERT_REQUIRED | CERT_OPTIONAL | CERT_NONE
590
+ check_hostname: bool = True
591
+ ```
592
+
593
+ ### AuthConfig
594
+
595
+ ```python
596
+ @dataclass
597
+ class AuthConfig:
598
+ username: Optional[str] = None
599
+ password: Optional[str] = None
600
+ ```
601
+
602
+ ### ReconnectConfig
603
+
604
+ ```python
605
+ @dataclass
606
+ class ReconnectConfig:
607
+ enabled: bool = True
608
+ interval: int = 5 # 初始重连间隔(秒)
609
+ max_attempts: int = 0 # 0 = 无限重试
610
+ backoff_multiplier: float = 1.5 # 指数退避倍数
611
+ max_interval: int = 60 # 最大重连间隔(秒)
612
+ ```
613
+
614
+ ### RPCConfig
615
+
616
+ ```python
617
+ @dataclass
618
+ class RPCConfig:
619
+ default_timeout: float = 30.0 # 默认超时时间(秒)
620
+ max_concurrent_calls: int = 100 # 最大并发调用数
621
+ ```
622
+
623
+ ---
624
+
625
+ ## 异常系统
626
+
627
+ ```python
628
+ # 基础异常
629
+ class MQTTXError(Exception)
630
+ class ConnectionError(MQTTXError)
631
+ class MessageError(MQTTXError)
632
+ class RPCError(MQTTXError)
633
+
634
+ # RPC 异常
635
+ class RPCTimeoutError(RPCError) # RPC 调用超时
636
+ class RPCRemoteError(RPCError) # 远程方法执行失败
637
+ class RPCMethodNotFoundError(RPCError) # 方法未找到
638
+ class PermissionDeniedError(RPCError) # 权限拒绝
639
+ class TooManyConcurrentCallsError(RPCError) # 并发调用超限
640
+
641
+ # 错误码
642
+ class ErrorCode(IntEnum):
643
+ NOT_CONNECTED = 1001
644
+ RPC_TIMEOUT = 3002
645
+ PERMISSION_DENIED = 4001
646
+ # ... 更多错误码见源码
647
+ ```
648
+
649
+ **使用示例:**
650
+
651
+ ```python
652
+ from mqttxx import RPCTimeoutError, RPCRemoteError
653
+
654
+ try:
655
+ result = await rpc.call_bot("456", "get_data", timeout=5)
656
+ except RPCTimeoutError:
657
+ print("调用超时")
658
+ except RPCRemoteError as e:
659
+ print(f"远程方法执行失败: {e}")
660
+ ```
661
+
662
+ ---
663
+
664
+ ## RPC 消息协议
665
+
666
+ ### 请求
667
+
668
+ ```json
669
+ {
670
+ "type": "rpc_request",
671
+ "request_id": "uuid-string",
672
+ "method": "get_status",
673
+ "params": {"id": 123},
674
+ "reply_to": "server/device_001",
675
+ "caller_id": "device_002"
676
+ }
677
+ ```
678
+
679
+ ### 响应(成功)
680
+
681
+ ```json
682
+ {
683
+ "type": "rpc_response",
684
+ "request_id": "uuid-string",
685
+ "result": {"status": "online"}
686
+ }
687
+ ```
688
+
689
+ ### 响应(错误)
690
+
691
+ ```json
692
+ {
693
+ "type": "rpc_response",
694
+ "request_id": "uuid-string",
695
+ "error": "Permission denied"
696
+ }
697
+ ```
698
+
699
+ ---
700
+
701
+ ## 版本变更
702
+
703
+ ### v3.2.0(当前版本)
704
+
705
+ **架构简化**:
706
+ - ✅ **移除可插拔编解码器** - 固定使用 JSON 编解码,简化架构设计
707
+ - ✅ **整合约定式 RPC** - `my_topic` 参数集成到 `RPCManager`,移除独立的 `ConventionalRPCManager`
708
+ - ✅ **整合传输层 API** - `RawAPI` 功能整合到 `MQTTClient`,统一接口
709
+
710
+ **迁移指南(从 v3.1.x)**:
711
+ 1. **移除 Codec 相关代码** - 不再支持自定义编解码器(如 MessagePack),统一使用 JSON
712
+ ```python
713
+ # ❌ 旧代码(不再支持)
714
+ rpc = RPCManager(client, codec=MessagePackCodec)
715
+
716
+ # ✅ 新代码(固定 JSON)
717
+ rpc = RPCManager(client)
718
+ ```
719
+ 2. **使用约定式 RPC** - 推荐使用 `my_topic` 参数简化代码
720
+ ```python
721
+ # ✅ 推荐写法
722
+ rpc = RPCManager(client, my_topic="edge/device_123")
723
+ result = await rpc.call("cloud/server", "get_config") # 自动注入 reply_to
724
+ ```
725
+
726
+ ---
727
+
728
+ ### v2.0.0 重大变更
729
+
730
+ 从 v2.0.0 开始,完全重写为基于 aiomqtt(纯 async/await),**不兼容** v0.x.x(gmqtt)。
731
+
732
+ **主要变化:**
733
+ - ✅ aiomqtt 替代 gmqtt
734
+ - ✅ 原生 dataclass 替代 python-box(性能提升 6 倍)
735
+ - ✅ **修复所有 P0 缺陷**(详见下方)
736
+ - ✅ 新增约定式 RPC(`RPCManager` 的 `my_topic` 参数)
737
+ - ✅ 新增权限控制(`auth_callback`)
738
+ - ✅ 新增 TLS/SSL 支持
739
+
740
+ **P0 缺陷修复(v3.0)**:
741
+ 1. **任务泄漏 + 消息处理模型** - Queue + Worker 模式,可控并发
742
+ 2. **取消订阅功能缺失** - 实现完整的取消订阅功能
743
+ 3. **重连竞态条件** - 快照模式避免并发修改
744
+ 4. **连接资源泄漏** - 显式关闭连接
745
+
746
+ **并发模型说明**:
747
+ - ✅ **单 Event Loop 设计**:所有方法(subscribe/unsubscribe/handler)必须在同一 asyncio loop 中调用
748
+ - ✅ **Handler 顺序执行**:同一消息的多个 handlers 按注册顺序顺序调用(非并发)
749
+ - ❌ **不支持多线程/多 loop 并发**:如需多线程,请为每个线程创建独立的 MQTTClient 实例
750
+
751
+ **迁移关键点:**
752
+ 1. 使用 `MQTTConfig` 配置对象
753
+ 2. 使用 `async with` 上下文管理器
754
+ 3. `publish_message()` → `publish()`
755
+ 4. 移除 `EventEmitter`(改用 dict)
756
+ 5. (可选)配置 `message_queue_maxsize` 和 `num_workers` 以优化性能
757
+
758
+ ---
759
+
760
+ ## 开发
761
+
762
+ ```bash
763
+ # 克隆项目
764
+ git clone <repository-url>
765
+ cd mqttx
766
+
767
+ # 安装开发依赖
768
+ pip install -e ".[dev]"
769
+
770
+ # 运行测试
771
+ pytest tests/ -v
772
+
773
+ # 代码检查和格式化
774
+ make lint # 代码检查
775
+ make format # 代码格式化
776
+
777
+ # 构建和发布
778
+ make build # 构建分发包
779
+ make version # 查看当前版本
780
+ ```
781
+
782
+ **测试覆盖**:
783
+ - 单元测试 (`test_*`)
784
+ - 集成测试 (`test_integration`)
785
+ - P0 缺陷修复验证 (`test_p0_fixes`)
786
+ - 性能测试 (`test_performance`)
787
+
788
+ ---
789
+
790
+ ## 示例项目
791
+
792
+ 查看 [examples/](examples/) 目录获取完整示例:
793
+
794
+ - **conventional_rpc_generic.py** - 约定式 RPC 完整示例
795
+ - 边缘设备与云端服务通信
796
+ - 微服务之间的 RPC 调用
797
+ - 展示自动订阅和自动注入 reply_to
798
+
799
+ - **event_channel_basic.py** - Event Channel 基础示例
800
+ - 订阅事件(支持通配符)
801
+ - 发布结构化事件和原始消息
802
+ - 一个 topic 多个订阅者
803
+
804
+ - **rpc_event_mixed.py** - RPC 和 Event Channel 混合使用
805
+ - 同一客户端同时使用 RPC 和 Event Channel
806
+ - RPC 调用(请求-响应)
807
+ - Event 广播(单向,无返回值)
808
+
809
+ 运行示例:
810
+ ```bash
811
+ # RPC 示例
812
+ # 终端 1: 运行边缘设备
813
+ python examples/conventional_rpc_generic.py edge
814
+
815
+ # 终端 2: 运行云端服务
816
+ python examples/conventional_rpc_generic.py cloud
817
+
818
+ # Event Channel 示例
819
+ python examples/event_channel_basic.py
820
+
821
+ # RPC + Event 混合使用
822
+ # 终端 1: 运行设备端
823
+ python examples/rpc_event_mixed.py device
824
+
825
+ # 终端 2: 运行服务器端
826
+ python examples/rpc_event_mixed.py server
827
+ ```
828
+
829
+ ---
830
+
831
+ ## 常见问题
832
+
833
+ ### Q: 如何处理断线重连?
834
+ A: MQTTClient 默认启用自动重连,配置参数在 `ReconnectConfig` 中:
835
+ ```python
836
+ config = MQTTConfig(
837
+ broker_host="localhost",
838
+ reconnect=ReconnectConfig(
839
+ enabled=True,
840
+ interval=5, # 初始重连间隔
841
+ max_attempts=0, # 0 = 无限重试
842
+ backoff_multiplier=1.5,
843
+ max_interval=60
844
+ )
845
+ )
846
+ ```
847
+
848
+ ### Q: RPC 调用超时了怎么办?
849
+ A: 捕获 `RPCTimeoutError` 异常并处理:
850
+ ```python
851
+ from mqttxx import RPCTimeoutError
852
+
853
+ try:
854
+ result = await rpc.call("topic", "method", timeout=5)
855
+ except RPCTimeoutError:
856
+ print("RPC 调用超时,请检查远程服务是否在线")
857
+ ```
858
+
859
+ ### Q: 如何实现双向认证(mTLS)?
860
+ A: 配置 TLS 证书和密钥:
861
+ ```python
862
+ config = MQTTConfig(
863
+ broker_host="secure.mqtt.example.com",
864
+ broker_port=8883,
865
+ tls=TLSConfig(
866
+ enabled=True,
867
+ ca_certs=Path("ca.crt"), # CA 证书
868
+ certfile=Path("client.crt"), # 客户端证书
869
+ keyfile=Path("client.key"), # 客户端私钥
870
+ )
871
+ )
872
+ ```
873
+
874
+ ### Q: 约定式 RPC 和传统 RPC 有什么区别?
875
+ A:
876
+ - **约定式 RPC**: 自动订阅 `my_topic`,自动注入 `reply_to`,代码量减少 40%
877
+ - **传统 RPC**: 需要手动订阅、手动传递 `reply_to`,适合需要精细控制的场景
878
+
879
+ 推荐大多数场景使用约定式 RPC。
880
+
881
+ ### Q: 如何优化高吞吐场景的性能?
882
+ A: 根据实际负载调整消息处理配置:
883
+ ```python
884
+ import os
885
+
886
+ config = MQTTConfig(
887
+ broker_host="localhost",
888
+ # 队列容量(默认 100k 适合大多数场景)
889
+ message_queue_maxsize=100_000,
890
+
891
+ # Worker 数量(根据 handler 类型调整)
892
+ num_workers=os.cpu_count() * 2, # IO-bound(默认)
893
+ # num_workers=os.cpu_count(), # CPU-bound
894
+ )
895
+ ```
896
+
897
+ **性能监控**:
898
+ - 监控队列深度:如果队列长期接近满载,考虑增加 workers
899
+ - 监控 CPU/延迟:队列满时会触发背压,CPU/延迟会升高
900
+ - Handler 并发:默认顺序执行,如需并发请在 handler 内使用 `asyncio.create_task()`
901
+
902
+ ### Q: Handlers 是并发执行的吗?
903
+ A: 不是。同一消息的多个 handlers 按注册顺序**顺序执行**(非并发):
904
+ ```python
905
+ # 顺序处理(默认)
906
+ async def handler1(topic, payload):
907
+ await process_data(payload) # 阻塞后续 handlers
908
+
909
+ # 并发处理(手动)
910
+ async def handler2(topic, payload):
911
+ asyncio.create_task(process_async(payload)) # 不阻塞
912
+ ```
913
+
914
+ 这是有意的设计,确保消息处理的可预测性。如需并发,请在 handler 内部创建 task。
915
+
916
+ ---
917
+
918
+ ## 贡献
919
+
920
+ 欢迎提交 Issue 和 Pull Request!
921
+
922
+ 在提交 PR 之前,请确保:
923
+ 1. 通过所有测试 (`pytest tests/ -v`)
924
+ 2. 代码通过检查 (`make lint`)
925
+ 3. 代码已格式化 (`make format`)
926
+
927
+ ---
928
+
929
+ ## License
930
+
931
+ MIT