当前位置: 首页 > news >正文

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版&#xff0c;wpf版&#xff0c;为什么要搞个cmd版&#xff0c;因为前面介绍了mqtt的报文结构&#xff0c;重点分析了【连接报文】&#xff0c;【订阅报文】&#xff0c;【发布报文】&#xff0c;这节就要就看看实际报文是怎么组装的&#xff0c;这…...

【高等数学之牛莱公式】

一、深入挖掘定积分 二、变限积分 三、变限积分的"天然"连续性 四、微积分基本定理 五、定积分基本方法 5.1、换元法 5.2、分部积分法 六、定积分经典结论 七、区间再现公式 八、三角函数积分变换公式 九、周期函数积分变换公式 十、分段函数求定积分...

基于HFSS的微带线特性阻抗仿真-与基于FDTD的计算电磁学方法对比(Matlab)

基于HFSS的微带线特性阻抗仿真-与基于FDTD的计算电磁学方法对比&#xff08;Matlab&#xff09; 工程下载&#xff1a; HFSS的微带线特性阻抗仿真工程文件&#xff08;注意版本&#xff1a;HFSS2023R2&#xff09;&#xff1a; https://download.csdn.net/download/weixin_445…...

【SQL】SQL语法小结

相关资料 参考链接1&#xff1a;SQL 语法&#xff08;超级详细&#xff09; 参考链接2&#xff1a;史上超强最常用SQL语句大全 SQL练习网站&#xff1a;CSDN、牛客、LeetCode、LintCode SQL相关视频&#xff1a; 推荐书籍&#xff1a; 文章目录 数据分析对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的基本使用

从这篇博客开始&#xff0c;我们的C部分就进入到了STL&#xff0c;STL的出现可以说是C发展历史上非常关键的一步&#xff0c;自此C和C语言有了较为明显的差别。那么什么是STL呢&#xff1f; 后来不断的演化&#xff0c;发展成了知名的两个版本&#xff0c;一个叫做P.J.版本&am…...

vue 里 props 类型为 Object 时设置 default: () => {} 返回的是 undefined 而不是 {}?

问题 今天遇到个小坑&#xff0c;就是 vue 里使用 props 传参类型为 Object 的时候设置 default: () > {} 报错&#xff0c;具体代码如下 <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圣诞主题绘图 导言 圣诞季节是个充满欢乐和创意的时刻。在这个技术博客中&#xff0c;我们将深入探讨如何使用Python的turtle库创建一…...

[开发语言][c++]:Static关键字和全局变量

Static关键字和全局变量 1. 生命周期、作用域和初始化时机2. 全局变量3. Static 关键字3.1 面向过程3.1.1 静态全局变量3.1.2 静态局部变量&#xff08;单例中会使用&#xff09;3.1.3 静态函数 3.2 面向对象3.2.1 类内静态成员变量3.2.2 类内静态成员函数 Reference 写在前面&…...

计算机组成原理 第一弹

ps&#xff1a;本文章的图片来源都是来自于湖科大教书匠高老师的视频&#xff0c;声明&#xff1a;仅供自己复习&#xff0c;里面加上了自己的理解 这里附上视频链接地址&#xff1a;1-2 计算机的发展_哔哩哔哩_bilibili ​​ 目录 &#x1f680;计算机系统 &#x1f680;计…...

Hadoop基础知识

Hadoop基础知识 1、Hadoop简介 广义上来说&#xff0c;Hadoop通常是指一个更广泛的概念——Hadoop生态圈。狭义上说&#xff0c;Hadoop指Apache这款开源框架&#xff0c;它的核心组件有&#xff1a; HDFS&#xff08;分布式文件系统&#xff09;&#xff1a;解决海量数据存储Y…...

Java进阶之旅第五天

Java进阶之旅第五天 不可变集合 应用场景 1.如果某个数据不能被修改,把它拷贝到不可变集合中是个很好的实践2.当集合对象被不可信的库调用时,不可变形式是安全的3.不可变集合不能修改,只能进行查询 获取方式 在List,Set,Map接口中,都存在静态的of方法,可以获取一个不可变的…...

拓展边界:前端世界的跨域挑战

目录 什么是跨域 概念 同源策略及限制内容 常见跨域场景 如何解决跨域 CORS Nginx代理跨域 Node中间件代理跨域 WebSocket postMessage JSONP 其他 什么是跨域 概念 在此之前&#xff0c;我们了解一下一个域名地址的组成&#xff1a; 跨域指的是在网络安全中&…...

旅游项目day03

1. 前端整合后端发短信接口 2. 注册功能 后端提供注册接口&#xff0c;接受前端传入的参数&#xff0c;创建新的用户对象&#xff0c;保存到数据库。 接口设计&#xff1a; 实现步骤&#xff1a; 手机号码唯一性校验&#xff08;后端一定要再次校验手机号唯一性&#xff09…...

单片机学习记录(一)

简答题 第1章 1.微处理器、微计算机、CPU、单片机、嵌入式处理器他们之间有何区别&#xff1f; 答&#xff1a;微处理器、CPU都是中央处理器的不同称谓&#xff0c;微处理器芯片本身不是计算机&#xff1b; 单片机、微计算机都是一个完整的计算机系统&#xff0c;单片机是集…...

MacBookPro怎么数据恢复? mac电脑数据恢复?

使用电脑的用户都知道&#xff0c;被删除的文件一般都会经过回收站&#xff0c;想要恢复它直接点击“还原”就可以恢复到原始位置。mac电脑同理也是这样&#xff0c;但是“回收站”在mac电脑显示为“废纸篓”。 如果电脑回收站&#xff0c;或者是废纸篓里面的数据被清空了&…...

Python多线程—threading模块

参考&#xff1a;《Python核心编程》 threading 模块的Thread 类是主要的执行对象&#xff0c;而且&#xff0c;使用Thread类可以有很多方法来创建线程&#xff0c;这里介绍以下两种方法&#xff1a; 创建 Thread 实例&#xff0c;传给它一个函数。派生 Thread 的子类&#xf…...

mysql limit

语法 SELECT * FROM TABLE_NAME LIMIT 起始位置&#xff0c;偏移量注&#xff1a; 起始位置从0开始 示例 查询的第1条数据到第100条数据 limit 0,100查询的第101条数据到第200条数据 limit 100,100注意不要用 limit 101,100示例2 limit 语句应放在order by语句后面执行 …...

解决国内Linux服务器无法使用Github的方法

解决思路&#xff1a;修改Host https://www.ipaddress.com/ 利用上面的网站查询github.com和raw.githubusercontent.com的DNS解析的IP地址 最后&#xff0c;修改服务器的/etc/hosts 添加如下两行&#xff1a; 140.82.112.3 github.com 185.199.108.133 raw.githubuserconte…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

Java编程之桥接模式

定义 桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型设计模式&#xff0c;它的核心意图是将抽象部分与实现部分分离&#xff0c;使它们可以独立地变化。这种模式通过组合关系来替代继承关系&#xff0c;从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

c++第七天 继承与派生2

这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分&#xff1a;派生类构造函数与析构函数 当创建一个派生类对象时&#xff0c;基类成员是如何初始化的&#xff1f; 1.当派生类对象创建的时候&#xff0c;基类成员的初始化顺序 …...