/// 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;
}
}
}
}