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不支持UDPsocket = 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;  // CleanSessionbodyBytes.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个字节:ClientIDloadBytes.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进制就是208while (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 方法1List<byte> headerBytes = new List<byte>();//报文类型byte msgType = 3 << 4;   //  1000 0000 headerBytes.Add((byte)(msgType | 2));   // QoS-0低4位全为1List<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 identifierstatic List<string> topic = new List<string> { "shanghai" };//主题static void Main(string[] args){Console.WriteLine("Hello MQTT!");Connection();//连接  Subscription(topic);//订阅Publish_Qos1();// 发布Qos=1Console.ReadKey();}/// <summary>/// 连接/// </summary>static void Connection(){// MQTT不支持UDPsocket = 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;  // CleanSessionbodyBytes.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个字节:ClientIDloadBytes.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进制就是208while (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 方法1List<byte> headerBytes = new List<byte>();//报文类型byte msgType = 3 << 4;   //  1000 0000 headerBytes.Add((byte)(msgType | 2));   // QoS-0低4位全为1List<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.hqwc.cn/news/415869.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

016-Vue-黑马2023:前后端分离开发(在线接口文档),前端工程化、Element、vue编写一个完成页面、Vue路由、vue打包部署到nginx

第三节 前后端分离开发 1、介绍 开发模式 前后端混合开发&#xff1a;传统开发模式 前后端分离开发&#xff1a;当前最为主流的开发模式 页面原型需求案例&#xff1a;分析出接口文档 离线开发文档示例&#xff1a; 2、YAPI&#xff08;官网已停用&#xff09; 202…

手机改直供电移除电池后使用改充电器供电改充电宝供电反复重启无法启动进入界面后重启黑屏

手机改直供电移除电池后使用改充电器供电改充电宝供电反复重启无法启动进入界面后重启黑屏 手机直供电改造拆电池问题接二连三无法连接电脑手机突然关机&#xff0c;反复重启 供电的解决方案新的希望 安装米家APP总结 手机直供电改造 首先我还是要声明一下&#xff0c;我非专业…

php反序列化之pop链构造(基于重庆橙子科技靶场)

常见魔术方法的触发 __construct() //创建类对象时调用 __destruct() //对象被销毁时触发 __call() //在对象中调用不可访问的方法时触发 __callStatic() //在静态方式中调用不可访问的方法时触发 __get() //调用类中不存在变量时触发&#xff08;找有连续箭头的…

cusor编程IDE + 配置gpt3.5 api

1、购买gpt3.5的api 可以使用这个 https://www.mailline.org?from1726 也可以自己想办法去官网获取 如果是mailline&#xff0c;需要选择第一个&#xff0c;可以先买一个看看&#xff0c;我每次都是买10个&#xff0c;一个0.8元&#xff0c;有5美刀的额度。 下单后获取到ke…

Windows电脑桌面便利贴,桌面便签记事本怎么设置?

在繁忙的工作中&#xff0c;因为有很多重要的事需要等忙完手头上的事后或者明后天才能去做&#xff0c;但又怕忘记&#xff0c;经常会用便利贴先记录下来。对于Windows电脑桌面便利贴&#xff0c;不仅需要随时可以记录图文内容&#xff0c;还要能添加待办清单&#xff0c;设置提…

MyBatis 使用报错:org.xml.sax.SAXParseException 元素内容必须由格式正确的字符数据或标记组成

文章目录 前言问题分析解决方案方案一&#xff1a;使用 CDATA 区块&#xff0c;依然使用 “ > ” 或者 “ < ”方案二&#xff1a;使用转义字符 个人简介 前言 今天在使用 MyBatis 时出现报错&#xff1a; Caused by: org.xml.sax.SAXParseException: 元素内容必须由格式…

Git教程学习:01 Git简介与安装

目录 1 版本控制1.1 什么是版本控制系统&#xff1f;1.2 本地版本控制系统1.3 集中式版本控制系统1.4 分布式版本控制系统 2 Git简史3 Git的安装3.1 在Linux上安装3.2 初次运行Git前的配置 1 版本控制 1.1 什么是版本控制系统&#xff1f; 版本控制系统(Version Control Syst…

Gin 框架之Cookie与Session

文章目录 一、Cookie和Session的由来二、Cookie简介1. 什么是Cookie2. Cookie规范3. 安全性4. Cookie 关键配置 三、Session简介1. 什么是Session2. Session 安全性3. 如何让客户端携带 sess_id 四、使用 Gin 的 Session 插件4.1 介绍4.2 基本使用 五、 session与store5.1 会话…

面对根据角色和单子状态如何有效的进行按钮权限的控制

当阁下看到这个按钮权限控制时&#xff0c;该如何应对 按钮权限是根据工单流程状态进行的&#xff0c;当工单走到某个流程时就显示该表格中对应的状态&#xff0c;初看也就简简单单&#xff0c;仔细一看&#xff0c;逻辑还是很复杂&#xff0c; 首先这里得说一下背景&#xff…

2024--Django平台开发-订单项目管理(十四)

day14 订单管理系统 1.关于登录 1.1 UI美化 页面美化&#xff0c;用BootStrap 自定义BooStrapForm类实现。 class BootStrapForm:exclude_filed_list []def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)# {title:对象,"percent":对象}fo…

ACL【新华三与华为的区别】

【解释】acl简单点解释就是&#xff0c;一套根据需求而设置的规则 【背景】 192.168.1.0/24 网段不允许访问 192.168.2.0/24 网段&#xff0c;要求使用基本 ACL 实现20_1 可以访问 20_6 的 TELNET 服务&#xff0c;但不能访问 FTP 服务 【操作步骤】 {易混点 }&#xff1a;1. …

阿里二面:要保证消息不丢失,又不重复,消息队列怎么选型?

大家好&#xff0c;我是君哥。 在使用消息队列时&#xff0c;有两个经常让我们烦恼的问题&#xff0c;消息丢失和消息重复。那我们在做技术选型时&#xff0c;有没有一个消息队列能解决消息丢失和消息重复这两个问题呢&#xff1f; 消息丢失 如上图&#xff0c;从生产者发送消…