/// <summary> /// Add a subscriber for a topic /// </summary> /// <param name="topic">Topic for subscription</param> /// <param name="qosLevel">QoS level for the topic subscription</param> /// <param name="client">Client to subscribe</param> public void Subscribe(string topic, byte qosLevel, MqttClient client) { string topicReplaced = topic.Replace(PLUS_WILDCARD, PLUS_WILDCARD_REPLACE).Replace(SHARP_WILDCARD, SHARP_WILDCARD_REPLACE); lock (this.subscribers) { // if the topic doesn't exist if (!this.subscribers.ContainsKey(topicReplaced)) { // create a new empty subscription list for the topic List <MqttSubscription> list = new List <MqttSubscription>(); this.subscribers.Add(topicReplaced, list); } // query for check client already subscribed var query = from s in this.subscribers[topicReplaced] where s.ClientId == client.ClientId select s; // if the client isn't already subscribed to the topic if (!query.Any()) { MqttSubscription subscription = new MqttSubscription() { ClientId = client.ClientId, Topic = topicReplaced, QosLevel = qosLevel, Client = client }; // add subscription to the list for the topic this.subscribers[topicReplaced].Add(subscription); } } }
/// <summary> /// Remove a subscriber for a topic /// </summary> /// <param name="topic">Topic for unsubscription</param> /// <param name="client">Client to unsubscribe</param> public void Unsubscribe(string topic, MqttClient client) { string topicReplaced = topic.Replace(PLUS_WILDCARD, PLUS_WILDCARD_REPLACE).Replace(SHARP_WILDCARD, SHARP_WILDCARD_REPLACE); lock (this.subscribers) { // if the topic exists if (this.subscribers.ContainsKey(topicReplaced)) { // query for check client subscribed var query = from s in this.subscribers[topicReplaced] where s.ClientId == client.ClientId select s; // if the client is subscribed for the topic if (query.Count() > 0) { MqttSubscription subscription = query.First(); // remove subscription from the list for the topic this.subscribers[topicReplaced].Remove(subscription); // dispose subscription subscription.Dispose(); // remove topic if there aren't subscribers if (this.subscribers[topicReplaced].Count == 0) { this.subscribers.Remove(topicReplaced); } } } } }
/// <summary> /// Publish retained message for a topic to a client /// </summary> /// <param name="topic">Topic to search for a retained message</param> /// <param name="clientId">Client Id to send retained message</param> public void PublishRetaind(string topic, string clientId) { lock (this.subscribersForRetained) { MqttSubscription subscription = this.subscriberManager.GetSubscription(topic, clientId); // add subscription to list of subscribers for receiving retained messages if (subscription != null) { this.subscribersForRetained.Enqueue(subscription); } } // unlock thread for sending messages to the subscribers this.publishQueueWaitHandle.Set(); }
/// <summary> /// Remove a subscriber for all topics /// </summary> /// <param name="client">Client to unsubscribe</param> public void Unsubscribe(MqttClient client) { lock (this.subscribers) { List <string> topicToRemove = new List <string>(); foreach (string topic in this.subscribers.Keys) { // query for check client subscribed var query = from s in this.subscribers[topic] where s.ClientId == client.ClientId select s; // if the client is subscribed for the topic if (query.Count() > 0) { MqttSubscription subscription = query.First(); // remove subscription from the list for the topic this.subscribers[topic].Remove(subscription); // dispose subscription subscription.Dispose(); // add topic to remove list if there aren't subscribers if (this.subscribers[topic].Count == 0) { topicToRemove.Add(topic); } } } // remove topic without subscribers // loop needed to avoid exception on modify collection inside previous loop foreach (string topic in topicToRemove) { this.subscribers.Remove(topic); } } }
/// <summary> /// Process the message queue to publish /// </summary> public void PublishThread() { int count; byte qosLevel; MqttMsgPublish publish; // create event to signal that current thread is ended this.publishEventEnd = new AutoResetEvent(false); while (this.isRunning) { // wait on message queueud to publish this.publishQueueWaitHandle.WaitOne(); // first check new subscribers to send retained messages ... lock (this.subscribersForRetained) { count = this.subscribersForRetained.Count; // publish retained messages to subscribers (new or reconnected) while (count > 0) { count--; MqttSubscription subscription = this.subscribersForRetained.Dequeue(); var query = from p in this.retainedMessages where (new Regex(subscription.Topic)).IsMatch(p.Key) // check for topics based also on wildcard with regex select p.Value; if (query.Count() > 0) { // reverse loop to allow for changes in "this.retainedMessages" for (int i = query.Count() - 1; i >= 0; i--) { MqttMsgPublish retained = query.ElementAt(i); qosLevel = (subscription.QosLevel < retained.QosLevel) ? subscription.QosLevel : retained.QosLevel; // send PUBLISH message to the current subscriber subscription.Client.Publish(retained.Topic, retained.Message, qosLevel, retained.Retain); } } } } // ... then check clients to send outgoing session messages lock (this.clientsForSession) { count = this.clientsForSession.Count; // publish outgoing session messages to clients (reconnected) while (count > 0) { count--; string clientId = this.clientsForSession.Dequeue(); MqttBrokerSession session = this.sessionManager.GetSession(clientId); while (session.OutgoingMessages.Count > 0) { MqttMsgPublish outgoingMsg = session.OutgoingMessages.Dequeue(); var query = from s in session.Subscriptions where (new Regex(s.Topic)).IsMatch(outgoingMsg.Topic) // check for topics based also on wildcard with regex select s; MqttSubscription subscription = query.First(); if (subscription != null) { qosLevel = (subscription.QosLevel < outgoingMsg.QosLevel) ? subscription.QosLevel : outgoingMsg.QosLevel; session.Client.Publish(outgoingMsg.Topic, outgoingMsg.Message, qosLevel, outgoingMsg.Retain); } } } } // ... then pass to process publish queue lock (this.publishQueue) { publish = null; count = this.publishQueue.Count; // publish all queued messages while (count > 0) { count--; publish = (MqttMsgPublish)this.publishQueue.Dequeue(); if (publish != null) { // get all subscriptions for a topic List <MqttSubscription> subscriptions = this.subscriberManager.GetSubscriptionsByTopic(publish.Topic); if ((subscriptions != null) && (subscriptions.Count > 0)) { foreach (MqttSubscription subscription in subscriptions) { qosLevel = (subscription.QosLevel < publish.QosLevel) ? subscription.QosLevel : publish.QosLevel; // send PUBLISH message to the current subscriber subscription.Client.Publish(publish.Topic, publish.Message, qosLevel, publish.Retain); } } // get all sessions List <MqttBrokerSession> sessions = this.sessionManager.GetSessions(); if ((sessions != null) && (sessions.Count > 0)) { foreach (MqttBrokerSession session in sessions) { var query = from s in session.Subscriptions where (new Regex(s.Topic)).IsMatch(publish.Topic) select s; MqttSubscriptionComparer comparer = new MqttSubscriptionComparer(MqttSubscriptionComparer.MqttSubscriptionComparerType.OnClientId); // consider only session active for client disconnected (not online) if (session.Client == null) { foreach (MqttSubscription subscription in query.Distinct(comparer)) { qosLevel = (subscription.QosLevel < publish.QosLevel) ? subscription.QosLevel : publish.QosLevel; // save PUBLISH message for client disconnected (not online) session.OutgoingMessages.Enqueue(publish); } } } } } } } } // signal thread end this.publishEventEnd.Set(); }