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

news2024/11/19 4:23:10

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 
        }
    }
}

 

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

问题解决:No module named ‘apex‘,apex安装

最近Git了一个别人的程序&#xff0c;跑的过程中遇到了报错&#xff1a; No module named apex 关于Apex&#xff0c;官方介绍是&#xff1a;该资源库包含英伟达维护的实用程序&#xff0c;用于简化 Pytorch 中的混合精度和分布式训练。这里的部分代码最终将被纳入 Pytorch …

WINCC读写EXCEL-VBS

原创 RENHQ WINCC 关于VBS操作EXCEL的文档不管在论坛上还是在网上&#xff0c;相关的脚本已经很多&#xff0c;但是依然有很多人在问这个问题&#xff0c;于是把我以前在论坛上发的一个集合帖子的脚本拿来&#xff0c;重新开个帖子&#xff0c;如果再有人问的话&#xff0c;可…

Debezium发布历史107

原文地址&#xff1a; https://debezium.io/blog/2021/08/02/debezium-1-7-alpha1-released/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. Debezium 1.7.0.Alpha1 Released August 2, 2021 by Jiri Pechanec …

使用xbindkeys设置鼠标侧键

1.安装如下包 sudo apt install xbindkeys xautomation 2.生成配置文件 xbindkeys --defaults > $HOME/.xbindkeysrc 3.确定侧键键号 在终端执行下面的代码&#xff1a; xev | grep button 此时会出现如下窗口&#xff0c;将鼠标指针移动到这个窗口上&#xff1a; 单…

三大3D引擎对比,直观感受AMRT3D渲染能力

作为当前热门的内容呈现形式&#xff0c;3D已经成为了广大开发者、设计师工作里不可或缺的一部分。 用户对于3D的热衷&#xff0c;源于其带来的【沉浸式体验】和【超仿真视觉效果】。借此我们从用户重点关注的四个3D视觉呈现内容&#xff1a; 材质- 呈现多元化内容水效果- 展…

k8s的对外服务ingress

1、service的作用体现在两个方面 &#xff08;1&#xff09;集群内部&#xff1a;不断跟踪pod的变化&#xff0c;更新deployment中的pod对象&#xff0c;基于pod的ip地址不断变化的一种服务发现机制 &#xff08;2&#xff09;集群外部&#xff1a;类似于负载均衡器&#xff…

经典目标检测YOLO系列(二)YOLOV2的复现(2)正样本的匹配、损失函数的实现及模型训练

经典目标检测YOLO系列(二)YOLOV2的复现(2)正样本的匹配、损失函数的实现及模型训练 我们在之前实现YOLOv1的基础上&#xff0c;加入了先验框机制&#xff0c;快速的实现了YOLOv2的网络架构&#xff0c;并且实现了前向推理过程。 经典目标检测YOLO系列(二)YOLOV2的复现(1)总体…

C#开源跨平台的多功能Steam工具箱GitHub加速神器

前言 作为一名程序员你是否会经常会遇到GitHub无法访问(如下无法访问图片)&#xff0c;或者是访问和下载源码时十分缓慢就像乌龟爬行一般。今天分享一款C#开源的、跨平台的多功能Steam工具箱和GitHub加速神器&#xff1a;Watt Toolkit。 项目功能 网络加速、账号切换、库存游…

C++的命名空间域

一、域作用限定符 :: 即是域作用限定符&#xff0c;它的作用是指明一个标识符&#xff08;变量、函数或类&#xff09;来自哪一个作用域范围 二、编译器搜索变量、函数等的原则 1.先搜索局部变量&#xff0c;2.再搜索全局变量&#xff0c;3.最后搜索指定的命名空间域 三、…

uni-app小程序 uni.showToast字数超过两行自动省略显示不全问题

在实际开发过程中如果用户提交某些文件时&#xff0c;如果缺少某些条件我们要提醒用户缺少那些条件才能提交&#xff0c;但是如果我们用uni.showToast提醒的次数超过7个字的时候就会导致文字显示不全&#xff0c;达不到提醒的效果&#xff0c;这种时候我们就需要使用uni.showMo…

C++后端笔记

C后端笔记 资源整理一、高级语言程序设计1.1 进制1.2 程序结构基本知识1.3 数据类型ASCII码命名规则变量间的赋值浮点型变量的作用字符变量常变量 const运算符 二、高级语言程序设计&#xff08;荣&#xff09; 资源整理 C后端开发学习路线及推荐学习时间 C基础知识大全 C那…

聚类模型评估指标

聚类模型评估指标-轮廓系数 计算样本i到同簇其它样本到平均距离ai&#xff0c;ai越小&#xff0c;说明样本i越应该被聚类到该簇&#xff08;将ai称为样本i到簇内不相似度&#xff09;&#xff1b;计算样本i到其它某簇Cj的所有样本的平均距离bij&#xff0c;称为样本i与簇Cj的…

SpiderFlow爬虫平台漏洞利用分析(CVE-2024-0195)

1. 漏洞介绍 SpiderFlow爬虫平台项目中spider-flow-web\src\main\java\org\spiderflow\controller\FunctionController.java文件的FunctionService.saveFunction函数调用了saveFunction函数&#xff0c;该调用了自定义函数validScript&#xff0c;该函数中用户能够控制 functi…

docker:Java通过nginx获取客户端的真实ip地址

问题现象 我们的平台使用Spring Cloud微服务架构&#xff0c;使用Spring Boot构建Java服务&#xff0c;使用google的jib插件打成docker镜像包我们使用docker虚拟化部署&#xff0c;使用docker-compose统一管理所有服务&#xff0c;包括Java服务和nginx等组件我们前后端分离&am…

【JVM】并发的可达性分析详细解释

​ &#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JVM ⛳️ 功不唐捐&#xff0c;玉汝于成 ​ 目录 前言 正文 可达性分析的基本原理&#xff1a; 根集合&#xff08;Root Set&#xff09;&#xff1a; 对象引用关系&#xff1a; 标记…

el-tabs 切换之前进行拦截

tabs标签页在切换的时候进行提示&#xff0c;点击确定执行&#xff0c;点击取消不切换 element文档&#xff1a;before-leave切换标签之前的钩子&#xff0c;若返回 false 或者返回 Promise 且被 reject&#xff0c;则阻止切换。 这里有个坑就是直接return false 不生效&#…

网易云音乐 API

网易云音乐 API 网易云音乐 API灵感来自环境要求安装运行Vercel 部署操作方法 可以在Node.js调用支持 TypeScript使用文档功能特性更新日志单元测试SDK贡献者License 网易云音乐 API 网易云音乐 Node.js API service 灵感来自 disoul/electron-cloud-music darknessomi/musi…

Leetcode2182. 构造限制重复的字符串

Every day a Leetcode 题目来源&#xff1a;2182. 构造限制重复的字符串 解法1&#xff1a;贪心 双指针 我们先用一个长度为 26 的数组 cnt 统计字符串 s 中每个字符出现的次数&#xff0c;然后从大到小枚举字母表的第 i 个字母&#xff0c;每次取出最多 min⁡(cnt[i], re…

微信小程序(七)navigator点击效果

注释很详细&#xff0c;直接上代码 新增内容&#xff1a; 1.默认效果 2.无效果 3.激活效果 源码&#xff1a; index.wxml //如果 <navigator url"/pages/logs/logs">跳转到log页面&#xff08;默认&#xff09; </navigator><navigator url"/pa…

Spring WebSocket实现实时通信的详细教程

简介 WebSocket 是基于TCP/IP协议&#xff0c;独立于HTTP协议的通信协议。WebSocket 连接允许客户端和服务器之间的全双工通信&#xff0c;以便任何一方都可以通过已建立的连接将数据推送到另一方。 我们常用的HTTP是客户端通过「请求-响应」的方式与服务器建立通信的&#x…