/// Version 1.0.0 /// Last modified 23.5.2014 /// /// Copyright (C) 2014 Veli-Mikko Puupponen /// /// Halyri-system is a prototype emergency call system. Its purpose is to /// demonstrate the use of the advanced capabilities available in the current /// generation smartphones in facilitating the emergency service dispatcher's /// capability to determine the nature of the emergency and to dispatch help. /// /// For more information, see the README file of this package. /// /// The MIT License (MIT) /// /// Permission is hereby granted, free of charge, to any person obtaining a copy /// of this software and associated documentation files (the "Software"), to /// deal in the Software without restriction, including without limitation the /// rights to use, copy, modify, merge, publish, distribute, sublicense, /// and/or sell copies of the Software, and to permit persons to whom the /// Software is furnished to do so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in /// all copies or substantial portions of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING /// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS /// IN THE SOFTWARE. /// using System; using System.Collections.Generic; using System.Collections; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using SimpleUdpMediaClient.Packets; using SimpleUdpMediaClient.Collections; using System.Net; using System.Net.Sockets; using System.Diagnostics; namespace SimpleUdpMediaClient { /// /// The function is delegate for media packet receive events. /// /// The sending SimpleUdpMediaClient instance. /// The received MediaPacket. public delegate void MediaPacketReceived(object sender, MediaPacket p); /// /// The function is delegate for control packet receive events. /// /// The sending instance of SimpleUdpMediaClient. /// The received ControlPacket. public delegate void ControlPacketReceived(object sender, ControlPacket c); /// /// The function is delegate for connection problem events. /// /// The sending instance of SimpleUdpMediaClient. public delegate void SuspectedConnectionProblem(object sender); /// /// The function is delegate for connection failure events. /// /// The sending instance of SimpleUdpMediaClient. public delegate void ConnectionFailed(object sender); /// Veli-Mikko Puupponen /// /// The class is client socket implementing a simple UDP-based media transfer protocol. /// Delivery of packets is not guaranteed. Sent packets are split to conform to the /// specified MTU. The underlaying IP checksum is relied upon for the integrity /// of the packets. The constant ping packets are employed to monitor the connection and /// to keep the UDP packets routed in both directions by the network channel. /// /// This socket is usable in both peer-to-peer and server connection scenarios. /// /// The existance of a remote host is monitored with constant ping packets. If excessive /// number of consecutive ping replies is lost, the socket is deemed faulted and /// the ConnectionFailedEvent is fired. /// /// Uses separate sequence number counters for control, ping and media transfer packets. /// public class UdpMediaClientSocket { private BlockingQueue outgoingPacketQueue; private BlockingQueue outgoingMediaQueue; private Dictionary incomingMediaDictionary; private BlockingQueue incomingMediaQueue; private int incomingMediaListSizeLimit = 15; /// /// The event for the MediaPacketReceived delegate function. /// public MediaPacketReceived MediaPacketReceivedEvent; /// /// The event for the ControlPacketReceived delegate function. /// public ControlPacketReceived ControlPacketReceivedEvent; /// /// The event for the SuspectedConnectionProblem delegate function. /// public SuspectedConnectionProblem SuspectedConnectionProblemEvent; /// /// The event for the ConnectionFailed delegate function. /// public ConnectionFailed ConnectionFailedEvent; private string guid; private int mtu = 1024; private int receiveBufferLength = 4096; private int socketTimeoutMilliseconds = 1000; /// /// The function to set and get the socket timeout in milliseconds. /// public int SocketTimeoutMilliseconds { get { return socketTimeoutMilliseconds; } set { socketTimeoutMilliseconds = value; } } private string address = "127.0.0.1"; private bool localPortSet = false; private int localPort = 8901; private int remotePort = 15103; private Socket socket; private int pingIntervalMilliSeconds = 2000; private int pingWarningWindowSize = 30; private int consecutivePingSendFailureCount = 0; private int consecutivePingWarningCountLimit = 20; private int consecutivePingLostCountFailureLimit = 20; private Int64 minSequence = 1; private Int64 controlSequence; private Int64 mediaSequence; private Int64 pingSequence; private Int64 lastPingResponseSequence = 0; private Int64 lastIncomingMediaSequence = 0; private Int64 mediaSequenceExpungeTreshold = 6; private ManualResetEvent socketSendDone; private ManualResetEvent socketReceiveDone; private SocketAsyncEventArgs socketSendEventArgs; private SocketAsyncEventArgs socketReceiveEventArgs; private Thread receiverThread; private Thread sendThread; private Thread outgoingParserThread; private Thread incomingMediaPublishThread; private Timer pingTimer; private object operationLock; private bool faulted = false; private bool enabled = false; private bool coldStart = true; /// /// The function returns True, if the socket has been enabled and /// the connection to the remote host has failed. /// Such a socket is no longer enabled. Otherwise false. /// public bool IsFaulted { get { return faulted; } } /// /// The function returns true, if the socket is enabled and functional. /// Otherwise false. /// public bool IsEnabled { get { return enabled; } } /// /// The function initializes a new UdpMediaClientSocket to connect to the specified host. /// The provided GUID is used for identifying this client during the connection. /// The socket is not connected before a call to the Connect method. /// The socket will use the specified MTU on the network packets. /// The underlying UDP socket will use the specified port at the local system. /// /// The ip address of the server or peer in the standard string format. /// The port at the remote host. /// The port at the local system. /// The guid identifying this client. /// The MTU for the underlying network. /// The time-out limit for send operations on the underlying UDP socket. public UdpMediaClientSocket(string serverIpAddress, int serverPort, int localPort, string clientGuid, int networkMtu, int timeOutMillis) : this(serverIpAddress, serverPort, localPort, clientGuid, networkMtu) { socketTimeoutMilliseconds = timeOutMillis; } /// /// The function initializes a new UdpMediaClientSocket to connect to the specified host. /// The provided GUID is used for identifying this client during the connection. /// The socket is not connected before a call to the Connect method. /// The socket will use the specified MTU on the network packets. /// The underlying UDP socket will use the specified port at the local system. /// /// The ip address of the server or peer in the standard string format. /// The port at the remote host. /// the port at the local system. /// The guid identifying this client. /// The MTU for the underlying network. public UdpMediaClientSocket(string serverIpAddress, int serverPort, int localPort, string clientGuid, int networkMtu) : this(serverIpAddress, serverPort, clientGuid, networkMtu) { this.localPort = localPort; localPortSet = true; } /// /// The function initializes a new UdpMediaClientSocket to connect to the specified host. /// The provided GUID is used for identifying this client during the connection. /// The socket is not connected before a call to the Connect method. /// The socket will use the specified MTU on the network packets. /// /// The ip address of the server or peer in the standard string format. /// The port at the remote host. /// The guid identifying this client. /// The MTU for the underlying network. public UdpMediaClientSocket(string serverIpAddress, int serverPort, string clientGuid, int networkMtu) : this(serverIpAddress, serverPort, clientGuid) { mtu = networkMtu; } /// /// The function initializes a new UdpMediaClientSocket to connect to the specified host. /// The provided GUID is used for identifying this client during the connection. /// The socket is not connected before a call to the Connect method. /// The socket will use a default MTU of 1024 bytes on the network. /// /// The ip address of the server or peer in the standard string format. /// The port at the remote host. /// The guid identifying this client. public UdpMediaClientSocket(string serverIpAddress, int serverPort, string clientGuid) { address = serverIpAddress; remotePort = serverPort; guid = clientGuid; operationLock = new object(); } /// /// The function initializes a new UdpMediaClientSocket to connect to the specified host. /// The provided GUID is used for identifying this client during the connection. /// The socket is not connected before a call to the Connect method. /// The socket will use a default MTU of 1024 bytes on the network. /// /// The meximum allowable number of lost consecutive ping packets. /// The ip address of the server or peer in the standard string format. /// The port at the remote host. /// The guid identifying this client. public UdpMediaClientSocket(int pingLossLimit, string serverIpAddress, int serverPort, string clientGuid) : this(serverIpAddress, serverPort, clientGuid) { consecutivePingLostCountFailureLimit = pingLossLimit; } /// /// The function dsconnets the connected instance. /// public void Disconnect() { Disable(); } /// /// The function connects a non-connected instance. This method /// needs to be called prior to any send operations /// for them to succeed. /// public void Connect() { Monitor.Enter(operationLock); try { if (!enabled) { InitializeVariables(); InitializeUnderlayingSocket(); SetActive(); } } finally { Monitor.Exit(operationLock); } } /// /// The function disables the underlaying socket, stops all threads and /// disables the timer incoking ping packet sends. /// /// TODO: Update to close the threads in a more graceful manner. /// private void Disable() { Monitor.Enter(operationLock); try { if (enabled) { enabled = false; socketReceiveEventArgs.Completed -= ReceiveFinishedHandler; socketSendEventArgs.Completed -= SendFinishedHandler; pingTimer.Dispose(); try { if (outgoingParserThread != null) outgoingParserThread.Abort(); } catch (Exception) { } try { if (incomingMediaPublishThread != null) incomingMediaPublishThread.Abort(); } catch (Exception) { } try { if (receiverThread != null) receiverThread.Abort(); } catch (Exception) { } try { if (sendThread != null) sendThread.Abort(); } catch (Exception) { } if (socket != null) socket.Dispose(); } } finally { Monitor.Exit(operationLock); } } /// /// The function initializes state variables, internal packet collections /// and socket event args for send and receive events. /// private void InitializeVariables() { pingSequence = minSequence; if (coldStart) mediaSequence = minSequence; controlSequence = minSequence; faulted = false; enabled = true; coldStart = false; outgoingPacketQueue = new BlockingQueue(40); outgoingMediaQueue = new BlockingQueue(10); incomingMediaQueue = new BlockingQueue(10); incomingMediaDictionary = new Dictionary(); socketSendDone = new ManualResetEvent(false); socketReceiveDone = new ManualResetEvent(false); InitializeNewSocketReceiveEventArgs(); InitializeNewSocketSendEventArgs(); } /// /// The function starts the internal threads for socket receive, send and outgoing media /// parsing. Enables ping timer. /// private void SetActive() { receiverThread = new Thread(SocketReceiveLoop); incomingMediaPublishThread = new Thread(IncomingMediaPublishLoop); sendThread = new Thread(SocketSendLoop); outgoingParserThread = new Thread(OutgoingMediaParseLoop); receiverThread.Start(); incomingMediaPublishThread.Start(); sendThread.Start(); outgoingParserThread.Start(); pingTimer = new Timer(SendPingEventHandler, null, 500, pingIntervalMilliSeconds); } /// /// The funciton initialized socket event arguments for sending data /// through the UDP socket. /// private void InitializeNewSocketSendEventArgs() { if (socketSendEventArgs != null) socketSendEventArgs.Completed -= SendFinishedHandler; socketSendEventArgs = new SocketAsyncEventArgs(); socketSendEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(address), remotePort); socketSendEventArgs.Completed += SendFinishedHandler; } /// /// Initialized socket event arguments for receiving data /// from the UDP socket. /// private void InitializeNewSocketReceiveEventArgs() { if (socketReceiveEventArgs != null) socketReceiveEventArgs.Completed -= ReceiveFinishedHandler; socketReceiveEventArgs = new SocketAsyncEventArgs(); socketReceiveEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, remotePort); socketReceiveEventArgs.SetBuffer(new byte[receiveBufferLength], 0, receiveBufferLength); socketReceiveEventArgs.Completed += ReceiveFinishedHandler; } /// /// Initializes the underlaying UDP socket. If localPortSet = false, /// the socket uses a random port on the local machine. If localPortSet = true, /// the socket uses the port specified in the localPort variable, /// private void InitializeUnderlayingSocket() { socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); if (localPortSet) socket.Bind(new IPEndPoint(IPAddress.Any, localPort)); else { socket.Bind(new IPEndPoint(IPAddress.Any, 0)); localPort = ((IPEndPoint)socket.LocalEndPoint).Port; } } /// /// The function sends a media to the remote host. Retuns true if the packet was /// successfully enqueued to the outgoing media packet queue. If the /// socket is not connected, has become faulted or the outgoing packet /// queue is already filled, returns false. /// /// The information describing the outgoing media. /// The media data bytes. /// The length of the original data in case of compressions /// that require this value for decompression at the remote end. /// True if the packet was /// successfully enqueued to the outgoing media packet queue, otherwise False. public bool SendMedia(MediaInformation mediaInfo, byte[] mediaData, int mediaOriginalLength) { if (mediaInfo == null || mediaData == null || mediaData.Length == 0) return false; Monitor.Enter(operationLock); try { if (faulted || !enabled) return false; MediaPacket outgoingMediaPacket = new MediaPacket(mediaInfo, mediaData, guid, mediaOriginalLength); return outgoingMediaQueue.Offer(outgoingMediaPacket); } finally { Monitor.Exit(operationLock); } } /// /// The function sends a routing request to the remote host. The socket enforces no rules /// on the guids that are specified on the request. /// /// The guid of the source for the routing request. /// The guid of the target for the routing request. /// Whether routing between the client sockets /// using the specified should be enabled or disabled. /// True if the routing request was successfully sent, otherwise False. public bool SendRoutingRequest(string fromGuid, string toGuid, bool enableRouting) { if (fromGuid == null || toGuid == null || !(fromGuid.Length > 0) || !(toGuid.Length > 0)) return false; Monitor.Enter(operationLock); try { if (faulted || !enabled) return false; ControlPacket.CommandType command = ControlPacket.CommandType.RequestEnableRouting; if (!enableRouting) command = ControlPacket.CommandType.RequestDisableRouting; bool sendSuccess = outgoingPacketQueue.Offer(new ControlPacket(fromGuid, toGuid, command, controlSequence)); if (sendSuccess) controlSequence++; return sendSuccess; } finally { Monitor.Exit(operationLock); } } /// /// The function handles UDP socket asynchronous send finished events. Fires /// the socnetSendDone event. /// /// The sending Socket instance. /// The socketAsyncEventArgs used on the related asynchronous send call. private void SendFinishedHandler(Object state, SocketAsyncEventArgs e) { if (e.SocketError != SocketError.Success) { faulted = true; Disable(); if (ConnectionFailedEvent != null) ConnectionFailedEvent(this); return; } else socketSendDone.Set(); } /// /// The function handles UDP socket asynchronous receive finished events. Determines the /// type of the packet based on the first byte of the data and invokes /// the relevant packet handler method. /// /// The sending Socket instance. /// The socketAsyncEventArgs used on the receive call. private void ReceiveFinishedHandler(Object state, SocketAsyncEventArgs e) { if (e.SocketError == SocketError.Success) { if (e != null && e.BytesTransferred > 0) { byte[] incomingData = new byte[e.BytesTransferred]; Array.Copy(e.Buffer, e.Offset, incomingData, 0, incomingData.Length); if (incomingData != null && incomingData.Length > 0) { switch (incomingData[0]) { case ControlPacket.PacketId: HandleIncomingControlPacket(incomingData); break; case PingPacket.PacketId: HandleIncomingPingResponse(incomingData); break; case MediaHeaderPacket.PacketId: HandleIncomingMediaHeaderPacket(incomingData); break; case MediaContinuationPacket.PacketId: HandleIncomingMediaContinuationPacket(incomingData); break; } } } } else { faulted = true; Disable(); if (ConnectionFailedEvent != null) ConnectionFailedEvent(this); return; } socketReceiveDone.Set(); } /// /// The function handles incoming media header packet. The packet is parsed /// into a new MediaHeaderPacket instance and added to the /// relevant media packet in the incomingMediaDictionary /// collection. If the packet completes an imcomplete /// media packet, the MediaPacketReceived event is fired and /// the media packet is removed from the incomingMediaDictionary. /// /// The byte representation of the received MediaHeaderPacket. private void HandleIncomingMediaHeaderPacket(byte[] data) { try { if (data.Length < MediaHeaderPacket.HeaderLengthInOctets) return; MediaHeaderPacket headerPacketIn = new MediaHeaderPacket(); headerPacketIn.FromBytes(data); if (headerPacketIn.Sequence < minSequence) return; MediaPacket existingMediaPacket = null; if (incomingMediaDictionary.TryGetValue(headerPacketIn.Sequence, out existingMediaPacket)) { if (existingMediaPacket != null) if (existingMediaPacket.AddHeaderPacket(headerPacketIn)) { if (existingMediaPacket.Sequence > lastIncomingMediaSequence) lastIncomingMediaSequence = existingMediaPacket.Sequence; incomingMediaQueue.Offer(existingMediaPacket); incomingMediaDictionary.Remove(existingMediaPacket.Sequence); } } else { existingMediaPacket = new MediaPacket(headerPacketIn); if (existingMediaPacket.HasAllContinuationPackets) { if (existingMediaPacket.Sequence > lastIncomingMediaSequence) lastIncomingMediaSequence = existingMediaPacket.Sequence; incomingMediaQueue.Offer(existingMediaPacket); } else incomingMediaDictionary.Add(existingMediaPacket.Sequence, existingMediaPacket); } IncomingMediaDictionaryCleanup(); } catch (ArgumentException) { } //Just ignore if broken. } /// /// The function removes all such entries from the incomingMediaDictionary that have /// a sequence number smaller than lastIncomingMediaSequence - mediaSequenceExpungeTreshold. /// private void IncomingMediaDictionaryCleanup() { if (incomingMediaDictionary.Count >= incomingMediaListSizeLimit) { List keys = new List(incomingMediaDictionary.Keys.Count); keys.AddRange(incomingMediaDictionary.Keys); foreach (long key in keys) { if (key < (lastIncomingMediaSequence - mediaSequenceExpungeTreshold)) incomingMediaDictionary.Remove(key); } } } /// /// The function handles incoming media continuation packet. The packet is parsed /// into a new MediaContinuationPacket instance and added to the /// relevant media packet in the incomingMediaDictionary /// collection. If the packet completes an imcomplete /// media packet, the MediaPacketReceived event is fired and /// the media packet is removed from the incomingMediaDictionary. /// /// The byte representation of the received MediaHeaderPacket. private void HandleIncomingMediaContinuationPacket(byte[] data) { try { if (data.Length < MediaContinuationPacket.HeaderLengthInOctets) return; MediaContinuationPacket continuationPacketIn = new MediaContinuationPacket(); continuationPacketIn.FromBytes(data); if (continuationPacketIn.Sequence < minSequence) return; MediaPacket existingMediaPacket = null; if (incomingMediaDictionary.TryGetValue(continuationPacketIn.Sequence, out existingMediaPacket)) { if (existingMediaPacket != null) if (existingMediaPacket.AddContinuationPacket(continuationPacketIn)) { if (existingMediaPacket.Sequence > lastIncomingMediaSequence) lastIncomingMediaSequence = existingMediaPacket.Sequence; incomingMediaQueue.Offer(existingMediaPacket); incomingMediaDictionary.Remove(existingMediaPacket.Sequence); } } else { existingMediaPacket = new MediaPacket(continuationPacketIn); incomingMediaDictionary.Add(existingMediaPacket.Sequence, existingMediaPacket); } } catch (ArgumentException) { } //Just ignore if broken. } /// /// The function handles incoming ControlPackets. If they are syntactically valid /// ControlPackets, they are sent in a ControlPacketReceivedEvent. /// /// Byte representation of a ControlPacket. private void HandleIncomingControlPacket(byte[] data) { try { if (data.Length < ControlPacket.HeaderLengthInOctets) return; ControlPacket controlIn = new ControlPacket(); controlIn.FromBytes(data); if (controlIn.TargetGuid != null && controlIn.TargetGuid.Length > 0 && controlIn.SourceGuid != null && controlIn.SourceGuid.Length > 0) if (controlIn.Sequence > minSequence && ControlPacketReceivedEvent != null) ControlPacketReceivedEvent(this, controlIn); } catch (ArgumentException) { } //Just ignore if broken. } /// /// The function handles incoming ping packets. If the packet has a valid /// sequence number and the guid matches the one used by this socket, /// the last received ping sequence number counter is set accordingly. /// If the ping packet has a guid different from the one used by this /// socket, but originates from the remote host, a response ping packet /// with the same information is sent to the remote host. /// /// /// The byte representation of a PingPacket. private void HandleIncomingPingResponse(byte[] data) { try { if (data.Length < PingPacket.HeaderLengthInOctets) return; PingPacket pingIn = new PingPacket(); pingIn.FromBytes(data); if (pingIn.Sequence < minSequence || pingIn.Sequence > (pingSequence - 1) || pingIn.SourceGuid == null || pingIn.SourceGuid.Length == 0) return; if (pingIn.SourceGuid == guid) { if (pingIn.Sequence > lastPingResponseSequence) lastPingResponseSequence = pingIn.Sequence; if ((pingSequence - lastPingResponseSequence) > pingWarningWindowSize) if (SuspectedConnectionProblemEvent != null) SuspectedConnectionProblemEvent(this); } else { if (((IPEndPoint)socketReceiveEventArgs.RemoteEndPoint).Port == remotePort && ((IPEndPoint)socketReceiveEventArgs.RemoteEndPoint).Address.ToString() == address) { PingPacket pingOut = new PingPacket(pingIn.SourceGuid, pingIn.Sequence); outgoingPacketQueue.Offer(pingOut); } } } catch (ArgumentException) { } //Just ignore if broken. } /// /// The function handles ping send events generated by the ping send timer. /// Sends a ping packet to the remote host using the current /// ping packet sequence number. If sending the packet fails /// due to overflow of the outgoing packet queue, the failure /// counter is incremented. If sequence number of the last /// received ping packet is found to deviate more than /// consecutivePingLostCountLimit from the sequnce number of /// the last ping packet sent, ConnectionFailedEvent is fired. /// /// Object instance of the firing timer. private void SendPingEventHandler(Object state) { if (!faulted && enabled && socket != null) { if ((pingSequence - lastPingResponseSequence) > consecutivePingLostCountFailureLimit) { faulted = true; Disable(); if (ConnectionFailedEvent != null) ConnectionFailedEvent(this); return; } PingPacket pingOut = new PingPacket(guid, pingSequence); pingSequence++; if (outgoingPacketQueue.Offer(pingOut)) consecutivePingSendFailureCount = 0; else { consecutivePingSendFailureCount++; if (consecutivePingSendFailureCount >= consecutivePingWarningCountLimit) { consecutivePingSendFailureCount = 0; if (SuspectedConnectionProblemEvent != null) SuspectedConnectionProblemEvent(this); } } } } /// /// A blocking method that parses the MediaPackets from the /// outgoingMediaQueue to the outgoingPacketQueue. Should /// be invoked from a dedicated parser thread. /// private void OutgoingMediaParseLoop() { try { while (enabled && !faulted && outgoingMediaQueue != null && outgoingMediaQueue != null) { MediaPacket currentPacket = outgoingMediaQueue.Dequeue(); if (currentPacket != null) { currentPacket.Sequence = mediaSequence; INetworkPacket[] outgoingMediaTransmissionPackets = currentPacket.GetAllTransmissionPackets(mtu); foreach (INetworkPacket packet in outgoingMediaTransmissionPackets) { outgoingPacketQueue.Enqueue(packet); } mediaSequence++; } } } catch (ThreadAbortException) { return; } } /// /// A blocking method that sends packets from the outgoingPacketQueue /// using the initialized UDP socket. Should be invoked from a /// dedicated thread. /// private void SocketSendLoop() { try { while (enabled && !faulted && socket != null && outgoingPacketQueue != null && socketSendEventArgs != null) { INetworkPacket currentPacket = outgoingPacketQueue.Dequeue(); if (currentPacket != null) { byte[] currentPacketAsTransferBytes = currentPacket.GetBytes(); socketSendEventArgs.SetBuffer(currentPacketAsTransferBytes, 0, currentPacketAsTransferBytes.Length); socketSendDone.Reset(); if (socket != null) try { socket.SendToAsync(socketSendEventArgs); } catch (ObjectDisposedException) { faulted = true; Disable(); if (ConnectionFailedEvent != null) ConnectionFailedEvent(this); return; } if (!socketSendDone.WaitOne(socketTimeoutMilliseconds)) InitializeNewSocketSendEventArgs(); } } } catch (ThreadAbortException) { return; } } /// /// A blocking method that receives datagrams from the /// UDP socket. Should be invoked from a dedicated thread. /// private void SocketReceiveLoop() { try { while (enabled && !faulted && socket != null && socketReceiveEventArgs != null) { socketReceiveDone.Reset(); if (socket != null) try { socket.ReceiveFromAsync(socketReceiveEventArgs); } catch (ObjectDisposedException) { faulted = true; Disable(); if (ConnectionFailedEvent != null) ConnectionFailedEvent(this); return; } if (!socketReceiveDone.WaitOne()) InitializeNewSocketReceiveEventArgs(); } } catch (ThreadAbortException) { return; } } /// /// A blocking method that distributes received media packets /// from the incomingMediaQueue to the MediaPacketReceivedEvent /// listeners. Should be invoked from a dedicated thread. /// private void IncomingMediaPublishLoop() { try { while (enabled && !faulted && incomingMediaQueue != null) { MediaPacket mediaInPacket = incomingMediaQueue.Dequeue(); if (mediaInPacket != null) if (MediaPacketReceivedEvent != null) MediaPacketReceivedEvent(this, mediaInPacket); } } catch (ThreadAbortException) { return; } } } }