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

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/oYaoWang123/article/details/52228166
需要做一个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正常,安卓平台需要修改一下字符串的编码函数。需要注意。
展开阅读全文

没有更多推荐了,返回首页