项目地址:https://github.com/hnlyf168/DotNet.Framework

昨天晚上大致测试了下 ,490个客户端(一个收一个发)  平均估计每个每秒60个包  使用mqtt协议  发送一个guid的字符串   服务器转发每秒大约1.2-1.3w 
cpu  占用:25% 一下
内存好像都在50m以下

1、协议简介

  MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。

  具体就不在这里记录了,写这个服务端和客户端也只是为了更加深入的学习mqtt协议。

2、mqtt的几种控制报文类型

名字

报文流动方向

描述

Reserved

0

禁止

保留

CONNECT

1

客户端到服务端

客户端请求连接服务端

CONNACK

2

服务端到客户端

连接报文确认

PUBLISH

3

两个方向都允许

发布消息

PUBACK

4

两个方向都允许

QoS       1消息发布收到确认

PUBREC

5

两个方向都允许

发布收到(保证交付第一步)

PUBREL

6

两个方向都允许

发布释放(保证交付第二步)

PUBCOMP

7

两个方向都允许

QoS       2消息发布完成(保证交互第三步)

SUBSCRIBE

8

客户端到服务端

客户端订阅请求

SUBACK

9

服务端到客户端

订阅请求报文确认

UNSUBSCRIBE

10

客户端到服务端

客户端取消订阅请求

UNSUBACK

11

服务端到客户端

取消订阅报文确认

PINGREQ

12

客户端到服务端

心跳请求

PINGRESP

13

服务端到客户端

心跳响应

DISCONNECT

14

客户端到服务端

客户端断开连接

Reserved

15

禁止

保留

2.1、协议头

    每个MQTT控制报文都包含一个固定报头,

  

      固定报头的格式

Bit

7

6

5

4

3

2

1

0

byte       1

MQTT控制报文的类型

用于指定控制报文类型的标志位

byte       2...

剩余长度

     剩余长度的计算方式:

剩余长度(Remaining      Length)表示当前报文剩余部分的字节数,包括可变报头和负载的数据。剩余长度不包括用于编码剩余长度字段本身的字节数。

剩余长度字段使用一个变长度编码方案,对小于128的值它使用单字节编码。更大的值按下面的方式处理。低7位有效位用于编码数据,最高有效位用于指示是否有更多的字节。因此每个字节可以编码128个数值和一个延续位(continuation     bit)。剩余长度字段最大4个字节

例如,十进制数64会被编码为一个字节,数值是64,十六进制表示为0x40,。十进制数字

321(=65+2*128)被编码为两个字节,最低有效位在前。第一个字节是    65+128=193。注意最高位为1表示后面至少还有一个字节。第二个字节是2。

.net 计算方式代码如下:

  1. /// <summary>
  2. /// 获取一个长度数据
  3. /// </summary>
  4. /// <returns></returns>
  5. protected virtual Result<int> ReadLength()
  6. {
  7. var result = this.Socket.ReceiveBytes();
  8. if (!result.Success)
  9. {
  10. WriteLog("获取mqtt 长度失败");
  11. return new Result<int>(result);
  12. }
  13. var msgType = result.Data[];
  14. var msgLength = msgType & ;//取低7为的值,因为可变长度有效值只有低7位,第8位用来标识下一个字节是否属于长度字节
  15. var leftBit = ;
  16. while (msgType >> == )//判断最高位是否为1,如果为1则说明后面的1个字节也是属于长度字节
  17. {
  18. result = this.Socket.ReceiveBytes();
  19. if (!result.Success)
  20. {
  21. WriteLog("获取mqtt 长度失败");
  22. return new Result<int>(result);
  23. }
  24. msgType = result.Data[];
  25. msgLength = ((msgType & ) << leftBit) | msgLength;// 因为mqtt 可变长度的字节是低位在前,所以新取到的长度要左移取到的次数*7位在|原来的长度。
  26. leftBit += ;
  27. }
  28. return msgLength;
  29. }

2.2、CONNECT   –     连接服务端

协议格式

可看到所需要的参数,于是定义一个连接信息类来保存

  1. /// <summary>
  2. /// mqtt 连接信息。
  3. /// </summary>
  4. public class MQTTConnectInfo
  5. {
  6. /// <summary>
  7. /// 客户端编号
  8. /// </summary>
  9. public virtual string ClientId { get; set; }
  10. /// <summary>
  11. /// 用户名
  12. /// </summary>
  13. public virtual string UserName { get; set; }
  14. /// <summary>
  15. /// 用户密码
  16. /// </summary>
  17. public virtual string Password { get; set; }
  18. /// <summary>
  19. /// 遗嘱保留
  20. /// </summary>
  21. public virtual bool WillRetain { get; set; }
  22. /// <summary>
  23. /// 遗嘱QoS
  24. /// </summary>
  25. public virtual Qos WillQos { get; set; }
  26. /// <summary>
  27. /// 遗嘱标志
  28. /// </summary>
  29. public virtual bool WillFlag { get; set; }
  30. /// <summary>
  31. /// 是否清除对话。
  32. /// </summary>
  33. public virtual bool CleanSession { get; set; }
  34. /// <summary>
  35. /// 保持连接
  36. /// <para>警告:这里的单位是秒</para>
  37. /// </summary>
  38. public virtual ushort KeepAlive { get; set; } = ;
  39. }

然后就是代码按协议格式组装好

代码如下:

  1. /// <summary>
  2. /// 获取包完整的字节
  3. /// </summary>
  4. /// <returns></returns>
  5. public override byte[] ToBytes()
  6. {
  7. var list = new List<byte>();
  8. var mqttBytes = ProtocolName.ToBytes(Encoding.ASCII);//协议名称:固定位MQTT
  9. list.Add((byte)(mqttBytes.Length >> ));
  10. list.Add((byte)(mqttBytes.Length & ));
  11. list.AddRange(mqttBytes);
  12.  
  13. list.Add(Version);//协议版本
  14. list.Add(ConnectFlag);//连接标识
  15.  
  16. list.Add((byte)(KeepAlive >> ));//心跳值
  17. list.Add((byte)(KeepAlive & ));
  18.  
  19. var clientIdBytes = ClientId.ToBytes(Encoding.ASCII);//客户端编号
  20. list.Add((byte)(clientIdBytes.Length >> ));
  21. list.Add((byte)(clientIdBytes.Length & ));
  22. list.AddRange(clientIdBytes);
  23.  
  24. if (HasUserName)//是否包含用户名
  25. {
  26. var userNameBytes = UserName.ToBytes(Encoding.ASCII);
  27. list.Add((byte)(userNameBytes.Length >> ));
  28. list.Add((byte)(userNameBytes.Length & ));
  29. list.AddRange(userNameBytes);
  30. }
  31. if (HasPassword)//是否包含用户密码
  32. {
  33. var passwordBytes = Password.ToBytes(Encoding.ASCII);
  34. list.Add((byte)(passwordBytes.Length >> ));
  35. list.Add((byte)(passwordBytes.Length & ));
  36. list.AddRange(passwordBytes);
  37. }
  38. Data = list.ToArray();
  39. list.Clear();
  40. return base.ToBytes();
  41. }

连接回复包格式:

描述

7

6

5

4

3

2

1

0

连接确认标志

Reserved     保留位

1 SP

byte       1

0

0

0

0

0

0

0

X

连接返回码

byte       2

X

X

X

X

X

X

X

X

描述

7

6

5

4

3

2

1

0

连接确认标志

Reserved      保留位

1 SP

byte       1

0

0

0

0

0

0

0

X

连接返回码

byte       2

X

X

X

X

X

X

X

X

,犹豫代码比较简单这里就不贴了

2.3、PUBLISH      –     发布消息

PUBLISH控制报文是指从客户端向服务端或者服务端向客户端传输一个应用消息

主题长度 16位  2字节

主题内容N

如果QoS大于0 则有一个消息Id   16位  2字节

剩余的N 是消息的主题

描述

7

6

5

4

3

2

1

0

Topic     Name    主题名

byte       1

Length   MSB      (0)

0

0

0

0

0

0

0

0

byte       2

Length   LSB       (3)

0

0

0

0

0

0

1

1

byte       3

‘a’    (0x61)

0

1

1

0

0

0

0

1

byte       4

‘/’     (0x2F)

0

0

1

0

1

1

1

1

byte       5

‘b’    (0x62)

0

1

1

0

0

0

1

0

报文标识符

byte       6

报文标识符  MSB       (0)

0

0

0

0

0

0

0

0

byte       7

报文标识符  LSB       (10)

0

0

0

0

1

0

1

0

代码按协议格式组装如下:

  1. /// <summary>
  2. /// 开始组装包。
  3. /// </summary>
  4. protected override void Packaging()
  5. {
  6. var topicBytes = Topic.ToBytes();//主题数据
  7. Data = new byte[topicBytes.Length + BodyBytes.Length + (QoS > ? : )];
  8. Data[] = (byte)(topicBytes.Length >> );
  9. Data[] = (byte)(topicBytes.Length & );
  10. topicBytes.CopyTo(Data, );
  11. if (QoS > )
  12. {
  13. Data[topicBytes.Length + ] = (byte)(Identifier >> );
  14. Data[topicBytes.Length + ] = (byte)(Identifier & );
  15. }
  16. BodyBytes.CopyTo(Data, Data.Length - BodyBytes.Length);//复制消息内容
  17. topicBytes = null;
  18. }

后面的格式我就不在一一写出来了 ,附上一个mqtt协议文档

MQTT协议中文版

这里还有一个非常重要的自己写的一个Socket 辅助类,主要实现高性能的读取和发送消息,已整包的形式,这样避免粘包等问题

  1. /// <summary>
  2. /// Berkeley 套接字 辅助
  3. /// </summary>
  4. public abstract class SocketClient<Package>
  5. where Package : IDataPackage
  6. {
  7. private Socket m_Socket;
  8. private Timer timerHeartbeat;
  9. private System.Net.EndPoint remoteEndPoint;
  10. /// <summary>
  11. /// 客户端唯一标识
  12. /// </summary>
  13. public virtual long Id { get; set; }
  14. /// <summary>
  15. /// Berkeley 套接字。
  16. /// </summary>
  17. public virtual Socket Socket { get => m_Socket; protected set { m_Socket = value; remoteEndPoint = m_Socket.RemoteEndPoint; } }
  18.  
  19. /// <summary>
  20. /// 客户端的远程信息。
  21. /// </summary>
  22. public virtual System.Net.EndPoint RemoteEndPoint { get => remoteEndPoint; }
  23. /// <summary>
  24. /// 心跳线程
  25. /// </summary>
  26. protected virtual Timer TimerHeartbeat { get => timerHeartbeat; }
  27.  
  28. /// <summary>
  29. /// 心跳时间。
  30. /// </summary>
  31. public virtual int KeepAlive { get; set; } = ;
  32. /// <summary>
  33. /// 初始化
  34. /// </summary>
  35. /// <param name="socket"></param>
  36. protected SocketClient(Socket socket)
  37. {
  38.  
  39. Socket = socket;
  40. }
  41. /// <summary>
  42. /// 初始化
  43. /// </summary>
  44. protected SocketClient()
  45. {
  46.  
  47. }
  48. /// <summary>
  49. /// 读取一个完整的包。
  50. /// </summary>
  51. /// <returns></returns>
  52. protected abstract DotNet.Result<Package> ReceivePackage();
  53. /// <summary>
  54. /// 开始循环读取消息。
  55. /// </summary>
  56. public virtual void OnReceive()
  57. {
  58. while (!IsClose)
  59. {
  60. try
  61. {
  62. OnHeartbeatTimer();
  63. var bytesResult = ReceivePackage();
  64. if (bytesResult.Success)
  65. {
  66. OnNewDataPackage(bytesResult);
  67. }
  68. else
  69. {
  70. WriteLog($"接收包时错误,错误内容:{bytesResult.Message}");
  71. if (bytesResult.Code == -)
  72. {
  73. this.Close();
  74. }
  75. }
  76. }
  77. catch (Exception ex)
  78. {
  79. WriteErrorLog($"接收包时异常", ex);
  80. }
  81. }
  82. Close();
  83. }
  84. /// <summary>
  85. /// 当接收到
  86. /// </summary>
  87. /// <param name="bytesResult"></param>
  88. protected virtual void OnNewDataPackage(Result<Package> bytesResult)
  89. {
  90. try
  91. {
  92. // 这里使用异步会有一个问题,就是如果一个客户端while(true)在发消息,会导致服务器线程被一个客户端占满而无法处理其他的客户端。
  93. OnHandleDataPackage(bytesResult.Data);
  94. }
  95. catch (Exception ex)
  96. {
  97. WriteErrorLog($"客户端处理包时报错", ex);
  98. }
  99. }
  100.  
  101. #if NET40
  102. /// <summary>
  103. /// 启用异步读取
  104. /// </summary>
  105. /// <returns></returns>
  106. public virtual Task OnReceiveAsync()
  107. {
  108. return Task.Factory.StartNew(OnReceive);
  109. }
  110. #else
  111. /// <summary>
  112. /// 启用异步读取
  113. /// </summary>
  114. /// <returns></returns>
  115. public virtual async Task OnReceiveAsync()
  116. {
  117. await Task.Run(() =>
  118. {
  119. OnReceive();
  120. });
  121. }
  122. #endif
  123.  
  124. private bool m_IsClose;
  125. /// <summary>
  126. /// 是否已经关闭
  127. /// </summary>
  128. public virtual bool IsClose => m_IsClose;
  129. /// <summary>
  130. /// 关闭连接,并退出当前线程
  131. /// </summary>
  132. public virtual void Close(int timeout = )
  133. {
  134. lock (this)
  135. {
  136. if (!IsClose)
  137. {
  138. m_IsClose = true;
  139. WriteLog($"关闭连接");
  140.  
  141. OnClose();
  142. //真正关闭,避免二次关闭
  143. }
  144. }
  145. Socket?.Close(timeout);
  146. Socket?.Dispose();
  147. timerHeartbeat?.Dispose();
  148. }
  149. /// <summary>
  150. /// 关闭连接并退出。
  151. /// </summary>
  152. protected abstract void OnClose();
  153. /// <summary>
  154. /// 设置心跳计数器
  155. /// </summary>
  156. protected virtual void OnHeartbeatTimer()
  157. {
  158. if (timerHeartbeat == null)
  159. {
  160. timerHeartbeat = new Timer(OnHeartbeatTimerCallback, this, KeepAlive, KeepAlive);
  161. }
  162. else
  163. {
  164. timerHeartbeat.Change(KeepAlive, KeepAlive);
  165. }
  166. }
  167. /// <summary>
  168. /// 心跳实际到达后触发,改方法又心跳计数器执行。
  169. /// </summary>
  170. /// <param name="state"></param>
  171. protected virtual void OnHeartbeatTimerCallback(object state)
  172. {
  173. WriteLog($"客户端{KeepAlive}s未发包,已丢弃");
  174. Close();
  175. }
  176. /// <summary>
  177. /// 写入日志。
  178. /// </summary>
  179. /// <param name="text">日志内容</param>
  180. public virtual void WriteLog(string text)
  181. {
  182. // Log.WriteLog($" 连接{RemoteEndPoint}-{text}");
  183. }
  184. /// <summary>
  185. /// 写入错误信息到日志。
  186. /// </summary>
  187. /// <param name="text">错误信息描述</param>
  188. /// <param name="exception">异常信息</param>
  189. public virtual void WriteErrorLog(string text, Exception exception = null)
  190. {
  191. // Log.WriteErrorLog($" 连接{RemoteEndPoint}-{text}", exception);
  192. }
  193. /// <summary>
  194. /// 写入日志。
  195. /// </summary>
  196. /// <param name="text">日志内容</param>
  197. /// <param name="args"></param>
  198. public virtual void WriteLog(string text, params object[] args)
  199. {
  200. WriteLog(string.Format(text, args));
  201. }
  202. /// <summary>
  203. /// 开始处理接收的包
  204. /// </summary>
  205. /// <param name="dataPackage"></param>
  206. protected abstract void OnHandleDataPackage(Package dataPackage);
  207. /// <summary>
  208. /// 发送数据
  209. /// </summary>
  210. /// <param name="bytes"></param>
  211. public virtual Result SendBytes(byte[] bytes)
  212. {
  213. lock (this)
  214. {
  215. if (!IsClose)
  216. {
  217. try
  218. {
  219. Socket.Send(bytes);
  220. return true;
  221. }
  222. catch (Exception ex)
  223. {
  224. WriteErrorLog($"发送数据{bytes.ToBase64String()}", ex);
  225. if (!Socket.Connected)
  226. {
  227. Close();
  228. }
  229. }
  230.  
  231. }
  232. }
  233. return false;
  234. }
  235. }

其mqtt的Socket实现子类如下:

  1. /// <summary>
  2. /// mqtt 服务器连接过来的客户端。
  3. /// </summary>
  4. public class MQTTSocketClient : DotNet.Net.SocketClient<MQTTDataPackage>
  5. {
  6. /// <summary>
  7. /// 表示mqtt服务器。
  8. /// </summary>
  9. public virtual MQTTServer TcpServer { get; set; }
  10. /// <summary>
  11. /// 获取一个值,该值指示客户端是否发送过了连接协议包。
  12. /// </summary>
  13. public virtual bool IsConnect { get; protected set; }
  14. private readonly List<TopicDataPackage> subscribeTopics = new List<TopicDataPackage>();
  15. /// <summary>
  16. /// 订阅主题。
  17. /// </summary>
  18. public TopicDataPackage[] SubscribeTopics { get => subscribeTopics.ToArray(); }
  19. /// <summary>
  20. /// 当前消息序号
  21. /// </summary>
  22. public virtual ushort Identifier { get; set; }
  23. /// <summary>
  24. /// 客户端连接编号
  25. /// </summary>
  26. public virtual string ClientId { get; set; }
  27. /// <summary>
  28. /// 客户端唯一连接id
  29. /// </summary>
  30. public override long Id
  31. {
  32. get
  33. {
  34. if (long.TryParse(ClientId, out long id))
  35. {
  36. base.Id = id;
  37. }
  38. else
  39. {
  40. base.Id = ClientId.GetHashCode();
  41. }
  42. return base.Id;
  43. }
  44. set
  45. {
  46. ClientId = value.ToString();
  47. }
  48. }
  49. /// <summary>
  50. /// 写日志。
  51. /// </summary>
  52. /// <param name="text"></param>
  53. public override void WriteLog(string text)
  54. {
  55. if (ClientId != null)
  56. {
  57. text = $"客户端编号:{ClientId}:{text}";
  58. }
  59. // base.WriteLog(text);
  60. }
  61. /// <summary>
  62. /// 使用<see cref="Socket"/>客户端初始化。
  63. /// </summary>
  64. /// <param name="socket"></param>
  65. public MQTTSocketClient(Socket socket) : base(socket)
  66. {
  67.  
  68. }
  69. /// <summary>
  70. /// 关闭服务端连接
  71. /// </summary>
  72. protected override void OnClose()
  73. {
  74. Console.WriteLine($"{ClientId}关闭连接");
  75. }
  76.  
  77. /// <summary>
  78. /// 处理收到的包
  79. /// </summary>
  80. /// <param name="dataPackage"></param>
  81. protected override void OnHandleDataPackage(MQTTDataPackage dataPackage)
  82. {
  83.  
  84. try
  85. {
  86.  
  87. WriteLog($"收到{dataPackage.MessageType} 包, QoS level:{dataPackage.QoS}");
  88.  
  89. if (IsConnect && dataPackage.MessageType != MessageType.Connect)
  90. {
  91. WriteLog($"收到{dataPackage.MessageType} 包, QoS level:{dataPackage.QoS} ,但连接尚未登录,被抛弃");
  92. this.Close();
  93.  
  94. }
  95.  
  96. switch (dataPackage.MessageType)
  97. {
  98. case MessageType.Connect:
  99. OnConnect(dataPackage);
  100. break;
  101. case MessageType.Subscribe:
  102. OnSubscribe(dataPackage);
  103. break;
  104. case MessageType.PingRequest:
  105. OnPingRequest(dataPackage);
  106. break;
  107. case MessageType.Publish:
  108. OnPublishPackage(dataPackage);
  109. break;
  110. case MessageType.UnSubscribe:
  111. OnUnSubscribe(dataPackage);
  112. break;
  113. case MessageType.Disconnect:
  114. this.Close();
  115. break;
  116. }
  117. }
  118. catch (Exception ex)
  119. {
  120.  
  121. }
  122. dataPackage = null;
  123.  
  124. }
  125. #if NET40
  126. /// <summary>
  127. /// 当收到发布消息
  128. /// </summary>
  129. /// <param name="dataPackage"></param>
  130. protected virtual Task OnPublishPackage(MQTTDataPackage dataPackage)
  131. {
  132. return Task.Factory.StartNew(() =>
  133. {
  134. #else
  135. /// <summary>
  136. /// 当收到发布消息
  137. /// </summary>
  138. /// <param name="dataPackage"></param>
  139. /// <returns></returns>
  140. protected virtual async Task OnPublishPackage(MQTTDataPackage dataPackage)
  141. {
  142. await Task.Run(() =>
  143. {
  144. #endif
  145. try
  146. {
  147. PublishDataPackage publishDataPackage = new PublishDataPackage(dataPackage);
  148. var result = OnPublish(publishDataPackage);
  149. if (dataPackage.QoS > )
  150. {
  151. var package = new MQTTDataPackage() { MessageType = MessageType.PublishAck, Data = new byte[] { (byte)(publishDataPackage.Identifier >> ), (byte)(publishDataPackage.Identifier & ), } };
  152. if (dataPackage.QoS == )
  153. {
  154. if (!result.Success)
  155. {
  156. package.Data[] = ;
  157. }
  158. //SendPackage(package);
  159. }
  160. }
  161. }
  162. catch (Exception ex)
  163. {
  164.  
  165. }
  166. });
  167. }
  168. /// <summary>
  169. /// 当客户发布消息。
  170. /// </summary>
  171. /// <param name="message"></param>
  172. /// <returns></returns>
  173. protected virtual Result OnPublish(PublishDataPackage message)
  174. {
  175. WriteLog($"客户端{message.ClientId}发布消息{message.Topic},QoS{message.QoS}。内容:{message.Text}");
  176. try
  177. {
  178. foreach (var client in TcpServer.Clients)
  179. {
  180. foreach (var topic in client.SubscribeTopics)
  181. {
  182. if (MqttTopicFilterComparer.IsMatch(message.Topic, topic.Topic))
  183. {
  184. var temp = message.Clone();
  185. temp.QoS = ;// Math.Min(message.QoS, topic.QoS);//mqtt协议规定,取订阅主题和发送主题中最小的qos值。
  186. client.Publish(temp);
  187. }
  188. }
  189. }
  190. }
  191. catch (Exception ex)
  192. {
  193.  
  194. }
  195. return true;
  196. }
  197. /// <summary>
  198. /// 发布消息。
  199. /// </summary>
  200. /// <param name="message">要发布的消息。</param>
  201. /// <returns></returns>
  202. public virtual Result Publish(PublishDataPackage message)
  203. {
  204. message.Identifier = ++Identifier;
  205. this.SendPackage(message);//目前不校验,qos 直接发送
  206. return true;
  207. }
  208. /// <summary>
  209. /// 当客户端发送了ping 请求
  210. /// </summary>
  211. /// <param name="dataPackage"></param>
  212. protected virtual void OnPingRequest(MQTTDataPackage dataPackage)
  213. {
  214. var package = new MQTTDataPackage() { MessageType = MessageType.PingResponse };
  215. SendPackage(package);
  216. }
  217. /// <summary>
  218. /// 发生订阅消息
  219. /// </summary>
  220. /// <param name="dataPackage"></param>
  221. private void OnSubscribe(MQTTDataPackage dataPackage)
  222. {
  223.  
  224. TopicDataPackage topicDataPackage = new TopicDataPackage(dataPackage);
  225. var result = OnSubscribe(topicDataPackage);
  226. var package = new SubscribeAckDataPackage() { Identifier = topicDataPackage.Identifier, Success = result.Success };
  227. if (result.Success)
  228. {
  229. if (!subscribeTopics.Contains(topicDataPackage))
  230. {
  231. subscribeTopics.Add(topicDataPackage);
  232. }
  233. package.ValidQos = Qos.QoS2;//
  234. }
  235. SendPackage(package);
  236. }
  237. /// <summary>
  238. /// 取消订阅消息
  239. /// </summary>
  240. /// <param name="dataPackage"></param>
  241. private void OnUnSubscribe(MQTTDataPackage dataPackage)
  242. {
  243. TopicDataPackage topicDataPackage = new TopicDataPackage(dataPackage);
  244. var result = OnUnSubscribe(topicDataPackage);
  245. if (result.Success)
  246. {
  247. if (subscribeTopics.Contains(topicDataPackage))
  248. {
  249. subscribeTopics.Remove(topicDataPackage);
  250. }
  251. var package = new IdentifierAckDataPackage(MessageType.UnSubscribeAck) { Identifier = topicDataPackage.Identifier };
  252. SendPackage(package);
  253. }
  254. }
  255. /// <summary>
  256. /// 当收到 取消订阅主题消息时。
  257. /// </summary>
  258. /// <param name="message"></param>
  259. /// <returns></returns>
  260. protected virtual Result OnUnSubscribe(TopicDataPackage message)
  261. {
  262. WriteLog($"客户端{message.ClientId} 取消订阅{message.Topic},QoS{message.QoS}");
  263. return true;
  264. }
  265. /// <summary>
  266. /// 当收到订阅主题消息时。
  267. /// </summary>
  268. /// <param name="message"></param>
  269. /// <returns></returns>
  270. protected virtual Result OnSubscribe(TopicDataPackage message)
  271. {
  272. WriteLog($"客户端{message.ClientId}订阅{message.Topic},QoS{message.RequestedQoS}");
  273. return true;
  274. }
  275. /// <summary>
  276. /// 当客户端发送连接请求时。
  277. /// </summary>
  278. /// <param name="dataPackage">连接请求的包</param>
  279. private void OnConnect(MQTTDataPackage dataPackage)
  280. {
  281. ConnectDataPackage connectDataPackage = new ConnectDataPackage(dataPackage);
  282. var result = OnClientConnect(connectDataPackage);
  283. var client = TcpServer.GetClientById(connectDataPackage.ClientId);
  284. if (client.Success)
  285. {
  286. client.Data.WriteLog($"新的客户端连接{this.RemoteEndPoint}上线,旧连接关闭");
  287. client.Data.Close();
  288. }
  289. ClientId = connectDataPackage.ClientId;
  290. this.KeepAlive = Convert.ToInt32(connectDataPackage.KeepAlive * * 1.5);
  291. var package = new ConnectAckDataPackage() { Result = result };
  292. SendPackage(package);
  293.  
  294. }
  295. /// <summary>
  296. /// 发送一个标准的mqtt包到客户端连接。
  297. /// </summary>
  298. /// <param name="package"></param>
  299. public virtual void SendPackage(MQTTDataPackage package)
  300. {
  301. WriteLog($"发送{package.MessageType}包,QOS:{package.QoS}");
  302. this.SendBytes(package.ToBytes());
  303. }
  304. /// <summary>
  305. /// 当客户端连接到服务验证是否可以连接
  306. /// </summary>
  307. /// <param name="message"></param>
  308. /// <returns></returns>
  309. protected virtual Result OnClientConnect(ConnectDataPackage message)
  310. {
  311. WriteLog($"客户端{message.ProtocolName}连接,客户端编号{message.ClientId},用户名:{message.UserName},密码:{message.Password},CeanSession:{message.CeanSession}");
  312. return true;
  313. }
  314. /// <summary>
  315. /// 接收一个完整的包。
  316. /// </summary>
  317. /// <returns></returns>
  318. protected override Result<MQTTDataPackage> ReceivePackage()
  319. {
  320.  
  321. Result<byte[]> result;
  322. Result<MQTTDataPackage> resultPackage = new Result<MQTTDataPackage>() { Success = false };
  323. MQTTDataPackage package = new MQTTDataPackage() { ClientId = ClientId, RemoteEndPoint = RemoteEndPoint };
  324. result = this.Socket.ReceiveBytes();
  325. if (!result.Success)
  326. {
  327. WriteLog("获取mqtt 头 首字节失败");
  328. this.Close();
  329. return resultPackage;
  330. }
  331. package.Header = result.Data[];
  332. var msgLengthResult = ReadLength();
  333. if (!msgLengthResult.Success)
  334. {
  335. WriteLog(msgLengthResult.Message);
  336. return resultPackage;
  337. }
  338. result = this.Socket.ReceiveBytes(msgLengthResult.Data);
  339. if (!result.Success)
  340. {
  341. WriteLog($"获取数据长度{msgLengthResult.Data}内容失败");
  342. return resultPackage;
  343. }
  344. package.Data = result.Data;
  345. resultPackage.Data = package;
  346. resultPackage.Success = true;
  347. resultPackage.Message = "获取包成功";
  348. return resultPackage;
  349. }
  350. /// <summary>
  351. /// 获取一个长度数据
  352. /// </summary>
  353. /// <returns></returns>
  354. protected virtual Result<int> ReadLength()
  355. {
  356. var result = this.Socket.ReceiveBytes();
  357. if (!result.Success)
  358. {
  359. WriteLog("获取mqtt 长度失败");
  360. return new Result<int>(result);
  361. }
  362. var msgType = result.Data[];
  363. var msgLength = msgType & ;//取低7为的值,因为可变长度有效值只有低7位,第8位用来标识下一个字节是否属于长度字节
  364. var leftBit = ;
  365. while (msgType >> == )//判断最高位是否为1,如果为1则说明后面的1个字节也是属于长度字节
  366. {
  367. result = this.Socket.ReceiveBytes();
  368. if (!result.Success)
  369. {
  370. WriteLog("获取mqtt 长度失败");
  371. return new Result<int>(result);
  372. }
  373. msgType = result.Data[];
  374. msgLength = ((msgType & ) << leftBit) | msgLength;// 因为mqtt 可变长度的字节是低位在前,所以新取到的长度要左移取到的次数*7位在|原来的长度。
  375. leftBit += ;
  376. }
  377. return msgLength;
  378. }
  379. }

动手实现一个较为简单的MQTT服务端和客户端的更多相关文章

  1. 采用MQTT协议实现android消息推送(2)MQTT服务端与客户端软件对比、android客户端示列表

    1.服务端软件对比 https://github.com/mqtt/mqtt.github.io/wiki/servers 名称(点名进官网) 特性 简介 收费 支持的客户端语言 IBM MQ 完整的 ...

  2. 编写一个简单的TCP服务端和客户端

    下面的实验环境是linux系统. 效果如下: 1.启动服务端程序,监听在6666端口上  2.启动客户端,与服务端建立TCP连接  3.建立完TCP连接,在客户端上向服务端发送消息 4.断开连接 实现 ...

  3. netcore 实现一个简单的Grpc 服务端和客户端

    参考资料,和详细背景不做赘述. 首先定义prop 文件 syntax ="proto3"; package RouteGrpc; service HelloWorld{ rpc S ...

  4. 简单的UDP服务端和客户端示例

    UDP的理论不再多说,我这里直接给出一个关于UDP的HelloWorld程序,代码明了,希望对刚入门的学生有所帮助! 当然,实际上,在这块我也刚入门! 首先写服务端代码,服务端邦定本地的IP和端口来监 ...

  5. 【.NET6】gRPC服务端和客户端开发案例,以及minimal API服务、gRPC服务和传统webapi服务的访问效率大对决

    前言:随着.Net6的发布,Minimal API成了当下受人追捧的角儿.而这之前,程序之间通信效率的王者也许可以算得上是gRPC了.那么以下咱们先通过开发一个gRPC服务的教程,然后顺势而为,再接着 ...

  6. 【.NET+MQTT】.NET6 环境下实现MQTT通信,以及服务端、客户端的双边消息订阅与发布的代码演示

    前言: MQTT广泛应用于工业物联网.智能家居.各类智能制造或各类自动化场景等.MQTT是一个基于客户端-服务器的消息发布/订阅传输协议,在很多受限的环境下,比如说机器与机器通信.机器与物联网通信等. ...

  7. [C语言]一个很实用的服务端和客户端进行TCP通信的实例

    本文给出一个很实用的服务端和客户端进行TCP通信的小例子.具体实现上非常简单,只是平时编写类似程序,具体步骤经常忘记,还要总是查,暂且将其记下来,方便以后参考. (1)客户端程序,编写一个文件clie ...

  8. C# 编写WCF简单的服务端与客户端

    http://www.wxzzz.com/1860.html Windows Communication Foundation(WCF)是由微软开发的一系列支持数据通信的应用程序框架,可以翻译为Win ...

  9. Unity使用C#实现简单Scoket连接及服务端与客户端通讯

    简介: 网络编程是个很有意思的事情,偶然翻出来很久之前刚开始看Socket的时候写的一个实例,贴出来吧 Unity中实现简单的Socket连接,c#中提供了丰富的API,直接上代码. 服务端代码: [ ...

随机推荐

  1. Docker精华 ,超全文档!

    我们的口号是:再小的帆也能远航,人生不设限!!    学习规划:继续上篇 <Docker入门>https://www.cnblogs.com/dk1024/p/13121389.html  ...

  2. 5、struct2的支持ModelDriver获得参数

    1.在以前的代码中我们我们需要获得jsp传递过来的参数我们采用下面的方式获得 package com.weiyuan.test; /** * struct2的action可以不继承任何框架的接口 * ...

  3. python用pandas遍历csv文件

    import pandas as pd df = pd.read_csv('a.csv') for index, row in df.iterrows(): x, y = row['X'], row[ ...

  4. 客官,来看看AspNetCore的身份验证吧

    开篇 这段时间潜水了太久,终于有时间可以更新一篇文章了. 通过本篇文章您将Get: Http的一些身份验证概念 在AspNetCore中实现身份验证方案 JWT等概念的基础知识 使用Bearer To ...

  5. 深入理解JavaScript系列(2):揭秘命名函数表达式(转)

    前言 网上还没用发现有人对命名函数表达式进去重复深入的讨论,正因为如此,网上出现了各种各样的误解,本文将从原理和实践两个方面来探讨JavaScript关于命名函数表达式的优缺点. 简 单的说,命名函数 ...

  6. navicat连接vagrant中的数据库

  7. 【FastDFS】如何打造一款高可用的分布式文件系统?这次我明白了!!

    写在前面 前面我们学习了如何基于两台服务器搭建FastDFS环境,而往往在生产环境中,需要FastDFS做到高可用,那如何基于FastDFS打造一款高可用的分布式文件系统呢?别急,今天,我们就一起来基 ...

  8. 求助:Runtime exception at 0x004000bc: invalid integer input (syscall 5)

    代码 .data S17: .asciiz "the bigger one is:" .text move $fp $sp j main max: lw $t8 ($sp) sub ...

  9. AspNetCore&Coding持续集成

    对于现有很多持续集成工具来讲,功能越来越高级,使用了 Coding 有大半年时间,越发觉好用,特别是没钱续费服务器时,找到了新的羊毛. 一.众多持续集成工具 现在可用的持续集成工具繁多,各大云服务商都 ...

  10. 一文说清 KubeSphere 容器平台的价值

    KubeSphere 作为云原生家族 后起之秀,开源近两年的时间以来收获了诸多用户与开发者的认可.本文通过大白话从零诠释 KubeSphere 的定位与价值,以及不同团队为什么会选择 KubeSphe ...