MQTT 协议 Client ID 长度不能超过23个字符(MqttException: MQIsdp ClientId > 23 bytes)

今天遇到一个MQTT的问题,MqttException: MQIsdp ClientId > 23 bytes

ClientId的长度大于23时,无法链接MQTT服务器。

经过查看协议发现:客户端标识符(Client ID)是介于123个字符长度,客户端到服务器的唯一标识。它必须在搜有客户端连接到一台服务器是唯一的,是在处理QoS级别12的消息ID中的关键。如果客户端ID包含23个字符,服务器响应CONNECT消息,通过一个CONNACK,返回码2:标识符被拒绝。

英文原版的协议中是这样描述的:


E文不行,好像是说开始的版本有23个字符的限制,后来的版本可以超过23字符了。

不过最新的Mosquitto只支持到了3.1.1,无奈只能改ClientId的规则,不超过23字符。




 

<think>我们使用TIdHTTPWebsocketClient和TIdIOHandlerWebsocket来连接ActiveMQ的MQTToverWebSocket。注意:ActiveMQ默认的WebSocket端口是61614或61623,且路径为/ws(或/mqtt,具体看配置)。步骤:1.创建TIdHTTPWebsocketClient实例,并设置IOHandler为TIdIOHandlerWebsocket(用于WebSocket通信)。2.配置主机、端口和WebSocket的URL(例如:ws://activemq-host:61614/ws)。3.建立WebSocket连接(调用Connect方法)。4.构建MQTTCONNECT报文(按照MQTT协议格式),并通过WebSocket发送。5.接收CONNACK响应,并验证是否连接成功。下面是一个详细的示例代码,包括连接、发送CONNECT帧和处理CONNACK响应。注意:MQTT协议细节,特别是报文格式,请参考MQTT3.1.1规范。我们将构建一个CONNECT报文,包含以下部分:固定报头:0x10(CONNECT)+剩余长度(可变)可变报头:协议名("MQTT")、协议级别(4表示3.1.1)、连接标志(用户名、密码等)、KeepAlive有效载荷:客户端ID、用户名、密码(如果有)由于TIdHTTPWebsocketClient在建立WebSocket连接后,使用TIdIOHandlerWebsocket进行数据传输,我们可以直接使用IOHandler.Write来发送二进制数据。代码示例:</think>### Delphi XE11中使用TIdHTTPWebsocketClient连接ActiveMQ MQTT的完整代码 以下是使用`TIdHTTPWebsocketClient`和`TIdIOHandlerWebsocket`连接ActiveMQ MQTT服务的详细实现代码: ```delphi unit ActiveMQConnector; interface uses System.SysUtils, System.Classes, IdHTTPWebsocketClient, IdIOHandlerWebsocket, IdSSLOpenSSL, IdGlobal; type TActiveMQConnector = class private FWSClient: TIdHTTPWebsocketClient; FHost: string; FPort: Integer; FClientID: string; FUsername: string; FPassword: string; FConnected: Boolean; function BuildMQTTConnectFrame: TIdBytes; function BuildUTF8Payload(const AStr: string): TIdBytes; function ParseConnAckFrame(Response: TIdBytes): Boolean; public constructor Create(const Host: string; Port: Integer; const ClientID, Username, Password: string); destructor Destroy; override; function Connect: Boolean; procedure Disconnect; procedure Subscribe(const Topic: string; QoS: Byte); procedure Publish(const Topic, Message: string; QoS: Byte); property IsConnected: Boolean read FConnected; end; implementation constructor TActiveMQConnector.Create(const Host: string; Port: Integer; const ClientID, Username, Password: string); begin FHost := Host; FPort := Port; FClientID := ClientID; FUsername := Username; FPassword := Password; FConnected := False; FWSClient := TIdHTTPWebsocketClient.Create(nil); FWSClient.IOHandler := TIdIOHandlerWebsocket.Create(nil); end; destructor TActiveMQConnector.Destroy; begin if FConnected then Disconnect; FreeAndNil(FWSClient); inherited; end; function TActiveMQConnector.Connect: Boolean; var ConnectFrame, Response: TIdBytes; begin Result := False; try // 配置WebSocket连接 FWSClient.Host := FHost; FWSClient.Port := FPort; // 建立WebSocket连接 FWSClient.Connect('ws://' + FHost + ':' + IntToStr(FPort) + '/ws'); // 构建并发送MQTT CONNECT帧 ConnectFrame := BuildMQTTConnectFrame; FWSClient.IOHandler.Write(ConnectFrame); // 读取响应(CONNACK) FWSClient.IOHandler.ReadBytes(Response, -1, True); // 解析CONNACK响应 FConnected := ParseConnAckFrame(Response); Result := FConnected; if not FConnected then raise Exception.Create('MQTT连接失败'); except on E: Exception do begin FConnected := False; raise Exception.Create('连接错误: ' + E.Message); end; end; end; procedure TActiveMQConnector.Disconnect; begin if not FConnected then Exit; // 发送DISCONNECT帧 (固定报头: E0 00) FWSClient.IOHandler.Write(ToBytes([$E0, $00])); FWSClient.Disconnect; FConnected := False; end; function TActiveMQConnector.BuildMQTTConnectFrame: TIdBytes; var ProtocolName: string; VarHeader, Payload: TIdBytes; FixedHeader: array[0..1] of Byte; RemainingLength: Integer; begin ProtocolName := 'MQTT'; // MQTT 3.1.1协议名 // 构建可变报头 SetLength(VarHeader, 0); AppendBytes(VarHeader, ToBytes(UInt16(Length(ProtocolName)))); // 协议长度 AppendBytes(VarHeader, IndyTextEncoding_UTF8.GetBytes(ProtocolName)); // 协议名 AppendByte(VarHeader, $04); // 协议级别: MQTT 3.1.1 AppendByte(VarHeader, $C2); // 连接标志: 用户名+密码+清除会话 (11000010) AppendBytes(VarHeader, ToBytes(UInt16(60))); // KeepAlive = 60秒 // 构建有效载荷 SetLength(Payload, 0); AppendBytes(Payload, BuildUTF8Payload(FClientID)); // 客户端ID AppendBytes(Payload, BuildUTF8Payload(FUsername)); // 用户名 AppendBytes(Payload, BuildUTF8Payload(FPassword)); // 密码 // 计算剩余长度 (可变报头长度 + 有效载荷长度) RemainingLength := Length(VarHeader) + Length(Payload); // 构建固定报头 FixedHeader[0] := $10; // CONNECT报文类型 (0001 0000) FixedHeader[1] := RemainingLength; // 剩余长度 // 组合完整帧 Result := ToBytes(FixedHeader) + VarHeader + Payload; end; function TActiveMQConnector.BuildUTF8Payload(const AStr: string): TIdBytes; begin // MQTT字符串格式: [2字节长度][UTF8字符串] Result := ToBytes(UInt16(Length(AStr))) + IndyTextEncoding_UTF8.GetBytes(AStr); end; function TActiveMQConnector.ParseConnAckFrame(Response: TIdBytes): Boolean; begin // CONNACK帧结构: [20][02][00][返回码] // 20 = CONNACK报文类型 (0010 0000) // 02 = 剩余长度 // 00 = 连接确认标志 // 00 = 返回码 (0=成功) if (Length(Response) >= 4) and (Response[0] = $20) and (Response[1] = $02) and (Response[3] = $00) then Result := True else Result := False; end; procedure TActiveMQConnector.Subscribe(const Topic: string; QoS: Byte); var Frame: TIdBytes; TopicLen: UInt16; begin if not FConnected then Exit; // 构建SUBSCRIBE帧 SetLength(Frame, 0); // 固定报头: SUBSCRIBE(0x82) + 剩余长度(占位) AppendByte(Frame, $82); AppendByte(Frame, 0); // 长度占位符 // 可变报头: 报文标识符(2字节) AppendBytes(Frame, ToBytes(UInt16(1))); // 使用固定标识符1 // 有效载荷: [主题长度][主题][QoS] TopicLen := Length(Topic); AppendBytes(Frame, ToBytes(TopicLen)); AppendBytes(Frame, IndyTextEncoding_UTF8.GetBytes(Topic)); AppendByte(Frame, QoS); // 更新剩余长度 Frame[1] := Length(Frame) - 2; FWSClient.IOHandler.Write(Frame); end; procedure TActiveMQConnector.Publish(const Topic, Message: string; QoS: Byte); var Frame: TIdBytes; TopicLen: UInt16; MsgBytes: TIdBytes; begin if not FConnected then Exit; // 构建PUBLISH帧 SetLength(Frame, 0); // 固定报头: PUBLISH(0x30) + 剩余长度(占位) AppendByte(Frame, $30 or (QoS shl 1)); // QoS级别 AppendByte(Frame, 0); // 长度占位符 // 可变报头: 主题长度 + 主题 TopicLen := Length(Topic); AppendBytes(Frame, ToBytes(TopicLen)); AppendBytes(Frame, IndyTextEncoding_UTF8.GetBytes(Topic)); // 报文标识符(QoS > 0时) if QoS > 0 then AppendBytes(Frame, ToBytes(UInt16(1))); // 使用固定标识符1 // 有效载荷: 消息内容 MsgBytes := IndyTextEncoding_UTF8.GetBytes(Message); AppendBytes(Frame, MsgBytes); // 更新剩余长度 Frame[1] := Length(Frame) - 2; FWSClient.IOHandler.Write(Frame); end; end. ``` ### 使用示例 ```delphi procedure TestActiveMQConnection; var MQTTConnector: TActiveMQConnector; begin MQTTConnector := TActiveMQConnector.Create( 'activemq.example.com', // ActiveMQ服务器地址 61614, // ActiveMQ WebSocket端口 'delphi-client-001', // 客户端ID 'admin', // 用户名 'password' // 密码 ); try if MQTTConnector.Connect then begin ShowMessage('成功连接到ActiveMQ'); // 订阅主题 MQTTConnector.Subscribe('delphi/test', 0); // 发布消息 MQTTConnector.Publish('delphi/test', 'Hello from Delphi!', 0); end; finally MQTTConnector.Free; end; end; ``` ### 关键配置说明 1. **ActiveMQ配置**: - 确保ActiveMQ已启用MQTT over WebSocket支持 - 默认WebSocket端口为`61614`或`61623` - 连接路径通常为`/ws`或`/mqtt` 2. **MQTT帧结构**: ```plaintext CONNECT帧结构: [10][长度][00 04 4D 51 54 54][04][C2][00 3C][客户端ID][用户名][密码] └─固定报头┘└───协议(MQTT)─┘└版本┘└标志┘└KeepAlive┘└──有效载荷──┘ ``` 3. **SSL/TLS支持**: ```delphi // 启用SSL FWSClient.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil); TIdSSLIOHandlerSocketOpenSSL(FWSClient.IOHandler).SSLOptions.Method := sslvTLSv1_2; FWSClient.Connect('wss://activemq.example.com:61614/ws'); // 注意wss协议 ``` 4. **错误处理**: - CONNACK返回码非0表示连接失败 - 常见错误码: - $01: 协议版本不支持 - $04: 无效的用户名或密码 - $05: 未授权 ### 性能优化建议 1. **心跳机制**: ```delphi // 定时发送PINGREQ TThread.CreateAnonymousThread(procedure begin while FConnected do begin Sleep(30000); // 30秒 FWSClient.IOHandler.Write(ToBytes([$C0, $00])); // PINGREQ end; end).Start; ``` 2. **异步接收**: ```delphi // 在独立线程中处理接收的消息 TThread.CreateAnonymousThread(procedure var Data: TIdBytes; begin while FConnected do begin FWSClient.IOHandler.ReadBytes(Data, -1, False); // 处理MQTT帧... end; end).Start; ``` 3. **连接池管理**: - 对于高频应用,实现连接池重用连接 - 设置合理的KeepAlive值(通常30-60秒)
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值