动手实现一个较为简单的MQTT服务端和客户端
项目地址: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 计算方式代码如下:
/// <summary>
/// 获取一个长度数据
/// </summary>
/// <returns></returns>
protected virtual Result<int> ReadLength()
{
var result = this.Socket.ReceiveBytes();
if (!result.Success)
{
WriteLog("获取mqtt 长度失败");
return new Result<int>(result);
}
var msgType = result.Data[];
var msgLength = msgType & ;//取低7为的值,因为可变长度有效值只有低7位,第8位用来标识下一个字节是否属于长度字节
var leftBit = ;
while (msgType >> == )//判断最高位是否为1,如果为1则说明后面的1个字节也是属于长度字节
{
result = this.Socket.ReceiveBytes();
if (!result.Success)
{
WriteLog("获取mqtt 长度失败");
return new Result<int>(result);
}
msgType = result.Data[];
msgLength = ((msgType & ) << leftBit) | msgLength;// 因为mqtt 可变长度的字节是低位在前,所以新取到的长度要左移取到的次数*7位在|原来的长度。
leftBit += ;
}
return msgLength;
}
2.2、CONNECT – 连接服务端
协议格式
可看到所需要的参数,于是定义一个连接信息类来保存
/// <summary>
/// mqtt 连接信息。
/// </summary>
public class MQTTConnectInfo
{
/// <summary>
/// 客户端编号
/// </summary>
public virtual string ClientId { get; set; }
/// <summary>
/// 用户名
/// </summary>
public virtual string UserName { get; set; }
/// <summary>
/// 用户密码
/// </summary>
public virtual string Password { get; set; }
/// <summary>
/// 遗嘱保留
/// </summary>
public virtual bool WillRetain { get; set; }
/// <summary>
/// 遗嘱QoS
/// </summary>
public virtual Qos WillQos { get; set; }
/// <summary>
/// 遗嘱标志
/// </summary>
public virtual bool WillFlag { get; set; }
/// <summary>
/// 是否清除对话。
/// </summary>
public virtual bool CleanSession { get; set; }
/// <summary>
/// 保持连接
/// <para>警告:这里的单位是秒</para>
/// </summary>
public virtual ushort KeepAlive { get; set; } = ;
}
然后就是代码按协议格式组装好
代码如下:
/// <summary>
/// 获取包完整的字节
/// </summary>
/// <returns></returns>
public override byte[] ToBytes()
{
var list = new List<byte>();
var mqttBytes = ProtocolName.ToBytes(Encoding.ASCII);//协议名称:固定位MQTT
list.Add((byte)(mqttBytes.Length >> ));
list.Add((byte)(mqttBytes.Length & ));
list.AddRange(mqttBytes); list.Add(Version);//协议版本
list.Add(ConnectFlag);//连接标识 list.Add((byte)(KeepAlive >> ));//心跳值
list.Add((byte)(KeepAlive & )); var clientIdBytes = ClientId.ToBytes(Encoding.ASCII);//客户端编号
list.Add((byte)(clientIdBytes.Length >> ));
list.Add((byte)(clientIdBytes.Length & ));
list.AddRange(clientIdBytes); if (HasUserName)//是否包含用户名
{
var userNameBytes = UserName.ToBytes(Encoding.ASCII);
list.Add((byte)(userNameBytes.Length >> ));
list.Add((byte)(userNameBytes.Length & ));
list.AddRange(userNameBytes);
}
if (HasPassword)//是否包含用户密码
{
var passwordBytes = Password.ToBytes(Encoding.ASCII);
list.Add((byte)(passwordBytes.Length >> ));
list.Add((byte)(passwordBytes.Length & ));
list.AddRange(passwordBytes);
}
Data = list.ToArray();
list.Clear();
return base.ToBytes();
}
连接回复包格式:
描述 |
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 |
代码按协议格式组装如下:
/// <summary>
/// 开始组装包。
/// </summary>
protected override void Packaging()
{
var topicBytes = Topic.ToBytes();//主题数据
Data = new byte[topicBytes.Length + BodyBytes.Length + (QoS > ? : )];
Data[] = (byte)(topicBytes.Length >> );
Data[] = (byte)(topicBytes.Length & );
topicBytes.CopyTo(Data, );
if (QoS > )
{
Data[topicBytes.Length + ] = (byte)(Identifier >> );
Data[topicBytes.Length + ] = (byte)(Identifier & );
}
BodyBytes.CopyTo(Data, Data.Length - BodyBytes.Length);//复制消息内容
topicBytes = null;
}
后面的格式我就不在一一写出来了 ,附上一个mqtt协议文档
这里还有一个非常重要的自己写的一个Socket 辅助类,主要实现高性能的读取和发送消息,已整包的形式,这样避免粘包等问题
/// <summary>
/// Berkeley 套接字 辅助
/// </summary>
public abstract class SocketClient<Package>
where Package : IDataPackage
{
private Socket m_Socket;
private Timer timerHeartbeat;
private System.Net.EndPoint remoteEndPoint;
/// <summary>
/// 客户端唯一标识
/// </summary>
public virtual long Id { get; set; }
/// <summary>
/// Berkeley 套接字。
/// </summary>
public virtual Socket Socket { get => m_Socket; protected set { m_Socket = value; remoteEndPoint = m_Socket.RemoteEndPoint; } } /// <summary>
/// 客户端的远程信息。
/// </summary>
public virtual System.Net.EndPoint RemoteEndPoint { get => remoteEndPoint; }
/// <summary>
/// 心跳线程
/// </summary>
protected virtual Timer TimerHeartbeat { get => timerHeartbeat; } /// <summary>
/// 心跳时间。
/// </summary>
public virtual int KeepAlive { get; set; } = ;
/// <summary>
/// 初始化
/// </summary>
/// <param name="socket"></param>
protected SocketClient(Socket socket)
{ Socket = socket;
}
/// <summary>
/// 初始化
/// </summary>
protected SocketClient()
{ }
/// <summary>
/// 读取一个完整的包。
/// </summary>
/// <returns></returns>
protected abstract DotNet.Result<Package> ReceivePackage();
/// <summary>
/// 开始循环读取消息。
/// </summary>
public virtual void OnReceive()
{
while (!IsClose)
{
try
{
OnHeartbeatTimer();
var bytesResult = ReceivePackage();
if (bytesResult.Success)
{
OnNewDataPackage(bytesResult);
}
else
{
WriteLog($"接收包时错误,错误内容:{bytesResult.Message}");
if (bytesResult.Code == -)
{
this.Close();
}
}
}
catch (Exception ex)
{
WriteErrorLog($"接收包时异常", ex);
}
}
Close();
}
/// <summary>
/// 当接收到
/// </summary>
/// <param name="bytesResult"></param>
protected virtual void OnNewDataPackage(Result<Package> bytesResult)
{
try
{
// 这里使用异步会有一个问题,就是如果一个客户端while(true)在发消息,会导致服务器线程被一个客户端占满而无法处理其他的客户端。
OnHandleDataPackage(bytesResult.Data);
}
catch (Exception ex)
{
WriteErrorLog($"客户端处理包时报错", ex);
}
} #if NET40
/// <summary>
/// 启用异步读取
/// </summary>
/// <returns></returns>
public virtual Task OnReceiveAsync()
{
return Task.Factory.StartNew(OnReceive);
}
#else
/// <summary>
/// 启用异步读取
/// </summary>
/// <returns></returns>
public virtual async Task OnReceiveAsync()
{
await Task.Run(() =>
{
OnReceive();
});
}
#endif private bool m_IsClose;
/// <summary>
/// 是否已经关闭
/// </summary>
public virtual bool IsClose => m_IsClose;
/// <summary>
/// 关闭连接,并退出当前线程
/// </summary>
public virtual void Close(int timeout = )
{
lock (this)
{
if (!IsClose)
{
m_IsClose = true;
WriteLog($"关闭连接"); OnClose();
//真正关闭,避免二次关闭
}
}
Socket?.Close(timeout);
Socket?.Dispose();
timerHeartbeat?.Dispose();
}
/// <summary>
/// 关闭连接并退出。
/// </summary>
protected abstract void OnClose();
/// <summary>
/// 设置心跳计数器
/// </summary>
protected virtual void OnHeartbeatTimer()
{
if (timerHeartbeat == null)
{
timerHeartbeat = new Timer(OnHeartbeatTimerCallback, this, KeepAlive, KeepAlive);
}
else
{
timerHeartbeat.Change(KeepAlive, KeepAlive);
}
}
/// <summary>
/// 心跳实际到达后触发,改方法又心跳计数器执行。
/// </summary>
/// <param name="state"></param>
protected virtual void OnHeartbeatTimerCallback(object state)
{
WriteLog($"客户端{KeepAlive}s未发包,已丢弃");
Close();
}
/// <summary>
/// 写入日志。
/// </summary>
/// <param name="text">日志内容</param>
public virtual void WriteLog(string text)
{
// Log.WriteLog($" 连接{RemoteEndPoint}-{text}");
}
/// <summary>
/// 写入错误信息到日志。
/// </summary>
/// <param name="text">错误信息描述</param>
/// <param name="exception">异常信息</param>
public virtual void WriteErrorLog(string text, Exception exception = null)
{
// Log.WriteErrorLog($" 连接{RemoteEndPoint}-{text}", exception);
}
/// <summary>
/// 写入日志。
/// </summary>
/// <param name="text">日志内容</param>
/// <param name="args"></param>
public virtual void WriteLog(string text, params object[] args)
{
WriteLog(string.Format(text, args));
}
/// <summary>
/// 开始处理接收的包
/// </summary>
/// <param name="dataPackage"></param>
protected abstract void OnHandleDataPackage(Package dataPackage);
/// <summary>
/// 发送数据
/// </summary>
/// <param name="bytes"></param>
public virtual Result SendBytes(byte[] bytes)
{
lock (this)
{
if (!IsClose)
{
try
{
Socket.Send(bytes);
return true;
}
catch (Exception ex)
{
WriteErrorLog($"发送数据{bytes.ToBase64String()}", ex);
if (!Socket.Connected)
{
Close();
}
} }
}
return false;
}
}
其mqtt的Socket实现子类如下:
/// <summary>
/// mqtt 服务器连接过来的客户端。
/// </summary>
public class MQTTSocketClient : DotNet.Net.SocketClient<MQTTDataPackage>
{
/// <summary>
/// 表示mqtt服务器。
/// </summary>
public virtual MQTTServer TcpServer { get; set; }
/// <summary>
/// 获取一个值,该值指示客户端是否发送过了连接协议包。
/// </summary>
public virtual bool IsConnect { get; protected set; }
private readonly List<TopicDataPackage> subscribeTopics = new List<TopicDataPackage>();
/// <summary>
/// 订阅主题。
/// </summary>
public TopicDataPackage[] SubscribeTopics { get => subscribeTopics.ToArray(); }
/// <summary>
/// 当前消息序号
/// </summary>
public virtual ushort Identifier { get; set; }
/// <summary>
/// 客户端连接编号
/// </summary>
public virtual string ClientId { get; set; }
/// <summary>
/// 客户端唯一连接id
/// </summary>
public override long Id
{
get
{
if (long.TryParse(ClientId, out long id))
{
base.Id = id;
}
else
{
base.Id = ClientId.GetHashCode();
}
return base.Id;
}
set
{
ClientId = value.ToString();
}
}
/// <summary>
/// 写日志。
/// </summary>
/// <param name="text"></param>
public override void WriteLog(string text)
{
if (ClientId != null)
{
text = $"客户端编号:{ClientId}:{text}";
}
// base.WriteLog(text);
}
/// <summary>
/// 使用<see cref="Socket"/>客户端初始化。
/// </summary>
/// <param name="socket"></param>
public MQTTSocketClient(Socket socket) : base(socket)
{ }
/// <summary>
/// 关闭服务端连接
/// </summary>
protected override void OnClose()
{
Console.WriteLine($"{ClientId}关闭连接");
} /// <summary>
/// 处理收到的包
/// </summary>
/// <param name="dataPackage"></param>
protected override void OnHandleDataPackage(MQTTDataPackage dataPackage)
{ try
{ WriteLog($"收到{dataPackage.MessageType} 包, QoS level:{dataPackage.QoS}"); if (IsConnect && dataPackage.MessageType != MessageType.Connect)
{
WriteLog($"收到{dataPackage.MessageType} 包, QoS level:{dataPackage.QoS} ,但连接尚未登录,被抛弃");
this.Close(); } switch (dataPackage.MessageType)
{
case MessageType.Connect:
OnConnect(dataPackage);
break;
case MessageType.Subscribe:
OnSubscribe(dataPackage);
break;
case MessageType.PingRequest:
OnPingRequest(dataPackage);
break;
case MessageType.Publish:
OnPublishPackage(dataPackage);
break;
case MessageType.UnSubscribe:
OnUnSubscribe(dataPackage);
break;
case MessageType.Disconnect:
this.Close();
break;
}
}
catch (Exception ex)
{ }
dataPackage = null; }
#if NET40
/// <summary>
/// 当收到发布消息
/// </summary>
/// <param name="dataPackage"></param>
protected virtual Task OnPublishPackage(MQTTDataPackage dataPackage)
{
return Task.Factory.StartNew(() =>
{
#else
/// <summary>
/// 当收到发布消息
/// </summary>
/// <param name="dataPackage"></param>
/// <returns></returns>
protected virtual async Task OnPublishPackage(MQTTDataPackage dataPackage)
{
await Task.Run(() =>
{
#endif
try
{
PublishDataPackage publishDataPackage = new PublishDataPackage(dataPackage);
var result = OnPublish(publishDataPackage);
if (dataPackage.QoS > )
{
var package = new MQTTDataPackage() { MessageType = MessageType.PublishAck, Data = new byte[] { (byte)(publishDataPackage.Identifier >> ), (byte)(publishDataPackage.Identifier & ), } };
if (dataPackage.QoS == )
{
if (!result.Success)
{
package.Data[] = ;
}
//SendPackage(package);
}
}
}
catch (Exception ex)
{ }
});
}
/// <summary>
/// 当客户发布消息。
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
protected virtual Result OnPublish(PublishDataPackage message)
{
WriteLog($"客户端{message.ClientId}发布消息{message.Topic},QoS{message.QoS}。内容:{message.Text}");
try
{
foreach (var client in TcpServer.Clients)
{
foreach (var topic in client.SubscribeTopics)
{
if (MqttTopicFilterComparer.IsMatch(message.Topic, topic.Topic))
{
var temp = message.Clone();
temp.QoS = ;// Math.Min(message.QoS, topic.QoS);//mqtt协议规定,取订阅主题和发送主题中最小的qos值。
client.Publish(temp);
}
}
}
}
catch (Exception ex)
{ }
return true;
}
/// <summary>
/// 发布消息。
/// </summary>
/// <param name="message">要发布的消息。</param>
/// <returns></returns>
public virtual Result Publish(PublishDataPackage message)
{
message.Identifier = ++Identifier;
this.SendPackage(message);//目前不校验,qos 直接发送
return true;
}
/// <summary>
/// 当客户端发送了ping 请求
/// </summary>
/// <param name="dataPackage"></param>
protected virtual void OnPingRequest(MQTTDataPackage dataPackage)
{
var package = new MQTTDataPackage() { MessageType = MessageType.PingResponse };
SendPackage(package);
}
/// <summary>
/// 发生订阅消息
/// </summary>
/// <param name="dataPackage"></param>
private void OnSubscribe(MQTTDataPackage dataPackage)
{ TopicDataPackage topicDataPackage = new TopicDataPackage(dataPackage);
var result = OnSubscribe(topicDataPackage);
var package = new SubscribeAckDataPackage() { Identifier = topicDataPackage.Identifier, Success = result.Success };
if (result.Success)
{
if (!subscribeTopics.Contains(topicDataPackage))
{
subscribeTopics.Add(topicDataPackage);
}
package.ValidQos = Qos.QoS2;//
}
SendPackage(package);
}
/// <summary>
/// 取消订阅消息
/// </summary>
/// <param name="dataPackage"></param>
private void OnUnSubscribe(MQTTDataPackage dataPackage)
{
TopicDataPackage topicDataPackage = new TopicDataPackage(dataPackage);
var result = OnUnSubscribe(topicDataPackage);
if (result.Success)
{
if (subscribeTopics.Contains(topicDataPackage))
{
subscribeTopics.Remove(topicDataPackage);
}
var package = new IdentifierAckDataPackage(MessageType.UnSubscribeAck) { Identifier = topicDataPackage.Identifier };
SendPackage(package);
}
}
/// <summary>
/// 当收到 取消订阅主题消息时。
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
protected virtual Result OnUnSubscribe(TopicDataPackage message)
{
WriteLog($"客户端{message.ClientId} 取消订阅{message.Topic},QoS{message.QoS}");
return true;
}
/// <summary>
/// 当收到订阅主题消息时。
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
protected virtual Result OnSubscribe(TopicDataPackage message)
{
WriteLog($"客户端{message.ClientId}订阅{message.Topic},QoS{message.RequestedQoS}");
return true;
}
/// <summary>
/// 当客户端发送连接请求时。
/// </summary>
/// <param name="dataPackage">连接请求的包</param>
private void OnConnect(MQTTDataPackage dataPackage)
{
ConnectDataPackage connectDataPackage = new ConnectDataPackage(dataPackage);
var result = OnClientConnect(connectDataPackage);
var client = TcpServer.GetClientById(connectDataPackage.ClientId);
if (client.Success)
{
client.Data.WriteLog($"新的客户端连接{this.RemoteEndPoint}上线,旧连接关闭");
client.Data.Close();
}
ClientId = connectDataPackage.ClientId;
this.KeepAlive = Convert.ToInt32(connectDataPackage.KeepAlive * * 1.5);
var package = new ConnectAckDataPackage() { Result = result };
SendPackage(package); }
/// <summary>
/// 发送一个标准的mqtt包到客户端连接。
/// </summary>
/// <param name="package"></param>
public virtual void SendPackage(MQTTDataPackage package)
{
WriteLog($"发送{package.MessageType}包,QOS:{package.QoS}");
this.SendBytes(package.ToBytes());
}
/// <summary>
/// 当客户端连接到服务验证是否可以连接
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
protected virtual Result OnClientConnect(ConnectDataPackage message)
{
WriteLog($"客户端{message.ProtocolName}连接,客户端编号{message.ClientId},用户名:{message.UserName},密码:{message.Password},CeanSession:{message.CeanSession}");
return true;
}
/// <summary>
/// 接收一个完整的包。
/// </summary>
/// <returns></returns>
protected override Result<MQTTDataPackage> ReceivePackage()
{ Result<byte[]> result;
Result<MQTTDataPackage> resultPackage = new Result<MQTTDataPackage>() { Success = false };
MQTTDataPackage package = new MQTTDataPackage() { ClientId = ClientId, RemoteEndPoint = RemoteEndPoint };
result = this.Socket.ReceiveBytes();
if (!result.Success)
{
WriteLog("获取mqtt 头 首字节失败");
this.Close();
return resultPackage;
}
package.Header = result.Data[];
var msgLengthResult = ReadLength();
if (!msgLengthResult.Success)
{
WriteLog(msgLengthResult.Message);
return resultPackage;
}
result = this.Socket.ReceiveBytes(msgLengthResult.Data);
if (!result.Success)
{
WriteLog($"获取数据长度{msgLengthResult.Data}内容失败");
return resultPackage;
}
package.Data = result.Data;
resultPackage.Data = package;
resultPackage.Success = true;
resultPackage.Message = "获取包成功";
return resultPackage;
}
/// <summary>
/// 获取一个长度数据
/// </summary>
/// <returns></returns>
protected virtual Result<int> ReadLength()
{
var result = this.Socket.ReceiveBytes();
if (!result.Success)
{
WriteLog("获取mqtt 长度失败");
return new Result<int>(result);
}
var msgType = result.Data[];
var msgLength = msgType & ;//取低7为的值,因为可变长度有效值只有低7位,第8位用来标识下一个字节是否属于长度字节
var leftBit = ;
while (msgType >> == )//判断最高位是否为1,如果为1则说明后面的1个字节也是属于长度字节
{
result = this.Socket.ReceiveBytes();
if (!result.Success)
{
WriteLog("获取mqtt 长度失败");
return new Result<int>(result);
}
msgType = result.Data[];
msgLength = ((msgType & ) << leftBit) | msgLength;// 因为mqtt 可变长度的字节是低位在前,所以新取到的长度要左移取到的次数*7位在|原来的长度。
leftBit += ;
}
return msgLength;
}
}
动手实现一个较为简单的MQTT服务端和客户端的更多相关文章
- 采用MQTT协议实现android消息推送(2)MQTT服务端与客户端软件对比、android客户端示列表
1.服务端软件对比 https://github.com/mqtt/mqtt.github.io/wiki/servers 名称(点名进官网) 特性 简介 收费 支持的客户端语言 IBM MQ 完整的 ...
- 编写一个简单的TCP服务端和客户端
下面的实验环境是linux系统. 效果如下: 1.启动服务端程序,监听在6666端口上 2.启动客户端,与服务端建立TCP连接 3.建立完TCP连接,在客户端上向服务端发送消息 4.断开连接 实现 ...
- netcore 实现一个简单的Grpc 服务端和客户端
参考资料,和详细背景不做赘述. 首先定义prop 文件 syntax ="proto3"; package RouteGrpc; service HelloWorld{ rpc S ...
- 简单的UDP服务端和客户端示例
UDP的理论不再多说,我这里直接给出一个关于UDP的HelloWorld程序,代码明了,希望对刚入门的学生有所帮助! 当然,实际上,在这块我也刚入门! 首先写服务端代码,服务端邦定本地的IP和端口来监 ...
- 【.NET6】gRPC服务端和客户端开发案例,以及minimal API服务、gRPC服务和传统webapi服务的访问效率大对决
前言:随着.Net6的发布,Minimal API成了当下受人追捧的角儿.而这之前,程序之间通信效率的王者也许可以算得上是gRPC了.那么以下咱们先通过开发一个gRPC服务的教程,然后顺势而为,再接着 ...
- 【.NET+MQTT】.NET6 环境下实现MQTT通信,以及服务端、客户端的双边消息订阅与发布的代码演示
前言: MQTT广泛应用于工业物联网.智能家居.各类智能制造或各类自动化场景等.MQTT是一个基于客户端-服务器的消息发布/订阅传输协议,在很多受限的环境下,比如说机器与机器通信.机器与物联网通信等. ...
- [C语言]一个很实用的服务端和客户端进行TCP通信的实例
本文给出一个很实用的服务端和客户端进行TCP通信的小例子.具体实现上非常简单,只是平时编写类似程序,具体步骤经常忘记,还要总是查,暂且将其记下来,方便以后参考. (1)客户端程序,编写一个文件clie ...
- C# 编写WCF简单的服务端与客户端
http://www.wxzzz.com/1860.html Windows Communication Foundation(WCF)是由微软开发的一系列支持数据通信的应用程序框架,可以翻译为Win ...
- Unity使用C#实现简单Scoket连接及服务端与客户端通讯
简介: 网络编程是个很有意思的事情,偶然翻出来很久之前刚开始看Socket的时候写的一个实例,贴出来吧 Unity中实现简单的Socket连接,c#中提供了丰富的API,直接上代码. 服务端代码: [ ...
随机推荐
- Python实现监测抖音在线时间,实时记录一个人全天的在线情况
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:小dull鸟 今天给大家分享一篇有趣的文章,灵感来自于前几天与室友的 ...
- Netty源码分析之自定义编解码器
在日常的网络开发当中,协议解析都是必须的工作内容,Netty中虽然内置了基于长度.分隔符的编解码器,但在大部分场景中我们使用的都是自定义协议,所以Netty提供了 MessageToByteEnco ...
- 面试题64:求 1 + 2 + ... + n
这道题目条件限制严格,需要发散思维...但是作者是以 C++ 语言特性来做讲解的,对于 Java 狗只能说稍微有点参考意义吧!
- Maven的pom文件依赖提示 ojdbc6 Missing artifact,需要手动下载并导入maven参考
eg: 需要 ojdbc6.jar 的下载地址 https://www.oracle.com/database/technologies/jdbcdriver-ucp-downloads.html c ...
- 尚学堂 217 java中的字节码操作2
package com.bjsxt.test; @Author(name="gaoqi", year=2014) public class Emp { private int em ...
- 几个超级实用但很少人知道的 VS 技巧
大家好,今天分享几个我知道的实用 VS 技巧,而这些技巧我发现很多人都不知道.因为我经常在工作中遇到:我在同事电脑上解决问题,或在会议上演示代码示例时,使用了一些 VS "骚"操作 ...
- Matlab矩阵间快速赋值方法
目前还没见到网上用过这个简单的方式 A= [1 2 3; 4 5 6; 7 8 9] B = zeros(5,5) B(1:3, 2:4) = A %将A赋值到B的第1行到3行,第2列岛4列, ...
- [CEOI1999]Parity Game 题解
P5937 [CEOI1999]Parity Game 洛谷P5937 P5937 [CEOI1999]Parity Game 前言: 个人感觉这道题初看想不到并查集啊!(说实话我题都没读懂,第二遍才 ...
- 你知道Redis可以实现延迟队列吗?
最近,又重新学习了下Redis,深深被Redis的魅力所折服,我才知道Redis不仅能快还能慢(我想也这么优秀o(╥﹏╥)o),简直是个利器呀. 咳咳咳,大家不要误会,本文很正经的啦! 好了,接下来回 ...
- centos7篇---开启防火墙和特定端口
开启防火墙服务 以前为了方便,把防火墙都关闭了,因为现在项目都比较重要,害怕受到攻击,所以为了安全性,现在需要将防火墙开启,接下来介绍一下步骤.1, 首先查看防火墙状态: firewall-cmd - ...