c# 版
mqtt 3.1.1 client
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html
上面為 3.1.1 協議報文
一個外鏈:
MQTT 3.1.1,值得升級的6個新特性
http://www.blogjava.net/yongboy/archive/2014/12/16/421460.html
socket 客服端,這裡我用了 SocketAsyncEventArgs 來實現,
這裡感覺要優化的地方還是挺多的,,希望大神指出不足之處
核心實現:
MqttConnection
using System; using System.IO; using System.Collections.Generic; using System.Net.Sockets; using System.Diagnostics; using nMqtt.Messages; namespace nMqtt { internal sealed class MqttConnection { Socket socket; int m_nConnection = 1; public Action<byte[]> Recv; /// <summary> /// Socket非同步物件池 /// </summary> SocketAsyncEventArgsPool socketAsynPool; public MqttConnection() { const int receiveBufferSize = 4096; socketAsynPool = new SocketAsyncEventArgsPool(m_nConnection); var bufferManager = new BufferManager(receiveBufferSize * m_nConnection, receiveBufferSize); bufferManager.InitBuffer(); //按照連線數建立讀寫物件 for (int i = 0; i < m_nConnection; i++) { var args = new SocketAsyncEventArgs(); args.Completed += IO_Completed; args.UserToken = new RecvToken(); bufferManager.SetBuffer(args); socketAsynPool.Push(args); } } public void Connect(string server, int port = 1883) { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(server, port); socket.ReceiveAsync(socketAsynPool.Pop()); } void IO_Completed(object sender, SocketAsyncEventArgs e) { switch (e.LastOperation) { case SocketAsyncOperation.Receive: ProcessRecv(e); break; default: throw new ArgumentException("nError in I/O Completed"); } } void ProcessRecv(SocketAsyncEventArgs e) { Debug.WriteLine("----------------------- ProcessRecv:{0}", e.BytesTransferred); var token = e.UserToken as RecvToken; if (e.SocketError == SocketError.Success && e.BytesTransferred > 0) { var buffer = new byte[e.BytesTransferred]; Buffer.BlockCopy(e.Buffer, e.Offset, buffer, 0, buffer.Length); token.Message.AddRange(buffer); if (token.IsReadComplete) { if (Recv != null) Recv(token.Message.ToArray()); token.Reset(); } socket.ReceiveAsync(e); } else { token.Reset(); //socketAsynPool.Push(e); //socket.ReceiveAsync(e); } } public void SendMessage(MqttMessage message) { Debug.WriteLine("onSend:{0}", message.FixedHeader.MessageType); using (var stream = new MemoryStream()) { message.Encode(stream); stream.Seek(0, SeekOrigin.Begin); Send(stream.ToArray()); } } void Send(byte[] message) { try { socket.Send(message, SocketFlags.None); } catch (Exception ex) { Debug.WriteLine(ex); } } class RecvToken { /// <summary> /// The read buffer size from the network /// </summary> public const int BufferSize = 4096; /// <summary> /// The total bytes expected to be read from from the header of content /// </summary> public int Count { get { if (Message != null && Message.Count >= 2) { int offset = 1; byte encodedByte; var multiplier = 1; var remainingLength = 0; do { encodedByte = Message[offset]; remainingLength += encodedByte & 0x7f * multiplier; multiplier *= 0x80; } while ((++offset <=4) && (encodedByte & 0x80) != 0); return remainingLength + offset; } return 0; } } /// <summary> /// The bytes associated with the message being read. /// </summary> public List<byte> Message { get; set; } = new List<byte>(); /// <summary> /// The buffer the last stream read wrote into. /// </summary> public byte[] Buffer; /// <summary> /// A boolean that indicates whether the message read is complete /// </summary> public bool IsReadComplete { get { return Message.Count >= Count; } } public void Reset() { Buffer = new byte[BufferSize]; Message.Clear(); } } } public enum ConnectionState { /// <summary> /// The MQTT Connection is in the process of disconnecting from the broker. /// </summary> Disconnecting, /// <summary> /// The MQTT Connection is not currently connected to any broker. /// </summary> Disconnected, /// <summary> /// The MQTT Connection is in the process of connecting to the broker. /// </summary> Connecting, /// <summary> /// The MQTT Connection is currently connected to the broker. /// </summary> Connected, /// <summary> /// The MQTT Connection is faulted and no longer communicating with the broker. /// </summary> Faulted } internal sealed class SocketAsyncEventArgsPool { private readonly Stack<SocketAsyncEventArgs> pool; internal SocketAsyncEventArgsPool(int capacity) { pool = new Stack<SocketAsyncEventArgs>(capacity); } internal void Push(SocketAsyncEventArgs item) { if (item == null) throw new ArgumentNullException(nameof(item)); lock (pool) { pool.Push(item); } } internal SocketAsyncEventArgs Pop() { lock (pool) return pool.Pop(); } internal int Count { get { return pool.Count; } } } internal sealed class BufferManager { readonly int m_numBytes; byte[] m_buffer; readonly Stack<int> m_freeIndexPool; int m_currentIndex; readonly int m_bufferSize; public BufferManager(int totalBytes, int bufferSize) { m_numBytes = totalBytes; m_currentIndex = 0; m_bufferSize = bufferSize; m_freeIndexPool = new Stack<int>(); } /// <summary> /// Allocates buffer space used by the buffer pool /// </summary> public void InitBuffer() { // create one big large buffer and divide that out to each SocketAsyncEventArg object m_buffer = new byte[m_numBytes]; } /// <summary> /// Assigns a buffer from the buffer pool to the specified SocketAsyncEventArgs object /// </summary> /// <returns>true if the buffer was successfully set, else false</returns> public bool SetBuffer(SocketAsyncEventArgs args) { if (m_freeIndexPool.Count > 0) { args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize); } else { if ((m_numBytes - m_bufferSize) < m_currentIndex) return false; args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize); m_currentIndex += m_bufferSize; } return true; } /// <summary> /// Removes the buffer from a SocketAsyncEventArg object. This frees the buffer back to the /// buffer pool /// </summary> public void FreeBuffer(SocketAsyncEventArgs args) { m_freeIndexPool.Push(args.Offset); args.SetBuffer(null, 0, 0); } } }
其中 RecvToken
public int Count { get { if (Message != null && Message.Count >= 2) { int offset = 1; byte encodedByte; var multiplier = 1; var remainingLength = 0; do { encodedByte = Message[offset]; remainingLength += encodedByte & 0x7f * multiplier; multiplier *= 0x80; } while ((++offset <=4) && (encodedByte & 0x80) != 0); return remainingLength + offset; } return 0; } }
此為報文長度,這裡需多注意
例示:
using System; using System.Text; using nMqtt.Messages; namespace nMqtt.Test { class Program { static void Main(string[] args) { var client = new MqttClient("server", "clientId"); var state = client.Connect("username"); if (state == ConnectionState.Connected) { client.MessageReceived += OnMessageReceived; client.Subscribe("a/b", Qos.AtLeastOnce); } Console.ReadKey(); } static void OnMessageReceived(string topic, byte[] data) { Console.WriteLine("-------------------"); Console.WriteLine("topic:{0}", topic); Console.WriteLine("data:{0}", Encoding.UTF8.GetString(data)); } } }
最後:
程式碼託管至 https://github.com/dtxlink/nMqtt