public void OnMsg(LeafSettingsMsg msg) { using (TimedLock.Lock(this.SyncRoot)) { if (advertiseTime != msg.AdvertiseTime) { advertiseTime = msg.AdvertiseTime; lastAdvertise = SysTime.Now; } if (msg.HubIPAddress != null && msg.HubTcpPort != 0) { if (hubIPAddress != msg.HubIPAddress || hubTcpPort != msg.HubTcpPort) { hubChannelEP = new ChannelEP(Transport.Tcp, new IPEndPoint(msg.HubIPAddress, msg.HubTcpPort)); } } else { hubChannelEP = null; } hubEP = msg.HubEP; hubIPAddress = msg.HubIPAddress; hubUdpPort = msg.HubUdpPort; hubTcpPort = msg.HubTcpPort; if (msg.DiscoverLogical) { SendLogicalAdvertiseMsgs(msg.HubEP); } } }
private int cbRecv; // Bytes to receive /// <summary> /// Constructor. /// </summary> /// <param name="router">The associated message router.</param> public TcpChannel(MsgRouter router) { this.router = router; this.sock = null; this.routerEP = null; this.remoteEP = null; this.localEP = null; this.connected = false; this.initProcessed = false; this.lastAccess = SysTime.Now; this.isUplink = false; this.isDownlink = false; this.isP2P = false; this.sending = false; this.sendMsg = null; this.sendQueue = new PriorityQueue <Msg>(); this.onSend = new AsyncCallback(OnSend); this.sendBuf = null; this.sendPos = 0; this.cbSend = 0; this.onReceive = new AsyncCallback(OnReceive); this.recvHeader = false; this.recvBuf = null; this.recvPos = 0; }
/// <summary> /// Removes the route to the channel endpoint passed if it exists in /// the table. /// </summary> /// <param name="ep">The channel endpoint of the route to remove.</param> public void Remove(ChannelEP ep) { if (ep.Transport != Transport.Tcp) { throw new MsgException("Only TCP endpoins supported."); } using (TimedLock.Lock(router.SyncRoot)) { var delList = new List <string>(); foreach (PhysicalRoute route in routes.Values) { if (route.TcpEP == ep) { delList.Add(route.RouterEP.ToString(-1, false)); } } for (int i = 0; i < delList.Count; i++) { routes.Remove(delList[i]); } } }
/// <summary> /// Transmits the message via the UDP broadcast client. /// </summary> /// <param name="toEP">The target endpoint.</param> /// <param name="msg">The message.</param> private void TransmitViaUdpBroadcast(ChannelEP toEP, Msg msg) { byte[] sendBuf; int cbSend; int cbMsg; msg._SetToChannel(toEP); msg._SetFromChannel(localEP); msg._Trace(router, 2, "UDP: Send", null); using (TimedLock.Lock(router.SyncRoot)) { // Initiate transmission of the message cbMsg = Msg.Save(new EnhancedMemoryStream(msgBuf), msg); sendBuf = router.EncryptFrame(msgBuf, cbMsg); cbSend = sendBuf.Length; Assertion.Validate(cbSend <= TcpConst.MTU - UdpBroadcastClient.MessageEnvelopeSize, "Message larger than UDP MTU."); try { broadcastClient.Broadcast(sendBuf); } catch { } } }
/// <summary> /// Opens a broadcast channel that uses a instance to /// broadcast messages across a collection of servers. This is typically used on networks /// that do not support multicast. /// </summary> /// <param name="settings">The settings to use for the <see cref="UdpBroadcastClient" />.</param> public void OpenUdpBroadcast(UdpBroadcastClientSettings settings) { using (TimedLock.Lock(router.SyncRoot)) { this.broadcastClient = new UdpBroadcastClient(settings); this.broadcastClient.PacketReceived += new UdpBroadcastDelegate(OnBroadcastReceive); this.transport = Transport.Multicast; this.localEP = router.NormalizeEP(new ChannelEP(Transport.Udp, router.UdpEP)); this.port = 0; this.sendQueue = null; this.onSend = null; this.sendMsg = null; this.cbSend = 0; this.sendBuf = null; this.msgBuf = new byte[TcpConst.MTU]; this.cloudEP = null; this.onSocketReceive = null; this.recvBuf = null; router.Trace(1, "UDP: OpenUdpBroadcast", null, null); // Mark the channel as open. this.isOpen = true; } }
/// <summary> /// Called occasionally by the associated router when the /// channel's local endpoint is changed. /// </summary> /// <param name="localEP">The new (normalized) endpoint.</param> /// <remarks> /// <para> /// The endpoint can change if the channel isn't bound to a /// specific IP address (aka IPAddress.Any), and the router /// detects an IP address change (due perhaps to a new network /// connection or a new IP address during a DHCP lease renewal). /// </para> /// <note> /// The endpoint passed will be normalized: the /// IP address will be valid. If no adapter IP address association /// can be found, then the IP address will be set to the loopback /// address: 127.0.0.1. /// </note> /// </remarks> public void OnNewEP(ChannelEP localEP) { using (TimedLock.Lock(router.SyncRoot)) { router.Trace(2, "TCP: NewEP", localEP.ToString(), null); this.localEP = localEP; } }
/// <summary> /// Used by unit tests to queue the message passed for sending /// rather than sending it immediately. A subsequent Send() call /// should send the message passed and then send the queued message. /// </summary> /// <param name="toEP">The target endpoint.</param> /// <param name="msg">The message to queue.</param> internal void QueueTo(ChannelEP toEP, Msg msg) { using (TimedLock.Lock(router.SyncRoot)) { msg._SetToChannel(toEP); Enqueue(msg); } }
/// <summary> /// Initializes the channel endpoints of the message's TO /// endpoint, creating the endpoint object if necessary. /// </summary> /// <param name="toChannelEP">The channel endpoint.</param> public void _SetToChannel(ChannelEP toChannelEP) { if (this.toEP == null) { this._ToEP = new MsgEP(toChannelEP); } this.toEP.ChannelEP = toChannelEP; }
/// <summary> /// Initializes the channel endpoints of the message's FROM /// endpoint, creating the endpoint object if necessary. /// </summary> /// <param name="fromChannelEP">The channel endpoint.</param> public void _SetFromChannel(ChannelEP fromChannelEP) { if (this.fromEP == null) { this.fromEP = new MsgEP(fromChannelEP); } this.fromEP.ChannelEP = fromChannelEP; }
/// <summary> /// Initiates a network connection to the message router at the /// specified network endpoint and then initiates the transmission /// of the message once the connection is established. /// </summary> /// <param name="ep">The remote router's endpoint.</param> /// <param name="msg">The message to be sent (or <c>null</c>).</param> public void Connect(IPEndPoint ep, Msg msg) { using (TimedLock.Lock(router.SyncRoot)) { Assertion.Test(sock == null); sock = new EnhancedSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); localEP = new ChannelEP(Transport.Tcp, router.NormalizeEP(router.TcpEP)); sock.NoDelay = !router.TcpDelay; sock.SendBufferSize = router.TcpSockConfig.SendBufferSize; sock.ReceiveBufferSize = router.TcpSockConfig.ReceiveBufferSize; if (router.FragmentTcp) { sock.SendMax = 1; sock.ReceiveMax = 1; } // Queue the channel initialization message and the message passed Msg initMsg; initMsg = new TcpInitMsg(router.RouterEP, new MsgRouterInfo(router), isUplink, router.TcpEP.Port); initMsg._TTL = 1; Serialize(initMsg); Enqueue(initMsg); try { SetLastAccess(); remoteEP = new ChannelEP(Transport.Tcp, router.NormalizeEP(ep)); if (msg != null) { msg._SetToChannel(remoteEP); msg._SetFromChannel(localEP); msg._Trace(router, 2, "TCP: Queue", null); Serialize(msg); Enqueue(msg); } router.Trace(2, "TCP: Outbound", "LocalEP=" + localEP.NetEP.ToString() + " remoteEP=" + remoteEP.NetEP.ToString(), null); sock.BeginConnect(remoteEP.NetEP, new AsyncCallback(OnConnect), null); } catch (Exception e) { router.Trace(string.Format(null, "TCP: Connect Failed [{0}]", ep), e); router.OnTcpClose(this); Close(); } } }
/// <summary> /// Transmits the message to the specified channel endpoint. /// </summary> /// <param name="toEP">The target endpoint.</param> /// <param name="msg">The message.</param> public void Transmit(ChannelEP toEP, Msg msg) { if (broadcastClient != null) { TransmitViaUdpBroadcast(toEP, msg); } else { TransmitViaSocket(toEP, msg); } }
/// <summary> /// Private implementation of the Transmit() method that implements an /// option that disables queuing for the message passed. /// </summary> /// <param name="toEP">The target endpoint.</param> /// <param name="msg">The message.</param> /// <param name="queue">Indicates whether queuing should be enabled for this message.</param> private void Transmit(ChannelEP toEP, Msg msg, bool queue) { msg._SetToChannel(toEP); msg._SetFromChannel(localEP); msg._Trace(router, 2, "TCP: Send", null); // Serialize the message here rather than within the lock // below for better multiprocessor performance. Serialize(msg); // Initiate transmission of the message or queue it if other // messages are awaiting transmission (if queuing is enabled). try { using (TimedLock.Lock(router.SyncRoot)) { if (!connected || sending) { Enqueue(msg); return; } // If there are already messages in the queue then add this // message to the end of the queue and then dequeue a message // from the front of the queue and send it. if (queue && sendQueue.Count > 0) { Enqueue(msg); msg = Dequeue(); } // Initiate message transmission sendBuf = msg._MsgFrame; sending = true; sendPos = 0; cbSend = sendBuf.Length; sock.BeginSend(sendBuf, sendPos, cbSend, SocketFlags.None, onSend, null); } } catch (Exception e) { TraceException(e); router.OnTcpClose(this); Close(); } }
/// <summary> /// Queues the message passed rather than initiating an /// immediate transmission. /// </summary> /// <param name="toEP">The target endpoint.</param> /// <param name="msg">The message to queue.</param> internal void QueueTo(ChannelEP toEP, Msg msg) { msg._SetToChannel(toEP); msg._SetFromChannel(localEP); msg._Trace(router, 2, "TCP: Queue", null); Serialize(msg); using (TimedLock.Lock(router.SyncRoot)) { msg._SetToChannel(toEP); Enqueue(msg); } }
private Guid logicalEndpointSetID; // The current logical endpoint set ID /// <summary> /// Constructor. /// </summary> /// <param name="routerEP">The router endpoint.</param> /// <param name="appName">The name of the application hosting the router.</param> /// <param name="appDescription">A description of the application.</param> /// <param name="routerInfo">The router's capability information.</param> /// <param name="logicalEndpointSetID">The router's logical endpoint set ID.</param> /// <param name="udpEP">The UDP network endpoint (or <c>null</c>).</param> /// <param name="tcpEP">The TCP network endpoint (or <c>null</c>).</param> /// <param name="ttd">Route time-to-die (SYS).</param> /// <remarks> /// The router endpoint must be a physical non-channel endpoint. /// </remarks> internal PhysicalRoute(MsgEP routerEP, string appName, string appDescription, MsgRouterInfo routerInfo, Guid logicalEndpointSetID, IPEndPoint udpEP, IPEndPoint tcpEP, DateTime ttd) { Assertion.Test(routerEP.IsPhysical); Assertion.Test(!routerEP.IsChannel); this.routerEP = routerEP; this.appName = appName; this.appDescription = appDescription; this.routerInfo = routerInfo; this.logicalEndpointSetID = logicalEndpointSetID; this.udpEP = udpEP == null ? null : new ChannelEP(Transport.Udp, udpEP); this.tcpEP = tcpEP == null ? null : new ChannelEP(Transport.Tcp, tcpEP); this.ttd = ttd; this.isP2P = routerInfo.IsP2P; }
/// <summary> /// Handles connection completions. /// </summary> /// <param name="ar">The async result.</param> private void OnConnect(IAsyncResult ar) { using (TimedLock.Lock(router.SyncRoot)) { try { if (sock == null) { return; } sock.EndConnect(ar); connected = true; remoteEP = new ChannelEP(Transport.Tcp, new IPEndPoint(((IPEndPoint)sock.RemoteEndPoint).Address, 0)); router.Trace(2, "TCP: Connected", "LocalEP=" + localEP.NetEP.ToString() + " remoteEP=" + remoteEP.NetEP.ToString(), null); SetLastAccess(); // Initiation reception of the first message BeginReceive(); // Start sending any queued messages Assertion.Test(!sending); if (sendQueue.Count > 0) { Msg msg; msg = Dequeue(); Transmit(msg._ToEP.ChannelEP, msg, false); } } catch (Exception e) { router.Trace(string.Format(null, "TCP: Connect Failed"), e); router.OnTcpClose(this); Close(); } } }
/// <summary> /// Opens a UDP unicast socket. /// </summary> /// <param name="ep">The UDP endpoint to open.</param> /// <remarks> /// <para> /// Pass <b>ep.Address=IPAddress.Any</b> if the channel should be opened on all /// network adapters. /// </para> /// <para> /// Pass <b>ep.Port=0</b> if Windows should assign the socket's port. The /// port assigned can be determined via the <see cref="Port" /> property. /// </para> /// </remarks> public void OpenUnicast(IPEndPoint ep) { using (TimedLock.Lock(router.SyncRoot)) { this.sock = new EnhancedSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); this.sock.Bind(ep); this.transport = Transport.Udp; this.localEP = router.NormalizeEP(new ChannelEP(Transport.Udp, (IPEndPoint)sock.LocalEndPoint)); this.port = localEP.NetEP.Port; this.sendQueue = new PriorityQueue <Msg>(); this.onSend = new AsyncCallback(OnSend); this.sendMsg = null; this.cbSend = 0; this.sendBuf = null; this.msgBuf = new byte[TcpConst.MTU]; this.cloudEP = null; this.multicastInit = false; this.broadcastClient = null; sendQueue.CountLimit = router.UdpMsgQueueCountMax; sendQueue.SizeLimit = router.UdpMsgQueueSizeMax; this.onSocketReceive = new AsyncCallback(OnSocketReceive); this.recvBuf = new byte[TcpConst.MTU]; router.Trace(1, "UDP: OpenUnicast", "localEP=" + localEP.NetEP.ToString(), null); sock.IgnoreUdpConnectionReset = true; sock.SendBufferSize = router.UdpUnicastSockConfig.SendBufferSize; sock.ReceiveBufferSize = router.UdpUnicastSockConfig.ReceiveBufferSize; // Initiate the first async receive operation on this socket sock.BeginReceiveFrom(recvBuf, 0, recvBuf.Length, SocketFlags.None, ref recvEP, onSocketReceive, null); // Mark the channel as open. this.isOpen = true; } }
/// <summary> /// Associates the channel with the open socket passed and begins /// listening for messages received on the socket. /// </summary> /// <param name="sock">The open socket.</param> public void Open(EnhancedSocket sock) { using (TimedLock.Lock(router.SyncRoot)) { Assertion.Test(this.sock == null); this.sock = sock; this.connected = true; this.localEP = router.NormalizeEP(new ChannelEP(Transport.Tcp, router.TcpEP)); this.remoteEP = new ChannelEP(Transport.Tcp, new IPEndPoint(((IPEndPoint)sock.RemoteEndPoint).Address, 0)); this.sendQueue.Clear(); sock.SendBufferSize = router.TcpSockConfig.SendBufferSize; sock.ReceiveBufferSize = router.TcpSockConfig.ReceiveBufferSize; // Send the channel initialization message to the other endpoint. sock.NoDelay = !router.TcpDelay; if (router.FragmentTcp) { sock.SendMax = 1; sock.ReceiveMax = 1; } router.Trace(2, "TCP: Inbound", "LocalEP=" + localEP.NetEP.ToString() + " remoteEP=" + remoteEP.NetEP.ToString(), null); SetLastAccess(); BeginReceive(); TcpInitMsg msg; msg = new TcpInitMsg(router.RouterEP, new MsgRouterInfo(router), isUplink, localEP.NetEP.Port); msg._TTL = 1; Transmit(router.NormalizeEP(remoteEP), msg, false); } }
/// <summary> /// Transmits the message via the socket. /// </summary> /// <param name="toEP">The target endpoint.</param> /// <param name="msg">The message.</param> private void TransmitViaSocket(ChannelEP toEP, Msg msg) { int cbMsg; if (cloudEP != null && !multicastInit) { // Retry adding the socket to the multicast group if this didn't // work earlier. try { this.sock.MulticastGroup = cloudEP.Address; this.multicastInit = true; } catch { this.multicastInit = false; } } msg._SetToChannel(toEP); msg._SetFromChannel(localEP); msg._Trace(router, 2, "UDP: Send", null); using (TimedLock.Lock(router.SyncRoot)) { if (sendMsg != null) { // We're already in the process of transmitting // a message so queue this one. Enqueue(msg); return; } // If there are already messages in the queue then queue // this one and then setup to transmit the first message // waiting in the queue. if (sendQueue.Count > 0) { Enqueue(msg); msg = sendQueue.Dequeue(); } // Initiate transmission of the message sendMsg = msg; cbMsg = Msg.Save(new EnhancedMemoryStream(msgBuf), sendMsg); sendBuf = router.EncryptFrame(msgBuf, cbMsg); cbSend = sendBuf.Length; Assertion.Validate(cbSend <= TcpConst.MTU, "Message larger than UDP MTU."); try { sock.BeginSendTo(sendBuf, 0, cbSend, SocketFlags.None, router.NormalizeEP(toEP.NetEP), onSend, null); } catch { // Ignoring } } }
public void OnMsg(RouterAdvertiseMsg msg) { PhysicalRoute physRoute; bool discoverLogical; if (this.RouterEP == null) { return; // Router is not fully initialized } if (this.RouterEP.IsPhysicalMatch(msg.RouterEP)) { // I've noticed that on multi-homed machines, we can see source IP addresses for // multicast messages from ourself that differ from what we think our IP address // is. I'm going to check the source IP address against all of the local IP // addresses to avoid issuing invalid warnings. var isLocalIPAddress = NetHelper.IsLocalAddress(msg.IPAddress); if (msg.TcpPort != this.TcpEP.Port || !isLocalIPAddress) { this.dupLeafDetected = true; SysLog.LogWarning("Duplicate router [{0}] appears to be advertising on TCP[{1}:{2}]. Local endpoint is TCP[{3}:{4}].", msg.RouterEP.ToString(-1, false), msg.IPAddress, msg.TcpPort, this.TcpEP.Address, this.TcpEP.Port); } if (msg.UdpPort != this.UdpEP.Port || !isLocalIPAddress) { this.dupLeafDetected = true; SysLog.LogWarning("Duplicate router [{0}] appears to be advertising on UDP[{1}:{2}]. Local endpoint is UDP[{3}:{4}].", msg.RouterEP.ToString(-1, false), msg.IPAddress, msg.UdpPort, this.UdpEP.Address, this.UdpEP.Port); } return; // Don't add routes to self to the routing table } // If the source router is this router's hub then send it a RouterAdvertiseMsg // so the hub can continue the route discovery process. if (msg.ReplyAdvertise && this.RouterEP.GetPhysicalParent().Equals(msg.RouterEP)) { // Set up a temporary route to the hub so the RouterAdvertiseMsg can be delivered. // These values will be finalized when the LeafSettingsMsg is received. this.hubEP = msg.RouterEP; this.hubChannelEP = new ChannelEP(Transport.Tcp, new IPEndPoint(msg.IPAddress, msg.TcpPort)); // Send the RouterAdvertiseMsg to the hub. SendTo(msg.RouterEP, new RouterAdvertiseMsg(this.RouterEP, this.AppName, this.AppDescription, this.RouterInfo, this.UdpEP.Port, this.TcpEP.Port, this.Dispatcher.LogicalEndpointSetID, false, false)); return; } // Ignore the message if either this router or the advertised router is not // peer-to-peer enabled. In these cases, messages will be forwarded to // the hub for delivery. if (!base.EnableP2P || !msg.RouterInfo.IsP2P) { return; } // Add/update the physical route to the advertised router. using (TimedLock.Lock(this.SyncRoot)) { if (!isRunning) { return; } // Handle route table management for peer routers. if (!this.RouterEP.IsPhysicalPeer(msg.RouterEP)) { const string format = @"RouterEP: {0} Route: {1}"; NetTrace.Write(MsgRouter.TraceSubsystem, 1, "Ignore route", this.GetType().Name + ": " + msg.RouterEP.ToString(), string.Format(null, format, this.RouterEP.ToString(), msg.RouterEP.ToString())); return; } // If this is the first time we've seen this router or if the router's set // of handled logical endpoints has changed then we'll need to set the // DiscoverLogical=true on the RouterAdvertiseMsg so the other will send // us its logical endpoints. physRoute = base.PhysicalRoutes[msg.RouterEP]; discoverLogical = physRoute == null || physRoute.LogicalEndpointSetID != msg.LogicalEndpointSetID; // Send a RouterAdvertiseMsg for this router back to the source router if that // router is P2P enabled and a reply is requested. This will give that router // a chance to learn about this router. if (msg.RouterInfo.IsP2P && (msg.ReplyAdvertise || discoverLogical)) { SendTo(msg.RouterEP, new RouterAdvertiseMsg(this.RouterEP, this.AppName, this.AppDescription, this.RouterInfo, this.UdpEP.Port, this.TcpEP.Port, this.Dispatcher.LogicalEndpointSetID, false, discoverLogical)); } AddPhysicalRoute(msg.RouterEP, msg.AppName, msg.AppDescription, msg.RouterInfo, msg.LogicalEndpointSetID, new IPEndPoint(msg.IPAddress, msg.UdpPort), new IPEndPoint(msg.IPAddress, msg.TcpPort)); // Send LogicalAdvertiseMsgs back to the sender if requested. if (msg.DiscoverLogical) { SendLogicalAdvertiseMsgs(msg.RouterEP); } } }
/// <summary> /// Opens a UDP multicast socket. /// </summary> /// <param name="adapter">The network adapter to bind this socket.</param> /// <param name="cloudEP">Specifies the multicast group and port.</param> /// <remarks> /// <para> /// Pass <b>adapter=IPAddress.Any</b> to bind the socket to all available network /// adapters. /// </para> /// <note> /// A valid port and address must be specified in <b>cloudEP</b>. /// </note> /// </remarks> public void OpenMulticast(IPAddress adapter, IPEndPoint cloudEP) { if (cloudEP.Address.Equals(IPAddress.Any)) { throw new MsgException("Invalid multicast address."); } if (cloudEP.Port == 0) { throw new MsgException("Invalid multicast port."); } using (TimedLock.Lock(router.SyncRoot)) { this.sock = new EnhancedSocket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); this.sock.ReuseAddress = true; this.sock.EnableBroadcast = true; this.sock.MulticastTTL = MulticastTTL; this.sock.MulticastLoopback = true; this.sock.IgnoreUdpConnectionReset = true; this.sock.Bind(new IPEndPoint(adapter, cloudEP.Port)); // The framework throws an exception if there is no connected network connection // when we attempt to add the socket to the multicast group. I'm going to catch // this exception and track that this didn't work and then periodically retry // the operation. try { this.sock.MulticastGroup = cloudEP.Address; this.multicastInit = true; } catch { this.multicastInit = false; } this.transport = Transport.Multicast; this.localEP = router.NormalizeEP(new ChannelEP(Transport.Udp, router.UdpEP)); this.port = cloudEP.Port; this.sendQueue = new PriorityQueue <Msg>(); this.onSend = new AsyncCallback(OnSend); this.sendMsg = null; this.cbSend = 0; this.sendBuf = null; this.msgBuf = new byte[TcpConst.MTU]; this.cloudEP = cloudEP; this.onSocketReceive = new AsyncCallback(OnSocketReceive); this.recvBuf = new byte[TcpConst.MTU]; sendQueue.CountLimit = router.UdpMsgQueueCountMax; sendQueue.SizeLimit = router.UdpMsgQueueSizeMax; router.Trace(1, "UDP: OpenMulticast", string.Format("cloudEP={0} localEP={1} adaptor={2} NIC={3}", cloudEP, localEP.NetEP, adapter, NetHelper.GetNetworkAdapterIndex(adapter)), null); sock.SendBufferSize = router.UdpMulticastSockConfig.SendBufferSize; sock.ReceiveBufferSize = router.UdpMulticastSockConfig.ReceiveBufferSize; // Initiate the first async receive operation on this socket sock.BeginReceiveFrom(recvBuf, 0, recvBuf.Length, SocketFlags.None, ref recvEP, onSocketReceive, null); // Mark the channel as open. this.isOpen = true; } }
/// <summary> /// Transmits the message to the specified channel endpoint. /// </summary> /// <param name="toEP">The target endpoint.</param> /// <param name="msg">The message.</param> public void Transmit(ChannelEP toEP, Msg msg) { Transmit(toEP, msg, true); }