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 }}
}
讲解不易,分析不易,原创不易,整理不易,伙伴们动动你的金手指,你的支持是我最大的动力。
讲解不易,分析不易,原创不易,整理不易,伙伴们动动你的金手指,你的支持是我最大的动力。

相关文章:
C#MQTT编程08--MQTT服务器和客户端(cmd版)
1、前言 前面完成了winform版,wpf版,为什么要搞个cmd版,因为前面介绍了mqtt的报文结构,重点分析了【连接报文】,【订阅报文】,【发布报文】,这节就要就看看实际报文是怎么组装的,这…...
【高等数学之牛莱公式】
一、深入挖掘定积分 二、变限积分 三、变限积分的"天然"连续性 四、微积分基本定理 五、定积分基本方法 5.1、换元法 5.2、分部积分法 六、定积分经典结论 七、区间再现公式 八、三角函数积分变换公式 九、周期函数积分变换公式 十、分段函数求定积分...
基于HFSS的微带线特性阻抗仿真-与基于FDTD的计算电磁学方法对比(Matlab)
基于HFSS的微带线特性阻抗仿真-与基于FDTD的计算电磁学方法对比(Matlab) 工程下载: HFSS的微带线特性阻抗仿真工程文件(注意版本:HFSS2023R2): https://download.csdn.net/download/weixin_445…...
【SQL】SQL语法小结
相关资料 参考链接1:SQL 语法(超级详细) 参考链接2:史上超强最常用SQL语句大全 SQL练习网站:CSDN、牛客、LeetCode、LintCode SQL相关视频: 推荐书籍: 文章目录 数据分析对SQL的要求SQL语法简介…...
Open CASCADE学习|显示模型
目录 1、编写代码 Viewer.h Viewer.cpp ViewerInteractor.h ViewerInteractor.cpp helloworld.cpp 2、配置 3、编译运行 1、编写代码 Viewer.h #pragma once #ifdef _WIN32 #include <Windows.h> #endif // Local includes #include "ViewerInteract…...
【C++】string的基本使用
从这篇博客开始,我们的C部分就进入到了STL,STL的出现可以说是C发展历史上非常关键的一步,自此C和C语言有了较为明显的差别。那么什么是STL呢? 后来不断的演化,发展成了知名的两个版本,一个叫做P.J.版本&am…...
vue 里 props 类型为 Object 时设置 default: () => {} 返回的是 undefined 而不是 {}?
问题 今天遇到个小坑,就是 vue 里使用 props 传参类型为 Object 的时候设置 default: () > {} 报错,具体代码如下 <template><div class"pre-archive-info"><template v-if"infoData.kaimo ! null">{{ infoD…...
04 SpringMVC响应数据之页面跳转控制+返回JSON数据+返回静态资源
1. handler方法分析 /*** TODO: 一个controller的方法是控制层的一个处理器,我们称为handler* TODO: handler需要使用RequestMapping/GetMapping系列,声明路径,在HandlerMapping中注册,供DS查找!* TODO: handler作用总结:* 1.接收请求参数(param,json,pathVariable,共享…...
Python圣诞主题绘图:用turtle库打造冬日奇妙画面【第31篇—python:圣诞节】
文章目录 Python圣诞主题绘图导言代码结构概览详细解析drawlight函数tree函数xzs函数drawsnow函数五角星的绘制 完整代码代码解析总结 Python圣诞主题绘图 导言 圣诞季节是个充满欢乐和创意的时刻。在这个技术博客中,我们将深入探讨如何使用Python的turtle库创建一…...
[开发语言][c++]:Static关键字和全局变量
Static关键字和全局变量 1. 生命周期、作用域和初始化时机2. 全局变量3. Static 关键字3.1 面向过程3.1.1 静态全局变量3.1.2 静态局部变量(单例中会使用)3.1.3 静态函数 3.2 面向对象3.2.1 类内静态成员变量3.2.2 类内静态成员函数 Reference 写在前面&…...
计算机组成原理 第一弹
ps:本文章的图片来源都是来自于湖科大教书匠高老师的视频,声明:仅供自己复习,里面加上了自己的理解 这里附上视频链接地址:1-2 计算机的发展_哔哩哔哩_bilibili 目录 🚀计算机系统 🚀计…...
Hadoop基础知识
Hadoop基础知识 1、Hadoop简介 广义上来说,Hadoop通常是指一个更广泛的概念——Hadoop生态圈。狭义上说,Hadoop指Apache这款开源框架,它的核心组件有: HDFS(分布式文件系统):解决海量数据存储Y…...
Java进阶之旅第五天
Java进阶之旅第五天 不可变集合 应用场景 1.如果某个数据不能被修改,把它拷贝到不可变集合中是个很好的实践2.当集合对象被不可信的库调用时,不可变形式是安全的3.不可变集合不能修改,只能进行查询 获取方式 在List,Set,Map接口中,都存在静态的of方法,可以获取一个不可变的…...
拓展边界:前端世界的跨域挑战
目录 什么是跨域 概念 同源策略及限制内容 常见跨域场景 如何解决跨域 CORS Nginx代理跨域 Node中间件代理跨域 WebSocket postMessage JSONP 其他 什么是跨域 概念 在此之前,我们了解一下一个域名地址的组成: 跨域指的是在网络安全中&…...
旅游项目day03
1. 前端整合后端发短信接口 2. 注册功能 后端提供注册接口,接受前端传入的参数,创建新的用户对象,保存到数据库。 接口设计: 实现步骤: 手机号码唯一性校验(后端一定要再次校验手机号唯一性)…...
单片机学习记录(一)
简答题 第1章 1.微处理器、微计算机、CPU、单片机、嵌入式处理器他们之间有何区别? 答:微处理器、CPU都是中央处理器的不同称谓,微处理器芯片本身不是计算机; 单片机、微计算机都是一个完整的计算机系统,单片机是集…...
MacBookPro怎么数据恢复? mac电脑数据恢复?
使用电脑的用户都知道,被删除的文件一般都会经过回收站,想要恢复它直接点击“还原”就可以恢复到原始位置。mac电脑同理也是这样,但是“回收站”在mac电脑显示为“废纸篓”。 如果电脑回收站,或者是废纸篓里面的数据被清空了&…...
Python多线程—threading模块
参考:《Python核心编程》 threading 模块的Thread 类是主要的执行对象,而且,使用Thread类可以有很多方法来创建线程,这里介绍以下两种方法: 创建 Thread 实例,传给它一个函数。派生 Thread 的子类…...
mysql limit
语法 SELECT * FROM TABLE_NAME LIMIT 起始位置,偏移量注: 起始位置从0开始 示例 查询的第1条数据到第100条数据 limit 0,100查询的第101条数据到第200条数据 limit 100,100注意不要用 limit 101,100示例2 limit 语句应放在order by语句后面执行 …...
解决国内Linux服务器无法使用Github的方法
解决思路:修改Host https://www.ipaddress.com/ 利用上面的网站查询github.com和raw.githubusercontent.com的DNS解析的IP地址 最后,修改服务器的/etc/hosts 添加如下两行: 140.82.112.3 github.com 185.199.108.133 raw.githubuserconte…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
