/// <summary> /// Sends a message to all matching listeners. This method is thread-safe! /// </summary> /// <param name="message">The message with the channel and command.</param> /// <param name="kind">The matching kind of listeners. Thus, you can send a message only to Ocean servers, component servers or to both.</param> /// <returns>All received answers.</returns> public IEnumerable<IICCCMessage> send2All(IICCCMessage message, byte kind) { // No message? No answer! if(message == null) { return new IICCCMessage[0]; } // We read the cache, thus, ensure the read access: this.cacheLock.EnterReadLock(); try { // Convert the message to data: var data = ICCCProcessor.INSTANCE.convertMessage2Data(message); // No valid message? if(data == null || data.Count < 2) // channel + command = 2 { return new IICCCMessage[0]; } // Get all matching listeners: var matchingListeners = this.icccListenerCache.Where(n => n.IsActive && (kind == ICCCKind.KindALL || n.Kind == kind)).Where(n => n.Channel == message.getChannel() && n.Command == message.getCommand()).ToArray(); // No matching listener? if(matchingListeners.Length == 0) { return new IICCCMessage[0]; } // Space for all threads and results: var tasks = new Task<IICCCMessage>[matchingListeners.Length]; // Loop over all matching listeners: for(var n = 0; n < matchingListeners.Length; n++) { // Get an listener: var listener = matchingListeners[n]; // Start a new thread: tasks[n] = Task.Run<IICCCMessage>(() => { // Send the message and read the answer: var answerData = this.sendMessage(data, listener); // Create another empty instance for the answer: var type = message.getAnswerObject().GetType(); var answerObj = type.GetConstructors().First(info => !info.GetParameters().Any()).Invoke(null) as IICCCMessage; // Create the answer's message and return it: return ICCCProcessor.INSTANCE.convertData2Message(answerData, answerObj); }); } // Wait for all answers: Task.WaitAll(tasks); return tasks.Where(n => n.Result != null).Select(t => t.Result).ToArray(); } catch { return new IICCCMessage[0]; } finally { this.cacheLock.ExitReadLock(); } }
/// <summary> /// Converts a message into data. This method is thread-safe! /// </summary> /// <param name="message">The message.</param> /// <returns>The data object which is may empty.</returns> public NameValueCollection convertMessage2Data(IICCCMessage message) { try { // The result's data: var data = new NameValueCollection(); // No message? if(message == null) { return data; } // Store the channel: data.Add("channel", message.getChannel()); // Store the command: data.Add("command", message.getCommand()); // Get the type of the message: var type = message.GetType(); // Get all properties: var properties = type.GetProperties(); // The .NET ICCC driver is based on properties. // In case that the type has no properties, we are // done here. if(!properties.Any()) { // We return the current data, because there are many // messages without any additional data. Thus, this is a valid case. return data; } // Loop over all properties: foreach(var property in properties) { // Read the type of this property: var dataType = property.PropertyType; // Read the value: var value = property.GetValue(message); // Read the name: var name = property.Name; // Find the matching encoding: switch(dataType.Name) { case "String": data.Add("str:" + name, Convert.ToBase64String(Encoding.UTF8.GetBytes(Uri.EscapeDataString(value as string)))); break; case "Int64": data.Add("int:" + name, Convert.ToBase64String(BitConverter.GetBytes((long)value))); break; case "Double": data.Add("f64:" + name, Convert.ToBase64String(BitConverter.GetBytes((double)value))); break; case "Boolean": var boolValue = (bool)value; data.Add("bool:" + name, Convert.ToBase64String(new byte[] { boolValue ? (byte)0x1 : (byte)0x0 })); break; case "Byte": data.Add("ui8:" + name, Convert.ToBase64String(new byte[] { (byte)value })); break; case "Byte[]": data.Add("ui8[]:" + name, Convert.ToBase64String(value as byte[])); break; case "Boolean[]": data.Add("bool[]:" + name, Convert.ToBase64String((value as bool[]).Select(n => n ? (byte)0x1 : (byte)0x0).ToArray())); break; case "Double[]": var doubleData = value as double[]; var doubleBuffer = new byte[doubleData.Length*8]; Buffer.BlockCopy(doubleData, 0, doubleBuffer, 0, doubleData.Length*8); data.Add("f64[]:" + name, Convert.ToBase64String(doubleBuffer)); break; case "Int64[]": var longData = value as long[]; var longBuffer = new byte[longData.Length*8]; Buffer.BlockCopy(longData, 0, longBuffer, 0, longData.Length*8); data.Add("int[]:" + name, Convert.ToBase64String(longBuffer)); break; case "String[]": var strings = value as string[]; data.Add("str[]:" + name, Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Join("\n", strings.Select(n => Uri.EscapeDataString(n)))))); break; } } return data; } catch { return new NameValueCollection(); } }
/// <summary> /// Sends a message to any matching listener. This method is thread-safe! /// </summary> /// <param name="message">The message with the channel and command.</param> /// <param name="kind">The matching kind of listeners. Thus, you can send a message only to an Ocean server, component server or to both.</param> /// <returns>The received answer or null.</returns> public IICCCMessage send2Any(IICCCMessage message, byte kind) { // No message? No answer! if(message == null) { return null; } // We read the cache, thus, ensure the read access: this.cacheLock.EnterReadLock(); try { // Convert the message to data: var data = ICCCProcessor.INSTANCE.convertMessage2Data(message); // No valid message? if(data == null || data.Count < 2) // channel + command = 2 { return null; } // Get all matching listeners: var matchingListeners = this.icccListenerCache.Where(n => n.IsActive && (kind == ICCCKind.KindALL || n.Kind == kind)).Where(n => n.Channel == message.getChannel() && n.Command == message.getCommand()).ToArray(); // The chosen listener: var chosenListener = null as ICCCListener; // No matching listener? if(matchingListeners.Length == 0) { return null; } if(matchingListeners.Length == 1) { // Take the only one: chosenListener = matchingListeners[0]; } else { // Choose a random listener: chosenListener = matchingListeners[BetterRND.INSTANCE.nextInt(0, matchingListeners.Length)]; } // Send the message and read the answer: var answerData = this.sendMessage(data, chosenListener); // Create the answer's message and return it: return ICCCProcessor.INSTANCE.convertData2Message(answerData, message.getAnswerObject()); } catch { return null; } finally { this.cacheLock.ExitReadLock(); } }