MQTT3.1.1delphi实现(xe7)跨平台

需要做一个APP控制设备的程序,思来想去放弃自己实现服务端,准备直接采用现成的MQTT服务端程序,自己只需要关心逻辑,传输的交个MQTT .网上能找到的delphi的版本是老外的基于一个三方网络库的,win32下面可以编译运行,需要修改部分AnsiString和WideString。测试的时候会掉线,此条可能是因为当时没细读协议,规定时间未发送心跳包,被服务端断开。改成 Android 死活没能编译通过,技术问题,遂放弃。准备自己读协议,重新用TIdTCPClient实现一遍,这样可以方便的跨平台,也无三方控件,同时深入理解一下mqtt.代码基本参考老外的版本。先弄个能用的测试代码出来。服务端使用mosquitto.客户端现已完成MQTT连接,MQTT心跳包。
unit uMain;  
  
interface  
  
uses  
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,IdGlobal,  
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, FMX.Layouts, FMX.Memo, FMX.Controls.Presentation, FMX.Edit;  
const  
FClientID='tndsoft';  //客户端ID,需要唯一,最终由设备ID代替  
  
type  
  //  Message type. 4 Bit unsigned.  
  TMQTTMessageType = (  
          Reserved0,    //0 Reserved  
          CONNECT, //   1   Client request to connect to Broker  
          CONNACK, //   2   Connect Acknowledgment  
          PUBLISH, //   3   Publish message  
          PUBACK, //    4   Publish Acknowledgment  
          PUBREC, //    5   Publish Received (assured delivery part 1)  
          PUBREL, //    6   Publish Release (assured delivery part 2)  
          PUBCOMP, //   7   Publish Complete (assured delivery part 3)  
          SUBSCRIBE, // 8   Client Subscribe request  
          SUBACK, //    9   Subscribe Acknowledgment  
          UNSUBSCRIBE, // 10    Client Unsubscribe request  
          UNSUBACK, // 11   Unsubscribe Acknowledgment  
          PINGREQ, //   12  PING Request  
          PINGRESP, //  13  PING Response  
          DISCONNECT, // 14 Client is Disconnecting  
          Reserved15 // 15  
        );  
  TRemainingLength = Array of Byte;   //剩余长度字段,可变,1到4字节  
  TUTF8Text = Array of Byte;  
type  
  TMQTTMessage = Record  
    FixedHeader: Byte;  
    RL: TBytes;  
    Data: TBytes;  
  End;  
  
{事件}  
TConnAckEvent = procedure (Sender: TObject; ReturnCode: integer) of object;  
  
type  
  TfMQTTForDelphi = class(TForm)  
    Memo1: TMemo;  
    IdTCPClient1: TIdTCPClient;  
    Button1: TButton;  
    Button2: TButton;  
    edtHost: TEdit;  
    edtPort: TEdit;  
    Button3: TButton;  
    Button4: TButton;  
    procedure IdTCPClient1Connected(Sender: TObject);  
    procedure IdTCPClient1Disconnected(Sender: TObject);  
    procedure Button1Click(Sender: TObject);  
    procedure Button2Click(Sender: TObject);  
    procedure Button3Click(Sender: TObject);  
    procedure Button4Click(Sender: TObject);  
  private  
    { Private declarations }  
  public  
    { Public declarations }  
  end;  
type  
  TClientHandleThread=class(TThread)  
  private  
    { Private declarations }  
    servercmd:integer;  
    serverMsg:string;  
    FCurrentData: TMQTTMessage;  
    FConnAckEvent: TConnAckEvent;//连接返回事件  
    procedure HandleInput;  
  
  protected  
    procedure Execute; override;  
  public  
    property OnConnAck : TConnAckEvent read FConnAckEvent write FConnAckEvent;  
  end;  
  
  
var  
  fMQTTForDelphi: TfMQTTForDelphi;  
  myClientHandleThread:TClientHandleThread;  
  function FixedHeader(MessageType: TMQTTMessageType; Dup, Qos,Retain: Word): Byte;  
  function VariableHeader_Connect(KeepAlive: Word): TBytes;  
  procedure CopyIntoArray(var DestArray: Array of Byte; SourceArray: Array of Byte; StartIndex: integer);  
  function StrToBytes(str: string; perpendLength: boolean): TUTF8Text;  
  procedure AppendArray(var Dest: TUTF8Text; Source: Array of Byte);  
  function RemainingLength(x: Integer): TRemainingLength;  
  function BuildCommand(FixedHeader: Byte; RemainL: TRemainingLength;  VariableHead: TBytes; Payload: Array of Byte): TBytes;  
  procedure AppendBytes(var DestArray: TBytes;const NewBytes: TBytes);  
  function RemainingLengthToInt(RLBytes: TBytes): Integer;  
implementation  
  
{$R *.fmx}  
{$R *.NmXhdpiPh.fmx ANDROID}  
{$R *.XLgXhdpiTb.fmx ANDROID}  
{$R *.SmXhdpiPh.fmx ANDROID}  
  
procedure TClientHandleThread.Execute;  
var  
  CurrentMessage:TMQTTMessage;  
  vBuffer:TIdBytes;  
  Buffer: TBytes;  
  RLInt: Integer;  
  I:Integer;  
begin  
  while not Terminated do  
  begin  
    if not fMQTTForDelphi.IdTCPClient1.Connected then  
      Terminate  
    else  
    try  
      CurrentMessage.FixedHeader := 0;  
      CurrentMessage.RL := nil;  
      CurrentMessage.Data := nil;  
      CurrentMessage.FixedHeader:=fMQTTForDelphi.IdTCPClient1.IOHandler.ReadByte;  //读取FixedHdader  
      {读取剩余长度-编码过}  
      SetLength(CurrentMessage.RL,1);  
      SetLength(Buffer,1);  
      CurrentMessage.RL[0]:=fMQTTForDelphi.IdTCPClient1.IOHandler.ReadByte;  //读取剩余长度第一位  
      for i := 1 to 3 do    //读取剩余长度其他位数,剩余长度为可变长度1-4.  
      begin  
        if (( CurrentMessage.RL[i - 1] and 128) <> 0) then  
        begin  
          Buffer[0] := fMQTTForDelphi.IdTCPClient1.IOHandler.ReadByte;  
          AppendBytes(CurrentMessage.RL, Buffer);  
        end  
        else  
        Break;  
      end;  
      {解码剩余长度}  
      RLInt := RemainingLengthToInt(CurrentMessage.RL);  
      {将剩余长度的数据全部读出}  
      if (RLInt > 0)  then  
      begin  
        SetLength(CurrentMessage.Data, RLInt);  
        fMQTTForDelphi.IdTCPClient1.IOHandler.ReadBytes(vBuffer,RLInt,True);  
        CurrentMessage.Data:=TBytes(vBuffer);  
//        FPSocket^.RecvBufferEx(Pointer(CurrentMessage.Data), RLInt, 1000);  
      end;  
      FCurrentData := CurrentMessage;  
      Synchronize(HandleInput);  
    except  
  
    end;  
  
    try  
      servercmd:=fMQTTForDelphi.IdTCPClient1.IOHandler.ReadWord;  
      Synchronize(HandleInput);  
    except  
  
    end;  
  end;  
end;  
  
procedure TClientHandleThread.HandleInput;  
var  
  MessageType: Byte;  
  DataLen: integer;  
  QoS: integer;  
  sErrCode:string;  
  Topic: string;  
  Payload: string;  
  ResponseVH: TBytes;  
  ConnectReturn: Integer;  
begin  
  if (FCurrentData.FixedHeader <> 0) then  
    begin  
      MessageType := FCurrentData.FixedHeader shr 4;  
  
      if (MessageType = Ord(CONNACK)) then  
        begin  
          // Check if we were given a Connect Return Code.  
          ConnectReturn := 0;  
          // Any return code except 0 is an Error  
          if ((Length(FCurrentData.Data) > 0) and (Length(FCurrentData.Data) < 4)) then  
            begin  
              ConnectReturn := FCurrentData.Data[1];  
              case ConnectReturn of  
                0:sErrCode:='连接已接受';  
                1:sErrCode:='连接已拒绝,不支持的协议版本';  
                2:sErrCode:='连接已拒绝,不合格的客户端标识符';  
                3:sErrCode:='连接已拒绝,服务端不可用';  
                4:sErrCode:='连接已拒绝,无效的用户名或密码';  
                5:sErrCode:='连接已拒绝,未授权';  
              end;  
              fMQTTForDelphi.Memo1.Lines.Add(sErrCode);  
            end;  
          //if Assigned(OnConnAck) then OnConnAck(Self, ConnectReturn);  
        end  
      else  
      if (MessageType = Ord(PUBLISH)) then  
        begin  
//          // Read the Length Bytes  
//          DataLen := BytesToStrLength(Copy(FCurrentData.Data, 0, 2));  
//          // Get the Topic  
//          SetString(Topic, PAnsiChar(@FCurrentData.Data[2]), DataLen);  
//          // Get the Payload  
//          SetString(Payload, PAnsiChar(@FCurrentData.Data[2 + DataLen]), (Length(FCurrentData.Data) - 2 - DataLen));  
//          if Assigned(OnPublish) then OnPublish(Self, Topic, Payload);  
        end  
      else  
      if (MessageType = Ord(SUBACK)) then  
        begin  
//          // Reading the Message ID  
//          ResponseVH := Copy(FCurrentData.Data, 0, 2);  
//          DataLen := BytesToStrLength(ResponseVH);  
//          // Next Read the Granted QoS  
//          QoS := 0;  
//          if (Length(FCurrentData.Data) - 2) > 0 then  
//            begin  
//              ResponseVH := Copy(FCurrentData.Data, 2, 1);  
//              QoS := ResponseVH[0];  
//            end;  
//          if Assigned(OnSubAck) then OnSubAck(Self, DataLen, QoS);  
        end  
      else  
      if (MessageType = Ord(UNSUBACK)) then  
        begin  
//          // Read the Message ID for the event handler  
//          ResponseVH := Copy(FCurrentData.Data, 0, 2);  
//          DataLen := BytesToStrLength(ResponseVH);  
//          if Assigned(OnUnSubAck) then OnUnSubAck(Self, DataLen);  
        end  
      else  
      if (MessageType = Ord(PINGRESP)) then  
      begin  
        fMQTTForDelphi.Memo1.Lines.Add('收到心跳应答包。');  
        //        if Assigned(OnPingResp) then OnPingResp(Self);  
      end;  
    end;  
end;  
  
  
procedure TfMQTTForDelphi.Button1Click(Sender: TObject);  
begin  
  IdTCPClient1.Host:=edtHost.Text;  
  IdTCPClient1.Port:=StrToInt(edtPort.Text);  
  IdTCPClient1.Connect;  
end;  
  
procedure TfMQTTForDelphi.Button2Click(Sender: TObject);  
begin  
  myClientHandleThread:=TClientHandleThread.Create();  
  myClientHandleThread.Resume;  
end;  
  
procedure TfMQTTForDelphi.Button3Click(Sender: TObject);  
var  
  MqttData_FixedHead:Byte;       //固定头  
  MqttData_VariableHeader:TBytes;//可变头  
  MqttData_RemainingLength: TRemainingLength;//剩余长度  
  Payload: TUTF8Text;  
  SendData: TBytes;            //构件完成的最终需要发送的二进制数据  
begin  
  MqttData_FixedHead:=FixedHeader(CONNECT,0,0,0);  
  MqttData_VariableHeader:= VariableHeader_Connect(40);//构建可变头。参数单位秒,在此时间之内,客户端需要发送 PINGREQ 否则服务端将断开网络连接  
  {开始构建有效荷载,由于需要认证帐号密码,此处荷载内容为ID,USER,PWD}  
  SetLength(Payload, 0);  
  AppendArray(Payload, StrToBytes(FClientID, true)); //id  
  AppendArray(Payload, StrToBytes('ade', true));     //user  
  AppendArray(Payload, StrToBytes('ade', true));     //pwd  
  {计算剩余长度}  
  MqttData_RemainingLength:=RemainingLength(Length(MqttData_VariableHeader) + Length(Payload));  
  {组包}  
  SendData:=BuildCommand(MqttData_FixedHead, MqttData_RemainingLength, MqttData_VariableHeader, Payload);  
  {发送}  
  IdTCPClient1.Socket.Write(TIdBytes(SendData));  
end;  
  
procedure TfMQTTForDelphi.Button4Click(Sender: TObject);  
var  
  FH: Byte;  
  RL: Byte;  
  Data: TBytes;  
begin  
  SetLength(Data, 2);  
  FH := FixedHeader(PINGREQ, 0, 0, 0);  
  RL := 0;  
  Data[0] := FH;  
  Data[1] := RL;  
  IdTCPClient1.Socket.Write(TIdBytes(Data));  
end;  
  
  
procedure TfMQTTForDelphi.IdTCPClient1Connected(Sender: TObject);  
begin  
  Memo1.Lines.Add('连接成功');  
end;  
  
procedure TfMQTTForDelphi.IdTCPClient1Disconnected(Sender: TObject);  
begin  
  Memo1.Lines.Add('连接断开');  
end;  
  
  
function FixedHeader(MessageType: TMQTTMessageType; Dup, Qos,Retain: Word): Byte; //固定头第一个字节  
begin  
  { Fixed Header Spec: 
    bit    |7 6 5   4       | |3         | |2   1        |  |  0   | 
    byte 1 |Message Type| |DUP flag| |QoS level|    |RETAIN| }  
  Result := (Ord(MessageType) * 16) + (Dup * 8) + (Qos * 2) + (Retain * 1);  
end;  
  
function RemainingLength(x: Integer): TRemainingLength;     //固定头第二个字节,动态长度1-4  
var  
  byteindex: integer;  
  digit: integer;  
begin  
  SetLength(Result, 1);  
  byteindex := 0;  
  while (x > 0) do  
  begin  
    digit := x mod 128;  
    x := x div 128;  
    if x > 0 then  
    begin  
      digit := digit or 128;  
    end;  
    Result[byteindex] := digit;  
    if x > 0 then  
    begin  
      inc(byteindex);  
      SetLength(Result, Length(Result) + 1);  
    end;  
  end;  
end;  
  
  
function VariableHeader_Connect(KeepAlive: Word): TBytes;  
const  
  MQTT_PROTOCOL = 'MQTT';  
  MQTT_VERSION = 4;//V3.1.1协议级别为4.非3了  
var  
  Qos, Retain: word;  
  iByteIndex: integer;  
  ProtoBytes: TUTF8Text;  
  ConnectFlag:Byte;//连接标志  
begin  
  SetLength(Result, 10);    //长度为10  
  iByteIndex := 0;  
  ProtoBytes := StrToBytes(MQTT_PROTOCOL, true);  
  CopyIntoArray(Result, ProtoBytes, iByteIndex);     //协议名  
  
  
  Inc(iByteIndex, Length(ProtoBytes));  
  Result[iByteIndex] := MQTT_VERSION;               //版本号  
  
  Inc(iByteIndex);  
  asm  
    mov ConnectFlag,11000010B //UserName,Pwd,CleanSession为1,其余均为0  
  end;  
  Result[iByteIndex] := ConnectFlag;//连接标志  
  
  Inc(iByteIndex);  
  Result[iByteIndex] := 0;          //保持连接时间第一位  
  
  Inc(iByteIndex);  
  Result[iByteIndex] := KeepAlive; //保持连接时间第二位  
end;  
  
procedure CopyIntoArray(var DestArray: Array of Byte; SourceArray: Array of Byte; StartIndex: integer);  
begin  
  Assert(StartIndex >= 0);  
  
  Move(SourceArray[0], DestArray[StartIndex], Length(SourceArray));  
end;  
  
  
function StrToBytes(str: string; perpendLength: boolean): TUTF8Text;  
var  
  i, offset: integer;  
begin  
  { This is a UTF-8 hack to give 2 Bytes of Length followed by the string itself. }  
  if perpendLength then  
    begin  
      SetLength(Result, Length(str) + 2);  
      Result[0] := Length(str) div 256;  
      Result[1] := Length(str) mod 256;  
      offset := 1;  
    end  
  else  
    begin  
      SetLength(Result, Length(str));  
      offset := -1;  
    end;  
  for I := 1 to Length(str) do  
    Result[i + offset] := ord(str[i]);  
end;  
  
procedure AppendArray(var Dest: TUTF8Text; Source: Array of Byte);  
var  
  DestLen: Integer;  
begin  
  DestLen := Length(Dest);  
  SetLength(Dest, DestLen + Length(Source));  
  Move(Source, Dest[DestLen], Length(Source));  
end;  
  
function BuildCommand(FixedHeader: Byte; RemainL: TRemainingLength;  VariableHead: TBytes; Payload: Array of Byte): TBytes; //构建最终的发送数据包  
var  
  iNextIndex: integer;  
begin  
  // Attach Fixed Header (1 byte)  
  iNextIndex := 0;  
  SetLength(Result, 1);  
  Result[iNextIndex] := FixedHeader;  
  
  // Attach RemainingLength (1-4 bytes)  
  iNextIndex := Length(Result);  
  SetLength(Result, Length(Result) + Length(RemainL));  
  CopyIntoArray(Result, RemainL, iNextIndex);  
  
  // Attach Variable Head  
  iNextIndex := Length(Result);  
  SetLength(Result, Length(Result) + Length(VariableHead));  
  CopyIntoArray(Result, VariableHead, iNextIndex);  
  
  // Attach Payload.  
  iNextIndex := Length(Result);  
  SetLength(Result, Length(Result) + Length(Payload));  
  CopyIntoArray(Result, Payload, iNextIndex);  
end;  
  
procedure AppendBytes(var DestArray: TBytes;const NewBytes: TBytes);  
var  
  DestLen: Integer;  
begin  
  DestLen := Length(DestArray);  
  SetLength(DestArray, DestLen + Length(NewBytes));  
  Move(NewBytes, DestArray[DestLen], Length(NewBytes));  
end;  
  
function RemainingLengthToInt(RLBytes: TBytes): Integer;  
var  
  multi: integer;  
  i: integer;  
  digit: Byte;  
begin  
  multi := 1;  
  i := 0;  
  Result := 0;  
  
  digit := RLBytes[i];  
  repeat  
    digit := RLBytes[i];  
    Result := Result + (digit and 127) * multi;  
    multi := multi * 128;  
    Inc(i);  
  until ((digit and 128) = 0);  
end;  
  
  
end.  
  
  
{MQTT包格式如下: 
[Fixed header]+[Variable header]+[Payload] 
[Fixed header]: FixedHeader+RemainingLength 
[Variable header] : 报文标识符 
[Payload]:有效荷载 
 
 
[CONNECT]:客户端发起连接 
格式如下: 
[Fixed header]: FixedHeader+RemainingLength 
[Variable header] :协议名+协议级别+连接标志+保持连接 
                04MQTT+$4+Flag+Time 
[Payload]: ClientId+User+Pwd 
 
}  
后来测试以上代码在win32正常,安卓平台需要修改一下字符串的编码函数。需要注意。
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: MQTT(Message Queuing Telemetry Transport)是一种轻量级的消息传递协议,用于不同设备之间的通信。MQTT 3.1.1是MQTT的版本号,是比较常用的版本。 MQTT 3.1.1协议具有以下特点和优势: 1. 轻量级:MQTT采用一种非常轻巧的封装格式,使得它非常适合在网络带宽较低或者资源受限的环境下使用,如物联网设备中。 2. 简单易用:MQTT协议的设计非常简单,只有3个主要角色:发布者(Publisher)、订阅者(Subscriber)和代理服务器(Broker)。这种简单的架构使得开发者可以轻松地实现MQTT通信。 3. 异步通信:MQTT允许发布者和订阅者之间进行异步通信。发布者只需要将消息发布到代理服务器上,订阅者则可以在需要的时候订阅到消息。这种异步通信的特性可以提高通信的效率和可扩展性。 4. 可靠性:MQTT 3.1.1协议提供了多种传输层的质量保证服务,如最多一次、至少一次和只有一次的传输保证。根据应用场景的不同,可以选择适当的服务质量。 5. 安全性:MQTT 3.1.1协议支持SSL/TLS加密和鉴权机制,确保通信的安全性。通过这些安全机制,可以对消息进行加密传输,同时也可以对客户端进行身份验证。 总而言之,MQTT 3.1.1协议是一种适用于物联网设备的轻量级、简单易用、可靠和安全的通信协议。它的设计目标是在网络带宽有限和资源受限的条件下,实现可靠的异步通信。在物联网领域,MQTT 3.1.1广泛应用于各种场景,如传感器数据的采集、智能家居的控制等。 ### 回答2: MQTT 3.1.1协议是一种轻量级的通信协议,主要用于物联网和小型设备之间的通信。 MQTT(Message Queuing Telemetry Transport)是一种基于发布/订阅的模式的消息协议,具有低带宽、低数据包开销和低延迟的特点。它使用TCP/IP协议进行通信,并支持多种可靠性的服务质量等级。 MQTT 3.1.1协议定义了客户端与服务器之间的消息传输格式和规则。客户端可以是任何设备或应用程序,服务器则负责接收、转发和传送消息。 MQTT 3.1.1协议使用发布/订阅模式进行通信。客户端可以发布消息到特定的主题(topic),而其他客户端可以通过订阅相应的主题来接收消息。通过定义主题层次结构,可以实现灵活的消息过滤和分发。 协议中还定义了三个服务质量等级(QoS):0级为“至多一次”,消息会至少传输一次,但可能会重复或丢失;1级为“至少一次”,消息会至少传输一次,但可以重复;2级为“恰好一次”,消息会确保只传输一次。 此外,MQTT 3.1.1协议还支持保留消息和遗嘱消息的功能。保留消息是指发布到特定主题的消息会被保留在服务器上,新的订阅者可以接收到最新的保留消息。而遗嘱消息是当客户端断开连接时,服务器会代替客户端发布一个预先定义的消息,用于通知其他订阅者。 总的来说,MQTT 3.1.1协议提供了一种高效、灵活和可靠的通信机制,特别适用于物联网场景。它的低带宽和低功耗特性使其成为物联网设备之间通信的理想选择。 ### 回答3: MQTT(Message Queuing Telemetry Transport)是一种轻量级发布/订阅协议,用于在物联网设备之间传输消息。MQTT 3.1.1是MQTT协议的一个版本。 MQTT 3.1.1协议增强了先前版本的功能和性能。它主要包含以下几个方面的改进: 1. 会话状态:MQTT 3.1.1引入了会话状态的概念,使得设备可以保存和恢复连接状态。这意味着设备断开连接后,可以通过恢复会话状态来继续传输消息。 2. 心跳机制:MQTT 3.1.1中添加了心跳机制,用于检测设备是否在线。通过在连接期间定期发送心跳包,可以及时检测设备的连接状态。如果设备长时间未发送心跳包,服务器就可以判断设备已经离线,并采取相应的措施。 3. 遗愿消息:MQTT 3.1.1允许设备在断开连接时发送遗愿消息。遗愿消息是设备事先配置好的消息,当设备异常断开连接时,服务器会自动发布这些消息给其他订阅者。 4. 主题过滤器:MQTT 3.1.1支持更灵活的主题过滤器,可以通过通配符匹配多个主题。这使得订阅者可以更方便地订阅特定类型的消息。 5. 安全性:MQTT 3.1.1支持SSL/TLS加密传输,保护消息的安全性。设备和服务器可以使用用户名和密码进行身份验证,确保只有授权的设备才能连接和发布消息。 总的来说,MQTT 3.1.1协议在原有的基础上增加了一些重要的功能和改进,使得物联网设备之间的通信更加可靠和灵活。它被广泛应用于物联网领域,提供了一种高效、轻量级的消息传输解决方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值