/// <summary> /// Publishes a message. /// </summary> /// <param name="contract">The contract</param> /// <param name="message">The message to publish.</param> /// <param name="channelHash">The hashed channel id.</param> /// <param name="channelName">The full name of the channel.</param> /// <param name="ttl">The time-to-live value for the message.</param> public static void Publish(EmitterContract contract, uint channelHash, string channelName, ArraySegment <byte> message, int ttl = EmitterConst.Transient) { try { // Make sure the TTL is within limits and max is 30 days. if (ttl < 0) { ttl = EmitterConst.Transient; } if (ttl > 2592000) { ttl = 2592000; } var contractId = contract.Oid; var cid = ((long)contractId << 32) + channelHash; // Increment the counter Service.MessageReceived?.Invoke(contract, channelName, message.Count); // The SSID var ssid = EmitterChannel.Ssid(contractId, channelName); // Get the subscriptions var subs = Subscription.Match(ssid); if (subs != null) { // Fast-Path: check local subscribers and send it to them ForwardToClients(contractId, channelName, message, subs); // Publish into the messaging cluster once ForwardToServers(contractId, channelName, message, subs); } // Only call into the storage service if necessary if (ttl > 0 && Services.Storage != null) { // If we have a storage service, store the message Services.Storage.AppendAsync(contractId, ssid, ttl, message); } } catch (Exception ex) { Service.Logger.Log(LogLevel.Warning, "Publish failed. Reason: " + ex.Message); //Service.Logger.Log(ex); return; } }
/// <summary> /// Publishes the status to the cluster. /// </summary> public void Publish() { // Enqueue into samplers SamplerMPSIn.CumulativeDelta(Service.Monitoring.MessagesIncoming); SamplerMPSOut.CumulativeDelta(Service.Monitoring.MessagesOutgoing); // Push the time and static values this.Time = DateTime.UtcNow; this.Node = EmitterStatus.NodeIdentifier.ToString(); this.Host = EmitterStatus.Address; this.Machine = EmitterStatus.MachineName; this.CPU = Math.Round(NetStat.AverageCpuUsage.Value, 2); this.Memory = Math.Round(NetStat.AverageWorkingSet.Value, 2); this.Uptime = NetStat.Uptime; // Set the network info this.Network.Connections = Service.Clients.Count; this.Network.AveragePPSIncoming = Math.Round(NetStat.AveragePPSIncoming.Value, 2); this.Network.AveragePPSOutgoing = Math.Round(NetStat.AveragePPSOutgoing.Value, 2); this.Network.AverageBPSIncoming = Math.Round(NetStat.AverageBPSIncoming.Value, 2); this.Network.AverageBPSOutgoing = Math.Round(NetStat.AverageBPSOutgoing.Value, 2); this.Network.AverageMPSIncoming = Math.Round(SamplerMPSIn.Sum(), 2); this.Network.AverageMPSOutgoing = Math.Round(SamplerMPSOut.Sum(), 2); this.Network.Compression = Math.Round(NetStat.Compression.Value, 2); // Create the message var message = Encoding.UTF8.GetBytes( JsonConvert.SerializeObject(this) ).AsSegment(); // Prepare options var contract = SecurityLicense.Current.Contract; var channel = "cluster/" + this.Host + "/"; // Get the channel hash for query EmitterChannel info; if (!EmitterChannel.TryParse(channel, false, out info)) { return; } // Publish the message in the cluster, don't need to store it as it might slow everything down Dispatcher.Publish(contract, info.Target, channel, message, 60); }
/// <summary> /// Forwards a message to a set of subscriptions /// </summary> internal static void ForwardToClients(int contractId, string channel, ArraySegment <byte> message) { // Separator must be added channel += EmitterConst.Separator; // Create an SSID var ssid = EmitterChannel.Ssid(contractId, channel); // Get the matching subscriptions var subs = Subscription.Match(ssid); if (subs == null) { return; } // Forward to the clients ForwardToClients(contractId, channel, message, subs); }
/// <summary> /// Unregisters a subscription from the current registry. /// </summary> /// <param name="client">The client to unsubscribe.</param> /// <param name="contract">The contract.</param> /// <param name="channel"></param> /// <returns>Whether the subscription was removed or not.</returns> public static bool Unregister(IMqttSender client, int contract, string channel, SubscriptionInterest interest, out Subscription subs) { // Make the subscription id var ssid = EmitterChannel.Ssid(contract, channel); // Get the subscription first if (!Index.TryGetValue(ssid, 0, out subs)) { return(false); } // Validate, only the correct contract can unsubscribe if (subs.ContractKey != contract) { return(false); } // Unregister a client subs.Unsubscribe(client, interest); return(true); }
/// <summary> /// Disposes the subscription. /// </summary> public void Dispose() { try { // Unbind events if (this.Presence != null) { this.Presence.Change -= this.OnPresenceChange; } // Get the ssid var ssid = EmitterChannel.Ssid(this.ContractKey, this.Channel); // Remove the subscription from the registry Subscription removed; Index.TryRemove(ssid, 0, out removed); } catch (Exception ex) { Service.Logger.Log(ex); } }
/// <summary> /// Unregisters a subscription from the current registry. /// </summary> /// <param name="server">The server to unsubscribe.</param> /// <param name="contract">The contract.</param> /// <param name="channel"></param> /// <returns>Whether the subscription was removed or not.</returns> public static bool Unregister(IServer server, int contract, string channel) { // Make the subscription id var ssid = EmitterChannel.Ssid(contract, channel); // Get the subscription first Subscription subs; if (!Index.TryGetValue(ssid, 0, out subs)) { return(false); } // Validate, only the correct contract can unsubscribe if (subs.ContractKey != contract) { return(false); } // Unregister a server subs.Unsubscribe(server); return(false); }
/// <summary> /// Attempts to parse the channel. /// </summary> /// <param name="channel">The channel string to parse.</param> /// <param name="info">The channel information parsed.</param> /// <param name="withKey">Whether we should parse the key first or not.</param> /// <returns>Whether we parsed or not.</returns> public static unsafe bool TryParse(string channel, bool withKey, out EmitterChannel info) { // The result info = new EmitterChannel(); info.Type = ChannelType.Invalid; info.Target = 0; // Variables and state char symbol; string key = null; int begin = 0; var last = false; var state = withKey ? ParseState.ReadingKey : ParseState.ReadingChannel; var length = channel.Length; // Pin the string fixed(char *pChar = channel) { // Start the single-pass parsing for (int i = 0; i < length; ++i) { // Is this the last character? last = (i + 1 == length); symbol = *(pChar + i); switch (state) { // We're trying to read the provided API key, this should be the 32-character long // key or 'emitter' string for custom API requests. case ParseState.ReadingKey: // We only need to wait until the '/' sign if (symbol != EmitterConst.Separator) { break; } // Copy the key info.Key = new string(pChar, 0, i); begin = i + 1; // Change the state and continue state = ParseState.ReadingChannel; continue; // We're trying to read and validate the channel and infer the type of the channel. case ParseState.ReadingChannel: // If we're reading the first separator and haven't set the channel hash yet, we should // compute the Murmur32 hash on our root. if (info.Target == 0 && (last || symbol == EmitterConst.Separator)) { info.Target = GetHash(pChar + begin, i - begin + (symbol == EmitterConst.Separator ? 0 : 1)); } // If this symbol is a wildcard symbol if (symbol == '+' || symbol == '*') { // Check if previous character is a '/' and whether we have another '/', '?' or nothing after. if (*(pChar + i - 1) == EmitterConst.Separator && (*(pChar + i + 1) == EmitterConst.Separator || *(pChar + i + 1) == '?' || last)) { // This is a valid wildcard info.Type = ChannelType.Wildcard; continue; } // Channel is invalid, we should stop parsing return(false); } // If it's the last character in the string, assign the whole thing to the channel. Similarly, // if this is the '?' sign that means we have finished parsing the channel and can assign it. else if (last || symbol == '?') { // We can now set the channel string info.Channel = new string(pChar, begin, i - begin + (symbol == '?' ? 0 : 1)); begin = i + 1; // Make sure channel ends with trailing slash if (!(info.Channel[info.Channel.Length - 1] == EmitterConst.Separator)) { info.Channel += EmitterConst.Separator; } // If we haven't detected that the channel is a wildcard, this means this is the // static channel or root. Set the type now. if (info.Type != ChannelType.Wildcard) { info.Type = ChannelType.Static; } // We need to create the options dictionary, since we do have options if (symbol == '?') { info.Options = new Dictionary <string, string>(); } // Change the state and continue state = ParseState.ReadingOptionKey; continue; } // Is this a valid character? else if ((symbol >= Code.DIGIT_0 && symbol <= Code.DIGIT_9) || (symbol == Code.DOT) || (symbol == Code.MINUS) || (symbol == Code.COLON) || (symbol == EmitterConst.Separator) || (symbol >= Code.A && symbol <= Code.Z) || (symbol >= Code.a && symbol <= Code.z)) { // That's ok, continue continue; } else { // Channel is invalid, we should stop parsing return(false); } // We're now reading the options, using the query-string format. This is a simple set of key/value pairs // separated from the channel by '?', from each other by '&' and key from value by '='. case ParseState.ReadingOptionKey: // This is the case where we do not have a value, we assign an empty value if (last || symbol == '=') { // Get the key key = new string(pChar, begin, i - begin + (symbol == '=' ? 0 : 1)); // Change the state and continue begin = i + 1; state = ParseState.ReadingOptionValue; continue; } // If we don't have the value else if (last || symbol == '&') { // Write the key we have for this value and the value itself key = new string(pChar, begin, i - begin + (symbol == '&' ? 0 : 1)); if (!info.Options.ContainsKey(key)) { info.Options.Add(key, string.Empty); key = null; } // Keep the state and continue begin = i + 1; continue; } // Is this a valid character? else if ((symbol >= Code.DIGIT_0 && symbol <= Code.DIGIT_9) || (symbol == Code.MINUS) || (symbol >= Code.A && symbol <= Code.Z) || (symbol >= Code.a && symbol <= Code.z)) { // That's ok, continue continue; } else { // Channel is invalid, we should stop parsing return(false); } // Similarly, we parse the value of the option. In some cases, options could have no value and serve as // simple flags (ie: true if present, false if not present). case ParseState.ReadingOptionValue: // We've finished reading the value, put it to the options dictionary now if (last || symbol == '&') { // Write the key we have for this value and the value itself if (!info.Options.ContainsKey(key)) { info.Options.Add(key, new string(pChar, begin, i - begin + (symbol == '&' ? 0 : 1))); key = null; } // Change the state and continue begin = i + 1; state = ParseState.ReadingOptionKey; continue; } // Is this a valid character? else if ((symbol >= Code.DIGIT_0 && symbol <= Code.DIGIT_9) || (symbol == Code.MINUS) || (symbol >= Code.A && symbol <= Code.Z) || (symbol >= Code.a && symbol <= Code.z)) { // That's ok, continue continue; } else { // Channel is invalid, we should stop parsing return(false); } } } // We're done parsing, return the structure return(true); } }
/// <summary> /// Adds or updates the subscription. /// </summary> /// <param name="channel">The channel for the subscription to find.</param> /// <param name="contract">The channel for the subscription to find.</param> /// <param name="addFunc">The add function to execute.</param> /// <param name="updateFunc">The update function to execute.</param> /// <returns></returns> public Subscription AddOrUpdate(int contract, string channel, Func <Subscription> addFunc, Func <Subscription, Subscription> updateFunc) { return(this.AddOrUpdate(EmitterChannel.Ssid(contract, channel), 0, addFunc, updateFunc)); }
/// <summary> /// Attempts to fetch a subscription from the trie. /// </summary> /// <param name="sub">The subscription retrieved.</param> /// <param name="channel">The channel for the subscription to find.</param> /// <param name="contract">The channel for the subscription to find.</param> /// <returns>Whether the subscription was found or not.</returns> public bool TryGetValue(int contract, string channel, out Subscription sub) { return(this.TryGetValue(EmitterChannel.Ssid(contract, channel), 0, out sub)); }
/// <summary> /// Attempts to remove a subscription from the trie. /// </summary> /// <param name="sub">The subscription to remove.</param> /// <returns>Whether the subscription was removed or not.</returns> public bool TryRemove(Subscription sub) { Subscription removed; return(this.TryRemove(EmitterChannel.Ssid(sub.ContractKey, sub.Channel), 0, out removed)); }
/// <summary> /// Attempts to add a subscription to the trie. /// </summary> /// <param name="sub">The subscription to add.</param> /// <returns>Whether the subscription was added or not.</returns> public bool TryAdd(Subscription sub) { return(this.TryAdd(EmitterChannel.Ssid(sub.ContractKey, sub.Channel), sub)); }