C#MQTT编程08--MQTT服务器和客户端(cmd版)

1、前言

前面完成了winform版,wpf版,为什么要搞个cmd版,因为前面介绍了mqtt的报文结构,重点分析了【连接报文】,【订阅报文】,【发布报文】,这节就要就看看实际报文是怎么组装的,这也是之前详细每个报文的结构,含义的目的,使用mqttnet这个组件实现mqtt通信是直接应用,不涉及到底层报文的结构内容,用户是看不到报文内容的,这节的目的就是为加深理解而干的。这节不安装任何mqtt的组件,而是直接使用socket的原始方式通信。

2、报文回顾

一共有14个报文,如下图

可以去看看3个报文的详细介绍,什么固定报头,可变报头,有效载荷这些东东:

C#MQTT编程03--连接报文

C#MQTT编程04--订阅报文 

C#MQTT编程05--发布报文 

总结出来就是这样的:

连接报文是客户端发1,服务器回2,

订阅报文是客户端发8,服务器回9,

发布报文是客户端发3,服务器回4,

心跳报文是客户端发12,服务器回13。

3、开始卷

1、创建项目方案

 2、编写连接报文

 完整的连接代码:

/// <summary>
/// 连接
/// </summary>
static void Connection()
{
    // MQTT不支持UDP
    socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    socket.Connect("127.0.0.1", 1869);
    //连接报文
    List<byte> connBytes = new List<byte>();

    #region 第一部分,固定报头
    // 第0个字节:固定报头
    List<byte> headerBytes = new List<byte>
    {
        1<<4 //表示连接请求  消息类型  
    };
    //第1个字节:剩余字节长度
    //需要后面计算得到
    #endregion

    #region 第二部分,可变报头 

    // 第2,3个字节:协议名称MQTT的字节长度
    List<byte> bodyBytes = new List<byte>();
    string protocolName = "MQTT";
    byte[] pnameBytes = Encoding.ASCII.GetBytes(protocolName);//得到“MQTT”的字节数组 
    bodyBytes.Add((byte)(pnameBytes.Length / 256 % 256));//高4位
    bodyBytes.Add((byte)(pnameBytes.Length % 256));//低4位

    // 第4,5,6,7个字节:协议名称
    bodyBytes.AddRange(pnameBytes);

    // 第8个字节: 协议版本
    bodyBytes.Add(0x04);

    // 第9个字节: 负载是否需要用户名,密码等设置
    byte flagByte = 0;
    flagByte |= 128;        //  1 0 0 0  0 0 0 0   128      // 需要用户 
    flagByte |= 64;         //  0 1 0 0  0 0 0 0   64      // 需要密码             
    flagByte |= 2;  // CleanSession
    bodyBytes.Add(flagByte);

    //  第10,11个字节: Keep Alive保持连接的时间,高位在前,低位在后
    int seconds = 100;  // 秒为单位
    bodyBytes.Add((byte)(seconds / 256 % 256));
    bodyBytes.Add((byte)(seconds % 256));

    #endregion

    #region 第三部分,载荷

    List<byte> loadBytes = new List<byte>();
    //   第12,13个字节:ClientID字符长度
    string clientID = "x2";
    byte[] ciBytes = Encoding.ASCII.GetBytes(clientID);
    loadBytes.Add((byte)(ciBytes.Length / 256 % 256));
    loadBytes.Add((byte)(ciBytes.Length % 256));

    //   第14,115,16,17个字节:ClientID
    loadBytes.AddRange(ciBytes);

    //   第18,19个字节:用户名长度
    string username = "boss";
    byte[] unBytes = Encoding.ASCII.GetBytes(username);
    loadBytes.Add((byte)(unBytes.Length / 256 % 256));
    loadBytes.Add((byte)(unBytes.Length % 256));

    //   第20,21,22,23,24个字节:用户名
    loadBytes.AddRange(unBytes);

    //   第25,26个字节:密码长度
    string pwd = "1234";
    byte[] pwdBytes = Encoding.ASCII.GetBytes(pwd);
    loadBytes.Add((byte)(pwdBytes.Length / 256 % 256));
    loadBytes.Add((byte)(pwdBytes.Length % 256));

    //   第27,28,29,30,31个字节:密码
    loadBytes.AddRange(pwdBytes);

    #endregion

    //第1个字节:剩余字节长度,从第 2 个字节开始。
    headerBytes.Add((byte)(bodyBytes.Count + loadBytes.Count));

    //组装成报文
    connBytes.AddRange(headerBytes);
    connBytes.AddRange(bodyBytes);
    connBytes.AddRange(loadBytes);

    //发送报文
    socket.Send(connBytes.ToArray());

    // 异步处理:开始心跳
    Task.Run(async () =>
    {
        byte[] pingBytes = new byte[2] { 12 << 4, 0 };//心跳的字节报文是固定的
        while (true)
        {
            Console.WriteLine("心跳时间:" + DateTime.Now.ToString());
            await Task.Delay(1000);//等待1秒
            socket.Send(pingBytes);
        }
    });


    //异步处理:服务器返回的报文
    Task.Run(() =>
    {
        //1:请求连接 (C->S)
        //2:连接确认 (S->C)
        //3:发布消息 (Both)
        //4:发布收到确认 (QoS > 0)
        //5:发布确认收到
        //6:发布释放
        //7:发布完成 (QoS 2)
        //8:订阅请求 (C->S)
        //9:订阅请求确认 (S->C)
        //10:取消订阅请求 (C->S)
        //11:取消订阅请求确认 (S->C)
        //12:心跳请求 (C->S)
        //13:心跳确认 (S->C)
        //14:客户端断开连接 (C->S)
        byte[] respBytes = new byte[1]; //接收MQTT报文类型,报文类型占1个字节
        //连接成功,MQTT报文类型(CONNACK),服务器返回2,即0000 0010,高低位交换位置就是返回:0 0 1 0 0 0 0 0  ,转成10进制就是32
        //发布成功,MQTT报文类型(PUBACK), 服务器返回4,即0000 0100,高低位交换位置就是返回:0 1 0 0 0 0 0 0  ,转成10进制就是64
        //订阅成功,MQTT报文类型(SUBACK), 服务器返回9,即0000 1001,高低位交换位置就是返回:1 0 0 1 0 0 0 0  ,转成10进制就是144
        //心跳成功,MQTT报文类型(PINGRESP),服务器返回13,即0000 1101,高低位交换位置就是返回:1 1 0 1 0 0 0 0 ,转成10进制就是208
        while (true)//循环接收
        {
            try
            {
                socket.Receive(respBytes, 0, 1, SocketFlags.None);
                int firstValue = Convert.ToInt32(respBytes[0]);
                //Console.WriteLine("第一个字节:" + firstValue);
                //根据报文类型进行处理
                switch (firstValue)
                {
                    case 32:
                        Console.WriteLine("连接成功!");
                        break;
                    case 64:
                        Console.WriteLine("发布成功!");
                        break;
                    case 144:
                        Console.WriteLine("订阅成功!");
                        break;
                    case 208:
                        Console.WriteLine("心跳成功!");
                        break;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("出错了," + ex.Message);
            }
        }
    });
}

特别注意这里的处理

 //连接成功,MQTT报文类型(CONNACK),服务器返回2,即0000 0010,高低位交换位置就是返回:0 0 1 0 0 0 0 0  ,转成10进制就是32
 //发布成功,MQTT报文类型(PUBACK), 服务器返回4,即0000 0100,高低位交换位置就是返回:0 1 0 0 0 0 0 0  ,转成10进制就是64
 //订阅成功,MQTT报文类型(SUBACK), 服务器返回9,即0000 1001,高低位交换位置就是返回:1 0 0 1 0 0 0 0  ,转成10进制就是144
 //心跳成功,MQTT报文类型(PINGRESP),服务器返回13,即0000 1101,高低位交换位置就是返回:1 1 0 1 0 0 0 0 ,转成10进制就是208

心跳的处理,它的作用是不断地发送命令,以证明客户端存在

 测试连接

先把前面的wpf版程序运行,启动服务器,启动客户端连接服务器

再启动本项目程序,可以看到连接成功,心跳也成功。

 

3、编写订阅报文

这里设置的qos级别是1,Qos级别-》 0:最多一次的传输,1:至少一次的传输、至多无限次,2:有且仅有一次的传输

完整代码

 

  /// <summary>
  /// 订阅
  /// </summary>
  /// <param name="topics">主题列表</param>
  static void Subscription(List<string> topics)
  {
      List<byte> headerBytes = new List<byte>();
      List<byte> bodyBytes = new List<byte>();

      //第0个字节:报文类型(10000010)
      byte msgType = 8 << 4;   //  1000 0000 
      headerBytes.Add((byte)(msgType | 2));

      //第1个字节:剩余字节长度,等后面计算获取后再添加

      //第2,3个字节:Package Identifier的长度,表示报文的标识
      int pi = random.Next(0, 1000);  // Package Identifier的具体值
      bodyBytes.Add((byte)(pi / 256 % 256));//高位
      bodyBytes.Add((byte)(pi % 256));//低位

      //遍历所有主题
      foreach (var item in topics)
      {
          //第8,9个字节:topic字符长度
          byte[] itemBytes = Encoding.UTF8.GetBytes(item);
          bodyBytes.Add((byte)(itemBytes.Length / 256 % 256));
          bodyBytes.Add((byte)(itemBytes.Length % 256));

          //第10,11,12,13,14,16个字节:topic字符内容
          bodyBytes.AddRange(itemBytes);

          //第17个字节:Qos级别-》 0:最多一次的传输,1:至少一次的传输、至多无限次,2:有且仅有一次的传输
          bodyBytes.Add(0x01);
      }

      //第1个字节:剩余字节长度,从第 2 个字节开始。
      headerBytes.Add((byte)bodyBytes.Count);

      //组成报文
      headerBytes.AddRange(bodyBytes);

      //发送报文
      socket.Send(headerBytes.ToArray());

      //接收服务器回应的报文 
      //byte[] respBytes = new byte[5];
      //socket.Receive(respBytes, 0, 5, SocketFlags.None);
      //var objSub = respBytes; 
  }

测试订阅

先让wpf客户端订阅一个主题“shanghai",订阅成功

再看本项目程序订阅主题”shanghai",可以看到订阅成功

4、编写发布报文

完整代码,注释详情

 /// <summary>
 /// 发布消息:服务级别(Qos1)
 /// </summary>
 static void Publish_Qos1()
 {
     #region 方法1
     List<byte> headerBytes = new List<byte>();

     //报文类型
     byte msgType = 3 << 4;   //  1000 0000 
     headerBytes.Add((byte)(msgType | 2));   // QoS-0低4位全为1

     List<byte> bodyBytes = new List<byte>();
     string topic = "shanghai";
     string msg = "hello9098";

     // 添加主题长度
     byte[] topicBytes = Encoding.UTF8.GetBytes(topic);
     bodyBytes.Add((byte)(topicBytes.Length / 256 % 256));
     bodyBytes.Add((byte)(topicBytes.Length % 256));

     // 添加主题内容
     bodyBytes.AddRange(topicBytes);

     // 必须添加Package Identifier:只包括它的字节长度
     int pi = random.Next(0, 1000);  // Package Identifier
     //Console.WriteLine(pi);
     bodyBytes.Add((byte)(pi / 256 % 256));
     bodyBytes.Add((byte)(pi % 256));

     // 添加消息长度 
     byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
     bodyBytes.Add((byte)(msgBytes.Length / 256 % 256));
     bodyBytes.Add((byte)(msgBytes.Length % 256));

     // 添加消息内容
     bodyBytes.AddRange(msgBytes);

     //添加第1个字节:剩余字节长度
     headerBytes.Add((byte)bodyBytes.Count);

     // 组装头   
     headerBytes.AddRange(bodyBytes);

     //发送消息
     socket.Send(headerBytes.ToArray());
     #endregion 


     //#region 方法2
     //string topic = "shanghai";
     //string msg = "hello9098";
     //int pi = random.Next(0, 1000);  // Package Identifier
     //List<byte> topicbytes = new List<byte>();
     //byte[] topicArray = Encoding.UTF8.GetBytes(topic);
     //byte[] payloadArray = Encoding.UTF8.GetBytes(msg);

     //topicbytes.Add((byte)((int)topicArray.Length / 256));
     //topicbytes.Add((byte)((int)topicArray.Length % 256));
     //topicbytes.AddRange(topicArray);

     //byte[] id = new byte[] { (byte)(pi / 256 % 256), (byte)(pi % 256) };
     //byte[] bufferLen = new byte[] { (byte)(topicbytes.Count + payloadArray.Length + id.Length) };
     //using (MemoryStream memoryStream = new MemoryStream())
     //{
     //    memoryStream.WriteByte((3 << 4) | 2 | 1);// 写入消息类型(QoS-1)
     //    memoryStream.Write(bufferLen, 0, (int)bufferLen.Length);// 写入后续报文长度
     //    memoryStream.Write(topicbytes.ToArray(), 0, (int)topicbytes.Count);// 写入Topic字节
     //    memoryStream.Write(id.ToArray(), 0, (int)id.Length);// 写入Package Identifier字节
     //    memoryStream.Write(payloadArray.ToArray(), 0, (int)payloadArray.Length);// 写入消息
     //    byte[] sendArray = memoryStream.ToArray();
     //    socket.Send(sendArray);
     //}
     接收服务器回应的报文 
     //byte[] respBytes = new byte[4];
     //socket.Receive(respBytes, 0, 4, SocketFlags.None);
     //var objSub = respBytes;

     //#endregion 
 }

测试发布

 前面的c1订阅了主题“shanghai",现在的x2客户端向shanghai主题发布一个消息,看看c1能不能收到

 

最后全部完整代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace MQTTNETClientCMD
{
    internal class Program
    {
        static Socket socket;//socket对象
        static Random random = new Random();//随机数,用于产生package identifier
        static List<string> topic = new List<string> { "shanghai" };//主题
        static void Main(string[] args)
        {
            Console.WriteLine("Hello MQTT!");

            Connection();//连接  
            Subscription(topic);//订阅
            Publish_Qos1();// 发布Qos=1
            Console.ReadKey();
        }

        /// <summary>
        /// 连接
        /// </summary>
        static void Connection()
        {
            // MQTT不支持UDP
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Connect("127.0.0.1", 1869);
            //连接报文
            List<byte> connBytes = new List<byte>();

            #region 第一部分,固定报头
            // 第0个字节:固定报头
            List<byte> headerBytes = new List<byte>
            {
                1<<4 //表示连接请求  消息类型  
            };
            //第1个字节:剩余字节长度
            //需要后面计算得到
            #endregion

            #region 第二部分,可变报头 

            // 第2,3个字节:协议名称MQTT的字节长度
            List<byte> bodyBytes = new List<byte>();
            string protocolName = "MQTT";
            byte[] pnameBytes = Encoding.ASCII.GetBytes(protocolName);//得到“MQTT”的字节数组 
            bodyBytes.Add((byte)(pnameBytes.Length / 256 % 256));//高4位
            bodyBytes.Add((byte)(pnameBytes.Length % 256));//低4位

            // 第4,5,6,7个字节:协议名称
            bodyBytes.AddRange(pnameBytes);

            // 第8个字节: 协议版本
            bodyBytes.Add(0x04);

            // 第9个字节: 负载是否需要用户名,密码等设置
            byte flagByte = 0;
            flagByte |= 128;        //  1 0 0 0  0 0 0 0   128      // 需要用户 
            flagByte |= 64;         //  0 1 0 0  0 0 0 0   64      // 需要密码             
            flagByte |= 2;  // CleanSession
            bodyBytes.Add(flagByte);

            //  第10,11个字节: Keep Alive保持连接的时间,高位在前,低位在后
            int seconds = 100;  // 秒为单位
            bodyBytes.Add((byte)(seconds / 256 % 256));
            bodyBytes.Add((byte)(seconds % 256));

            #endregion

            #region 第三部分,载荷

            List<byte> loadBytes = new List<byte>();
            //   第12,13个字节:ClientID字符长度
            string clientID = "x2";
            byte[] ciBytes = Encoding.ASCII.GetBytes(clientID);
            loadBytes.Add((byte)(ciBytes.Length / 256 % 256));
            loadBytes.Add((byte)(ciBytes.Length % 256));

            //   第14,115,16,17个字节:ClientID
            loadBytes.AddRange(ciBytes);

            //   第18,19个字节:用户名长度
            string username = "boss";
            byte[] unBytes = Encoding.ASCII.GetBytes(username);
            loadBytes.Add((byte)(unBytes.Length / 256 % 256));
            loadBytes.Add((byte)(unBytes.Length % 256));

            //   第20,21,22,23,24个字节:用户名
            loadBytes.AddRange(unBytes);

            //   第25,26个字节:密码长度
            string pwd = "1234";
            byte[] pwdBytes = Encoding.ASCII.GetBytes(pwd);
            loadBytes.Add((byte)(pwdBytes.Length / 256 % 256));
            loadBytes.Add((byte)(pwdBytes.Length % 256));

            //   第27,28,29,30,31个字节:密码
            loadBytes.AddRange(pwdBytes);

            #endregion

            //第1个字节:剩余字节长度,从第 2 个字节开始。
            headerBytes.Add((byte)(bodyBytes.Count + loadBytes.Count));

            //组装成报文
            connBytes.AddRange(headerBytes);
            connBytes.AddRange(bodyBytes);
            connBytes.AddRange(loadBytes);

            //发送报文
            socket.Send(connBytes.ToArray());

            // 异步处理:开始心跳
            Task.Run(async () =>
            {
                byte[] pingBytes = new byte[2] { 12 << 4, 0 };//心跳的字节报文是固定的
                while (true)
                {
                    Console.WriteLine("心跳时间:" + DateTime.Now.ToString());
                    await Task.Delay(1000);//等待1秒
                    socket.Send(pingBytes);
                }
            });


            //异步处理:服务器返回的报文
            Task.Run(() =>
            {
                //1:请求连接 (C->S)
                //2:连接确认 (S->C)
                //3:发布消息 (Both)
                //4:发布收到确认 (QoS > 0)
                //5:发布确认收到
                //6:发布释放
                //7:发布完成 (QoS 2)
                //8:订阅请求 (C->S)
                //9:订阅请求确认 (S->C)
                //10:取消订阅请求 (C->S)
                //11:取消订阅请求确认 (S->C)
                //12:心跳请求 (C->S)
                //13:心跳确认 (S->C)
                //14:客户端断开连接 (C->S)
                byte[] respBytes = new byte[1]; //接收MQTT报文类型,报文类型占1个字节
                //连接成功,MQTT报文类型(CONNACK),服务器返回2,即0000 0010,高低位交换位置就是返回:0 0 1 0 0 0 0 0  ,转成10进制就是32
                //发布成功,MQTT报文类型(PUBACK), 服务器返回4,即0000 0100,高低位交换位置就是返回:0 1 0 0 0 0 0 0  ,转成10进制就是64
                //订阅成功,MQTT报文类型(SUBACK), 服务器返回9,即0000 1001,高低位交换位置就是返回:1 0 0 1 0 0 0 0  ,转成10进制就是144
                //心跳成功,MQTT报文类型(PINGRESP),服务器返回13,即0000 1101,高低位交换位置就是返回:1 1 0 1 0 0 0 0 ,转成10进制就是208
                while (true)//循环接收
                {
                    try
                    {
                        socket.Receive(respBytes, 0, 1, SocketFlags.None);
                        int firstValue = Convert.ToInt32(respBytes[0]);
                        //Console.WriteLine("第一个字节:" + firstValue);
                        //根据报文类型进行处理
                        switch (firstValue)
                        {
                            case 32:
                                Console.WriteLine("连接成功!");
                                break;
                            case 64:
                                Console.WriteLine("发布成功!");
                                break;
                            case 144:
                                Console.WriteLine("订阅成功!");
                                break;
                            case 208:
                                Console.WriteLine("心跳成功!");
                                break;
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("出错了," + ex.Message);
                    }
                }
            });
        }

        /// <summary>
        /// 订阅
        /// </summary>
        /// <param name="topics">主题列表</param>
        static void Subscription(List<string> topics)
        {
            List<byte> headerBytes = new List<byte>();
            List<byte> bodyBytes = new List<byte>();

            //第0个字节:报文类型(10000010)
            byte msgType = 8 << 4;   //  1000 0000 
            headerBytes.Add((byte)(msgType | 2));

            //第1个字节:剩余字节长度,等后面计算获取后再添加

            //第2,3个字节:Package Identifier的长度,表示报文的标识
            int pi = random.Next(0, 1000);  // Package Identifier的具体值
            bodyBytes.Add((byte)(pi / 256 % 256));//高位
            bodyBytes.Add((byte)(pi % 256));//低位

            //遍历所有主题
            foreach (var item in topics)
            {
                //第8,9个字节:topic字符长度
                byte[] itemBytes = Encoding.UTF8.GetBytes(item);
                bodyBytes.Add((byte)(itemBytes.Length / 256 % 256));
                bodyBytes.Add((byte)(itemBytes.Length % 256));

                //第10,11,12,13,14,16个字节:topic字符内容
                bodyBytes.AddRange(itemBytes);

                //第17个字节:Qos级别-》 0:最多一次的传输,1:至少一次的传输、至多无限次,2:有且仅有一次的传输
                bodyBytes.Add(0x01);
            }

            //第1个字节:剩余字节长度,从第 2 个字节开始。
            headerBytes.Add((byte)bodyBytes.Count);

            //组成报文
            headerBytes.AddRange(bodyBytes);

            //发送报文
            socket.Send(headerBytes.ToArray());

            //接收服务器回应的报文 
            //byte[] respBytes = new byte[5];
            //socket.Receive(respBytes, 0, 5, SocketFlags.None);
            //var objSub = respBytes; 
        }

        /// <summary>
        /// 发布消息:服务级别(Qos1)
        /// </summary>
        static void Publish_Qos1()
        {
            #region 方法1
            List<byte> headerBytes = new List<byte>();

            //报文类型
            byte msgType = 3 << 4;   //  1000 0000 
            headerBytes.Add((byte)(msgType | 2));   // QoS-0低4位全为1

            List<byte> bodyBytes = new List<byte>();
            string topic = "shanghai";
            string msg = "hello9098";

            // 添加主题长度
            byte[] topicBytes = Encoding.UTF8.GetBytes(topic);
            bodyBytes.Add((byte)(topicBytes.Length / 256 % 256));
            bodyBytes.Add((byte)(topicBytes.Length % 256));

            // 添加主题内容
            bodyBytes.AddRange(topicBytes);

            // 必须添加Package Identifier:只包括它的字节长度
            int pi = random.Next(0, 1000);  // Package Identifier
            //Console.WriteLine(pi);
            bodyBytes.Add((byte)(pi / 256 % 256));
            bodyBytes.Add((byte)(pi % 256));

            // 添加消息长度 
            byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
            bodyBytes.Add((byte)(msgBytes.Length / 256 % 256));
            bodyBytes.Add((byte)(msgBytes.Length % 256));

            // 添加消息内容
            bodyBytes.AddRange(msgBytes);

            //添加第1个字节:剩余字节长度
            headerBytes.Add((byte)bodyBytes.Count);

            // 组装头   
            headerBytes.AddRange(bodyBytes);

            //发送消息
            socket.Send(headerBytes.ToArray());
            #endregion 


            //#region 方法2
            //string topic = "shanghai";
            //string msg = "hello9098";
            //int pi = random.Next(0, 1000);  // Package Identifier
            //List<byte> topicbytes = new List<byte>();
            //byte[] topicArray = Encoding.UTF8.GetBytes(topic);
            //byte[] payloadArray = Encoding.UTF8.GetBytes(msg);

            //topicbytes.Add((byte)((int)topicArray.Length / 256));
            //topicbytes.Add((byte)((int)topicArray.Length % 256));
            //topicbytes.AddRange(topicArray);

            //byte[] id = new byte[] { (byte)(pi / 256 % 256), (byte)(pi % 256) };
            //byte[] bufferLen = new byte[] { (byte)(topicbytes.Count + payloadArray.Length + id.Length) };
            //using (MemoryStream memoryStream = new MemoryStream())
            //{
            //    memoryStream.WriteByte((3 << 4) | 2 | 1);// 写入消息类型(QoS-1)
            //    memoryStream.Write(bufferLen, 0, (int)bufferLen.Length);// 写入后续报文长度
            //    memoryStream.Write(topicbytes.ToArray(), 0, (int)topicbytes.Count);// 写入Topic字节
            //    memoryStream.Write(id.ToArray(), 0, (int)id.Length);// 写入Package Identifier字节
            //    memoryStream.Write(payloadArray.ToArray(), 0, (int)payloadArray.Length);// 写入消息
            //    byte[] sendArray = memoryStream.ToArray();
            //    socket.Send(sendArray);
            //}
            接收服务器回应的报文 
            //byte[] respBytes = new byte[4];
            //socket.Receive(respBytes, 0, 4, SocketFlags.None);
            //var objSub = respBytes;

            //#endregion 
        }
    }
}

 

讲解不易,分析不易,原创不易,整理不易,伙伴们动动你的金手指,你的支持是我最大的动力。

讲解不易,分析不易,原创不易,整理不易,伙伴们动动你的金手指,你的支持是我最大的动力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/333084.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Python的疫情返乡人员管理系统的设计与实现-计算机毕业设计源码11411

摘 要 近年来&#xff0c;随着经济全球化和社会发展&#xff0c;返乡现象日益普遍&#xff0c;其中部分返乡人员可能存在抗疫突发事件的风险&#xff0c;为此&#xff0c;本文从宏观层面探讨了疫情期间返乡人员的管理方案。首先&#xff0c;建立疫情返乡人员的信息登记系统;其次…

python爬虫代码示例:爬取京东详情页图片

python爬虫代码示例:爬取京东详情页图片 一、Requests安装及示例 爬虫爬取网页内容首先要获取网页的内容&#xff0c;通过requests库进行获取。 GitHub: https://github.com/requests/requests PyPl: https://pypi.python.org/pypi/requests 官方文档:http://wwwpython-requ…

KaiwuDB × 风电企业 | 高性能、低成本、释放数据价值

项目背景 某风电企业是国内一流的大型风电装备公司&#xff0c;其自主研发建设了新一代高标准风电 SCADA 系统&#xff0c;专门用于解决风场风机状态监控、发电监测、综合能源管控、智能化运管等难题。 现公司已承接多个风场的风机管理运营工作&#xff0c;共计包含 96 台风力…

最新企业数据实时同步软件推荐

实时同步软件能够帮助企业快速、准确地共享和更新数据&#xff0c;提高工作效率和决策质量。本文将介绍企业数据实时同步的概念、意义和应用场景&#xff0c;并推荐几款非常优秀的企业数据实时同步软件。 一、数据实时同步的意义 企业数据实时同步是指在企业内部或跨部门之间&…

分布式概念

文章目录 一、CAP定理和BASE定理1.1 CAP定理1.2 CAP取舍1.3 BASE定理 二、分布式事务2.1 柔性事务2.2 两阶段提交协议2.3 三阶段提交协议 三、分布式ID3.1 数据库自增ID3.2 数据库多主模式3.3 号段模式3.4 雪花算法3.5 Leaf3.6 使用Redis生成ID 四、限流算法4.1 固定窗口计数器…

使用的uview 微信高版本 头像昵称填写能力

<template><view><button class"cu-btn block bg-blue margin-tb-sm lg" tap"wxGetUserInfo">一键登录</button><view><!-- 提示窗示例 --><u-popup :show"show" background-color"#fff">&…

【踩坑日志】SpringBoot读取nacos配置信息并提取信息中的IP地址(配置属性解析异常+排错记录)

缘起 &#xff1a;项目需读取nacos中动态的TDengine数据库连接信息并提取IP&#xff0c;一个并不复杂的操作&#xff0c;但作为一个nacos知识浅薄的菜鸡&#xff0c;我愣是捯饬了几个小时……惭愧惭愧…… 异常代码 Data Component public class TaosLink { // Value("…

Docker--harbor私有仓库

目录 一、什么是Harbor&#xff1f; 二、Harbor的特性 三、Harbor的构成 四、部署 五、维护管理Harbor 一、什么是Harbor&#xff1f; Harbor 是 VMware 公司开源的企业级 Docker Registry 项目&#xff0c;其目标是帮助用户迅速搭建一个企业级的 Docker Registry 服务。 …

【昇思技术公开课笔记-大模型】Transformer理论知识

什么是Transformer Transformer是一种神经网络结构&#xff0c;由Vaswani等人在2017年的论文“Attention Is All You Need”中提出&#xff0c;用于处理机器翻译、语言建模和文本生成等自然语言处理任务。 Transformer与传统NLP特征提取类模型的区别主要在以下两点&#xff1…

WebGL开发智慧城市应用

在使用WebGL实现智慧城市应用时&#xff0c;需要考虑一系列的问题&#xff0c;以确保系统的性能、安全性和用户体验。以下是在开发WebGL智慧城市应用时需要注意的问题&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;…

【iOS】——基于Vision Kit框架实现图片文字识别

文章目录 前言一、文本识别的分类二、实现步骤1.导入Vision Kit框架2.创建请求处理器3.在请求处理器中设置文字识别功能4.将图片添加到请求处理器中5.发起文字识别请求6.处理识别结果 三、运行结果测试1.纯英文环境2.中英文混合环境 前言 根据苹果的官方文档&#xff0c;Visio…

MySQL面试题 | 15.精选MySQL面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

YOLOv7全网独家首发:DCNv4更快收敛、更高速度、更高性能,效果秒杀DCNv3、DCNv2等 ,助力检测实现暴力涨点

💡💡💡本文独家改进:DCNv4更快收敛、更高速度、更高性能,完美和YOLOv7结合,助力涨点 DCNv4优势:(1) 去除空间聚合中的softmax归一化,以增强其动态性和表达能力;(2) 优化存储器访问以最小化冗余操作以加速。这些改进显著加快了收敛速度,并大幅提高了处理速度,DCN…

Mybatis 动态SQL条件查询①

需求 : 根据用户的输入情况进行条件查询 新建了一个 userInfo2Mapper 接口,然后写下如下代码,声明 selectByCondition 这个方法 package com.example.mybatisdemo.mapper; import com.example.mybatisdemo.model.UserInfo; import org.apache.ibatis.annotations.*; import j…

LeetCode、2462. 雇佣 K 位工人的总代价【中等,最小堆+双指针】

文章目录 前言LeetCode、2462. 雇佣 K 位工人的总代价【中等&#xff0c;最小堆双指针】题目及类型思路及代码实现 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后…

【安全篇 / FortiGuard】(7.4) ❀ 02. 独立VDOM下的FortiGuard服务升级 ❀ FortiGate 防火墙

【简介】由于业务的需要&#xff0c;创建两个独立VDOM&#xff0c;每个VDOM有各自的宽带&#xff0c;但是FortiGuard服务却无法升级&#xff0c;有什么办法解决吗&#xff1f; VDOM概念 首先我们看看什么是VDOM。 ① VDOM将你的FortiGate划分为多个逻辑设备&#xff0c;并将一个…

用 Python 制作可视化 GUI 界面,一键实现自动分类管理文件!

经常杂乱无章的文件夹会让我们找不到所想要的文件&#xff0c;因此小编特意制作了一个可视化GUI界面&#xff0c;通过输入路径一键点击实现文件分门别类的归档。 不同的文件后缀归类为不同的类别 我们先罗列一下大致有几类文件&#xff0c;根据文件的后缀来设定&#xff0c;大…

Babylonjs inspector工具开启embedMode模式后不显示

项目地址见&#xff1a;https://github.com/tipace/simple-babylonjs 简单的babylonjs example 本身问题挺简单的&#xff0c;仅做一个记录。开始以为是babylon的问题&#xff0c;最后发现是css问题。 因为是做demo&#xff0c;把canas设置为占满全屏&#xff0c;习惯性的写…

Linux--磁盘与文件系统

目录 1.什么是文件系统 2.磁盘 2.1什么时磁盘 2.2磁盘的物理存储结构 2.3磁盘的逻辑抽象结构 3.磁盘文件系统&#xff08;EXT2&#xff09; inode Table(i结点表) Data Block inode Bitmap(inode位图) Block Bitmap(块位图) 在Linux如何删除文件 Group Descriptor Ta…

uniapp web-view组件双向通信

前言 本文主要介绍在uniapp中页面与webview组件内页面的双向通信问题。 准备 uniapp项目 调用webview组件 <web-view src"/hybrid/html/index.html"></web-view> Web项目 项目目录 在uniapp项目根目录下新建hybrid/html目录&#xff0c;web项目文件…