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…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果 5.2 IPsec隧道模式(Tunne…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...