Mqtt ----心跳機制 長鏈接 ping

Mqtt ----心跳機制

心跳機制

  Keep Alive指定連接最大空閒時間T,當客戶端檢測到連接空閒時間超過T時,必須向Broker發送心跳報文PINGREQ,Broker收到心跳請求後返回心跳響應PINGRESP。若Broker超過1.5T時間沒收到心跳請求則斷開連接,並且投遞遺囑消息到訂閱方;同樣,若客戶端超過一定時間仍沒收到心跳響應PINGRESP則斷開連接。 
  連接空閒時發送心跳報文可以降低網絡請求,弱化對帶寬的依賴。

Keep Alive設定時機

  創建連接時,在CONNECT報文中指定,單位s。

Client Take-Over

場景:客戶端與Broker連接正常,然後客戶端快速重啓(小於1.5T),再重新連接Broker,在未達到1.5T這段時間內,客戶端與Broker存在兩條連接。 
處理措施: 先斷開之前的連接再建立新的連接。

 

 mqtt 協議裏最簡單的是 ping 協議吧 (心跳包), ping 協議是已連接的客戶端發往服務端, 告訴服務端,我還"活着"

PINGREQ - PING request

fixed header format.

 

bit 7 6 5 4 3 2 1 0
byte 1 Message Type (12) DUP flag QoS level RETAIN
  1 1 0 0 x x x x
byte 2 Remaining Length (0)
  0 0 0 0 0 0 0 0

 

no variable header

no payload 

response:   The response to a PINGREQ message is a PINGRESP message.

 

PINGRESP - PING response

 

fixed header

 

bit 7 6 5 4 3 2 1 0
byte 1 Message Type (13) DUP flag QoS level RETAIN
  1 1 0 1 x x x x
byte 2 Remaining Length (0)
  0 0 0 0 0 0 0 0

 

no variable header

no payload

 

------------------------------------------------------------------------ 華麗的分界線 ---------------------------------------

 

客戶端會在一個心跳週期內發送一條PINGREQ消息到服務器端。兩個字節,固定值。

服務器收到PINGREQ請求之後,會立即響應一個兩個字節固定格式的PINGRESP消息。

週期定義在 心跳頻率在CONNECT(連接包)可變頭部“Keep Alive timer”中定義時間,單位爲秒,無符號16位short表示。

ok ,上代碼 :

固定頭部 FinedHeader

複製代碼

    /// <summary>
    /// Fixed header
    /// </summary>
    internal class FixedHeader
    {
        /// <summary>
        /// Message type
        /// </summary>
        public MessageType MessageType { get; set; }

        /// <summary>
        /// DUP flag
        /// </summary>
        public bool Dup { get; set; }

        /// <summary>
        /// QoS flags
        /// </summary>
        public Qos Qos { get; set; }

        /// <summary>
        /// RETAIN 保持
        /// </summary>
        public bool Retain { get; set; }

        /// <summary>
        /// Remaining Length 剩餘長度
        /// 單個字節最大值:01111111,16進制:0x7F,10進製爲127。
        /// MQTT協議規定,第八位(最高位)若爲1,則表示還有後續字節存在。
        /// MQTT協議最多允許4個字節表示剩餘長度。
        /// 最大長度爲:0xFF,0xFF,0xFF,0x7F,
        /// 二進制表示爲:11111111,11111111,11111111,01111111,十進制:268435455
        /// </summary>
        public int RemaingLength { get; set; }

        public FixedHeader() { }

        public FixedHeader(Stream stream)
        {
            if (stream.Length < 2)
                throw new Exception("The supplied header is invalid. Header must be at least 2 bytes long.");

            var byte1 = stream.ReadByte();
            MessageType = (MessageType)((byte1 & 0xf0) >> 4);
            Dup = ((byte1 & 0x08) >> 3) > 0;
            Qos = (Qos)((byte1 & 0x06) >> 1);
            Retain = (byte1 & 0x01) > 0;

            //Remaining Length
            //var byte2 = stream.ReadByte();
            var lengthBytes = ReadLengthBytes(stream);
            RemaingLength = CalculateLength(lengthBytes);
        }

        public void WriteTo(Stream stream)
        {
            var flags = (byte)MessageType << 4;
            flags |= (Dup ? 1 : 0) << 3;
            flags |= (byte)Qos << 1;
            flags |= Retain ? 1 : 0;

            stream.WriteByte((byte)flags);     //byte 1
            if (RemaingLength == 0)         //byte 2
                stream.WriteByte(0);
            else
            {
                do
                {
                    int digit = RemaingLength & 0x7f;
                    RemaingLength = RemaingLength >> 7;
                    if (RemaingLength > 0)
                        digit = digit | 0x80;
                    stream.WriteByte((byte)digit);
                } while (RemaingLength > 0);
            }
        }

        internal static byte[] ReadLengthBytes(Stream stream)
        {
            var lengthBytes = new List<byte>();

            // read until we've got the entire size, or the 4 byte limit is reached
            byte sizeByte;
            int byteCount = 0;
            do
            {
                sizeByte = (byte)stream.ReadByte();
                lengthBytes.Add(sizeByte);
            } while (++byteCount <= 4 && (sizeByte & 0x80) == 0x80);

            return lengthBytes.ToArray();
        }

        internal static int CalculateLength(byte[] lengthBytes)
        {
            var remainingLength = 0;
            var multiplier = 1;

            foreach (var currentByte in lengthBytes)
            {
                remainingLength += (currentByte & 0x7f) * multiplier;
                multiplier *= 0x80;
            }

            return remainingLength;
        }
    }

複製代碼

 

消息父類: Message

複製代碼

    internal class Message
    {
        public FixedHeader FixedHeader { get; protected set; }

        public Message()
        {
        }

        public Message(MessageType messageType)
        {
            FixedHeader = new FixedHeader
            {
                MessageType = messageType
            };
        }

        public virtual void WriteTo(Stream stream)
        {
        }

        public static Message CreateFrom(byte[] buffer)
        {
            using (var stream = new MemoryStream(buffer))
            {
                return CreateFrom(stream);
            }
        }

        public static Message CreateFrom(Stream stream)
        {
            var header = new FixedHeader(stream);
            return CreateMessage(header, stream);
        }

        public static Message CreateMessage(FixedHeader header, Stream stream)
        {
            switch (header.MessageType)
            {
                case MessageType.CONNACK:
                    return new ConnAckMessage(header, stream);
                case MessageType.DISCONNECT:
                    return null;
                case MessageType.PINGREQ:
                    return new PingReqMessage();
                case MessageType.PUBACK:
                    return new PublishAckMessage(header, stream);
                case MessageType.PUBCOMP:
                    //return new MqttPubcompMessage(str, header);
                case MessageType.PUBLISH:
                    //return new MqttPublishMessage(str, header);
                case MessageType.PUBREC:
                    //return new MqttPubrecMessage(str, header);
                case MessageType.PUBREL:
                    //return new MqttPubrelMessage(str, header);
                case MessageType.SUBACK:
                    //return new MqttSubackMessage(str, header);
                case MessageType.UNSUBACK:
                    //return new MqttUnsubackMessage(str, header);
                case MessageType.PINGRESP:
                    return new PingRespMessage(header, stream);
                case MessageType.UNSUBSCRIBE:
                case MessageType.CONNECT:
                case MessageType.SUBSCRIBE:
                default:
                    throw new Exception("Unsupported Message Type");
            }
        }
    }

複製代碼

 

兩個枚舉:

MessageType  (消息類型)

Qos (服務質量等級)

複製代碼

    [Flags]
    public enum MessageType : byte
    {
        CONNECT     = 1,
        CONNACK     = 2,
        PUBLISH     = 3,
        PUBACK      = 4,
        PUBREC      = 5,
        PUBREL      = 6,
        PUBCOMP     = 7,
        SUBSCRIBE   = 8,
        SUBACK      = 9,
        UNSUBSCRIBE = 10,
        UNSUBACK    = 11,
        PINGREQ     = 12,
        PINGRESP    = 13,
        DISCONNECT  = 14
    }

    /// <summary>
    /// 服務質量等級
    /// </summary>
    [Flags]
    public enum Qos : byte
    {
        /// <summary>
        ///     QOS Level 0 - Message is not guaranteed delivery. No retries are made to ensure delivery is successful.
        /// </summary>
        AtMostOnce = 0,

        /// <summary>
        ///     QOS Level 1 - Message is guaranteed delivery. It will be delivered at least one time, but may be delivered
        ///     more than once if network errors occur.
        /// </summary>
        AtLeastOnce = 1,

        /// <summary>
        ///     QOS Level 2 - Message will be delivered once, and only once. Message will be retried until
        ///     it is successfully sent..
        /// </summary>
        ExactlyOnce = 2,
    }

複製代碼

 

 

ping 請求包:  PingReqMessage

響應包:         PingRespMessage

複製代碼

    internal sealed class PingReqMessage : Message
    {
        public PingReqMessage()
            : base(MessageType.PINGREQ)
        {
        }

        public override void WriteTo(Stream stream)
        {
            FixedHeader.WriteTo(stream);
        }
    }

    internal class PingRespMessage : Message
    {
        public PingRespMessage()
            : base(MessageType.PINGRESP)
        {
        }

        public PingRespMessage(FixedHeader header, Stream stream)
        {
            FixedHeader = header;
        }
    }

複製代碼

 

 

 OK.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章