I implemented my own code of Publish/subscribe pattern implementation with WCF with WSDualHttpBinding, but i've a little problem with timeouts that i explain later, for now let me show what i'm doing:
public interface IEventSubscriber
{
[OperationContract]
void NotifyEvent(EventNotification notification);
[OperationContract]
void NotifyServiceDisconnecting();
}
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IEventSubscriber))]
public interface IEventPublisherService
{
[OperationContract(IsOneWay = false, IsInitiating = true)]
void Subscribe(string culture);
[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = true)]
void Unsubscribe();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
internal class EventPublisherServiceImpl : IEventPublisherService
{
ServiceHost host;
public bool StartService()
{
bool ret = false;
try
{
Uri baseAddress = new Uri(ConfigurationManager.AppSettings[GlobalConstants.CfgKeyConfigEventPublishserServiceBaseAddress].ToString());
EventHelper.AddEvent(string.Format("Event Publisher Service on: {0}", baseAddress.ToString()));
host = new ServiceHost(this, baseAddress);
// duplex session enable http binding
WSDualHttpBinding httpBinding = new WSDualHttpBinding(WSDualHttpSecurityMode.None);
httpBinding.ReceiveTimeout = TimeSpan.FromMinutes(10);
httpBinding.ReliableSession = new ReliableSession();
httpBinding.ReliableSession.Ordered = true;
httpBinding.ReliableSession.InactivityTimeout = TimeSpan.FromMinutes(10);
host.AddServiceEndpoint(typeof(IEventPublisherService), httpBinding, baseAddress);
// Enable metadata publishing.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(smb);
// Open the ServiceHost to start listening for messages.
host.Open();
ret = true;
}
catch (Exception e)
{
EventHelper.AddException(e.Message);
}
return ret;
}
...
}
now in my implementation class i have a list of subscribers that are stored in memory, when a new notification arrived the following code is performed for each subscriber:
...
/// <summary>
/// List of active subscribers
/// </summary>
private static Dictionary<IEventSubscriber, string> subscribers = new Dictionary<IEventSubscriber, string>();
...
that i use it like this:
internal void Subscribe(string culture)
{
lock (subscribers)
{
// Get callback contract as specified on the service definition
IEventSubscriber callback = OperationContext.Current.GetCallbackChannel<IEventSubscriber>();
// Add subscriber to the list of active subscribers
if (!subscribers.ContainsKey(callback))
{
subscribers.Add(callback, culture);
}
}
}
...
private void OnNotificationEvent(NormalEvent notification)
{
lock (subscribers)
{
List<IEventSubscriber> listToRemove = new List<IEventSubscriber>();
// Method signature: Parallel.ForEach(IEnumerable<TSource> source, Action<TSource> body)
Parallel.ForEach(subscribers, kvp =>
{
try
{
kvp.Key.NotifyEvent(new EventNotification(notification, kvp.Value));
}
catch (Exception ex)
{
EventHelper.AddException(string.Format("Error notifying event notification to client: {0} - removing this one", ex.Message));
listToRemove.Add(kvp.Key);
}
} //close lambda expression
); //close method invocation
Parallel.ForEach(listToRemove, subs =>
{
try
{
subs.NotifyServiceDisconnecting();
}
catch (Exception ex) {
EventHelper.AddException(string.Format("Failed to notify client that is to be removed: {0}",
ex.Message));
}
subscribers.Remove(subs);
}
);
}
}
What is the problem with this, when timeouts are achieved (note that i set 10 minutes for ReceiveTimeout and inactive timeout) the subscribers that are in the list go to fault state, and the following exception is catched in the OnNotificationEvent
*The operation 'NotifyEvent' could not be completed because the sessionful channel timed out waiting to receive a message. To increase the timeout, either set the receiveTimeout property on the binding in your configuration file, or set the ReceiveTimeout property on the Binding directly. *
Ok i can increase the timeout value, but if i do this it will happen some time, eventually.
My question are: i'm doing something wrong when trying to implement this pattern? or exists any other way of implementing this pattern a better way that avoid this problem? or exist any way of reconnecting the faulted callback channel (for what i'm reading it's not possible, but due to that i can't notify the client that is connection was lost, letting the client blind not knowing that the communication ended!? or is a way to give knowledge that he lost communication with the publisher!?)
Of course solution like ping messages are out of date :) but ok, if nothing better appears look like i've to implement something like that...
Thanks
For now the solution was to change the timeouts to have a infinite value:
// duplex session enable http binding
WSDualHttpBinding httpBinding = new WSDualHttpBinding(WSDualHttpSecurityMode.None);
httpBinding.ReceiveTimeout = TimeSpan.MaxValue;
httpBinding.ReliableSession = new ReliableSession();
httpBinding.ReliableSession.Ordered = true;
httpBinding.ReliableSession.InactivityTimeout = TimeSpan.MaxValue;
You are using Parallel.ForEach but I'm not sure that this is enough. AFAIR Parallel.ForEach does not execute each iteration in the separate thread.
I would like to suggest to start separate threads for each subscriber in the OnNotificationEvent and use lock to make sure that foreach will not be breaked by Collection modified exception:
lock (_subscribersSync)
foreach (var chatter in subscribers)
{
Logger.Log.DebugFormat("putting all users to {0}", subscribers.Key.Name);
Thread th = new Thread(PublishAllUserMessage);
th.Start(new MessageData() { Message = "", Subscriber = chatter.Key};
}
void PublishAllUserMessage(object messageData)
{
MessageData md = (MessageData)messageData;
try
{
md.Subscriber.NotifyEvent(...event parameters here ...);
}
catch (Exception ex)
{
Logger.Log.Error(string.Format("failed to publish message to '{0}'", md.Subscriber.Name), ex);
KickOff(md.Subscriber);
}
}
object _subscribersSync = new object();
void KickOff(IEventSubscriber p)
{
lock (_subscribersSync)
{
subscribers.Remove(p);
Logger.Log.WarnFormat("'{0}' kicked off", p.Name);
}
}
public class MessageData
{
public string Message;
public IEventSubscriber Subscriber;
}
Related
I have implemented dequeuing of messages from Oracle AQ and exposing it to the system as IObservable. The workflow is as follows:-
Application receives a call back event from Oracle about a new message.
Application de-queues the message and add it to IObservable (Message is dequeued as part of a transaction which is committed as soon as the message is dequeued).
I realized a potential issue and that is when the message is dequeued, the transaction is committed straight away rather than waiting for it to be consumed successfully by the application. Below is the code which I am using but need a suggestion where/how to commit the transaction after it has been consumed successfully by the application. Currently it starts and commits/rollbacks the transaction in the private Dequeue method.
public sealed class Queue<T> : IQueue<T> where T : IQueueDataType
{
private readonly OracleConnection _connection;
private readonly string _consumerName;
private readonly IQueueSetting _queueSetting;
private readonly IDbConnectionFactory _dbConnectionFactory;
private OracleAQQueue _queue;
private IObservable<T> _messages;
private bool _isDisposed;
public Queue(IDbConnectionFactory dbConnectionFactory, IDalSettings dalSettings, IQueueSetting queueSetting)
{
_dbConnectionFactory = dbConnectionFactory;
_connection = dbConnectionFactory.Create() as OracleConnection;
_consumerName = dalSettings.Consumer;
_queueSetting = queueSetting;
}
public void Connect()
{
_connection.Open();
_queue = new OracleAQQueue(_queueSetting.QueueName, _connection)
{
DequeueOptions = { Wait = 10, Visibility = OracleAQVisibilityMode.Immediate , ConsumerName = _consumerName, NavigationMode = OracleAQNavigationMode.FirstMessage, DequeueMode = OracleAQDequeueMode.Remove},
UdtTypeName = _queueSetting.QueueDataTypeName,
MessageType = OracleAQMessageType.Udt
};
_queue.NotificationConsumers = new[] { _consumerName };
_messages = Observable
.FromEventPattern<OracleAQMessageAvailableEventHandler, OracleAQMessageAvailableEventArgs>(
h => _queue.MessageAvailable += h, h => _queue.MessageAvailable -= h)
.Where(x => x.EventArgs.AvailableMessages > 0)
.Select(x =>
{
try
{
Log.Info("Msg received", "Queue", _queueSetting.QueueName);
OracleAQMessage msg = Dequeue();
Log.Info("Msg received id " + msg.MessageId, "Queue", _queueSetting.QueueName);
return (T)msg.Payload;
}
catch (Exception e)
{
}
}).Publish().RefCount();
}
private OracleAQMessage Dequeue()
{
using (var connection = _dbConnectionFactory.Create() as OracleConnection)
{
try
{
connection.Open();
using (OracleTransaction transaction = connection.BeginTransaction())
{
try
{
OracleAQMessage msg = _queue.Dequeue();
**transaction.Commit();**
return msg;
}
catch (Exception e)
{
**transaction.Rollback();**
throw;
}
}
}
catch (Exception e)
{
Log.Error(string.Format("Error occurred while connecting to database to dequeue new message. Error : {0}", e),
"Dequeue", GetType().FullName);
throw;
}
finally
{
connection.Close();
}
}
}
public IObservable<T> GetMessages()
{
return _messages;
}
public void Dispose()
{
if (!_isDisposed)
{
if (_queue != null)
{
_queue.Dispose();
}
_connection.Dispose();
_isDisposed = true;
}
}
}
If rather than using IObservable, I just expose an event committing and rolling back transaction would be very easy but I like what I can do with IObservable i.e I can run a Linq but don't know how to commit the transaction.
I don't think there's an easy fix here. If I understand this correctly:
An event is pushed from Oracle,
You want to expose the event via an IObservable stream,
The application 'handles' it,
You want to commit if handled successfully, roll-back if not.
The problem is that IObservable is a one-way mechanism. Once you publish a message (in our case that you got something from this Oracle queue), the intention isn't to track it, and decide later whether to commit/rollback. So your options are pretty much to stuff your app-logic into a handler of some form:
Func<OracleMessage, bool> isMessageCommitable; //...application handling logic here
var appHandledMessages = oracleSourceMessages
.Select(m => Tuple.Create(m, isMessageCommitable(m)))
.Publish()
.RefCount();
appHandledMessages
.Where(t => t.Item2)
.Subscribe(t => Commit(t.Item1));
appHandledMessages
.Where(t => !t.Item2)
.Subscribe(t => Rollback(t.Item1));
...or to set up IObservables pointing the other way which would push from the application back to the queue which messages should be committed/rolled back. You would probably want two, one for committing, one for rolling back, and those should probably be passed into the constructor of Queue<T>.
Good luck.
.Net application which will subscribe to streaming notification. This application executes well, this subscription disconnected after 30 mints so I added the code to reconnect the connection .I tested the application by adding break point at the reconnect line and waited for some time before it established the connection again. During that time I created a few new emails and watched if console displays those events. It did not as connection was disconnected then I run the code again and I was able to see the events which were created during the time connection disconnected and connected. I need to know if this is the right way to make this process run continuously and track the events which occurred during the disconnect and reconnect of the application. also need to know why the below code is not notifying about delete mail event. kindly looking for help.
namespace NewMailNotification
{
class Program
{
static void Main(string[] args)
{
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
//***********New**********************
ExchangeService mailbox = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
string mailboxEmail = ConfigurationSettings.AppSettings["user-id"];
WebCredentials wbcred = new WebCredentials(ConfigurationSettings.AppSettings["user"], ConfigurationSettings.AppSettings["PWD"]);
mailbox.Credentials = wbcred;
// mailbox.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, mailboxEmail);
mailbox.AutodiscoverUrl(mailboxEmail, RedirectionUrlValidationCallback);
mailbox.HttpHeaders.Add("X-AnchorMailBox", mailboxEmail);
FolderId mb1Inbox = new FolderId(WellKnownFolderName.Inbox, mailboxEmail);
SetStreamingNotification(mailbox, mb1Inbox);
bool run = true;
bool reconnect = false;
while (run)
{
System.Threading.Thread.Sleep(100);
}
}
internal static bool RedirectionUrlValidationCallback(string redirectionUrl)
{
//The default for the validation callback is to reject the URL
bool result=false;
Uri redirectionUri=new Uri(redirectionUrl);
if(redirectionUri.Scheme=="https")
{
result=true;
}
return result;
}
static void SetStreamingNotification(ExchangeService service,FolderId fldId)
{
StreamingSubscription streamingssubscription=service.SubscribeToStreamingNotifications(new FolderId[]{fldId},
EventType.NewMail,
EventType.Created,
EventType.Deleted);
StreamingSubscriptionConnection connection=new StreamingSubscriptionConnection(service,30);
connection.AddSubscription(streamingssubscription);
//Delagate event handlers
connection.OnNotificationEvent+=new StreamingSubscriptionConnection.NotificationEventDelegate(OnEvent);
connection.OnDisconnect += new StreamingSubscriptionConnection.SubscriptionErrorDelegate(Connection_OnDisconnect);
connection.OnSubscriptionError+=new StreamingSubscriptionConnection.SubscriptionErrorDelegate(OnError);
connection.Open();
}
static private void Connection_OnDisconnect(object sender, SubscriptionErrorEventArgs args)
{
StreamingSubscriptionConnection connection = (StreamingSubscriptionConnection)sender;
if (!connection.IsOpen)
{
// Console.WriteLine("no connection");
connection.Open();
}
}
static void OnEvent(object sender,NotificationEventArgs args)
{
StreamingSubscription subscription=args.Subscription;
if(subscription.Service.HttpHeaders.ContainsKey("X-AnchorMailBox"))
{
Console.WriteLine("event for nailbox"+subscription.Service.HttpHeaders["X-AnchorMailBox"]);
}
//loop through all the item-related events.
foreach(NotificationEvent notification in args.Events)
{
switch(notification.EventType)
{
case EventType.NewMail:
Console.WriteLine("\n----------------Mail Received-----");
break;
case EventType.Created:
Console.WriteLine("\n-------------Item or Folder deleted-------");
break;
case EventType.Deleted:
Console.WriteLine("\n------------Item or folder deleted---------");
break;
}
//Display notification identifier
if(notification is ItemEvent)
{
//The NotificationEvent for an email message is an ItemEvent
ItemEvent itemEvent=(ItemEvent)notification;
Console.WriteLine("\nItemId:"+ itemEvent.ItemId.UniqueId);
Item NewItem=Item.Bind(subscription.Service,itemEvent.ItemId);
if(NewItem is EmailMessage)
{
Console.WriteLine(NewItem.Subject);
}
}
else
{
//the Notification for a Folder is an FolderEvent
FolderEvent folderEvent=(FolderEvent)notification;
Console.WriteLine("\nFolderId:"+folderEvent.FolderId.UniqueId);
}
}
}
static void OnError(object sender,SubscriptionErrorEventArgs args)
{
//Handle error conditions.
Exception e=args.Exception;
Console.WriteLine("\n-----Error-----"+e.Message+"--------");
}
}
}
Here is an example :
_BackroundSyncThread = new Thread(streamNotification.SynchronizeChangesPeriodically); _BackroundSyncThread.Start();
private void SynchronizeChangesPeriodically()
{
while (true)
{
try
{
// Get all changes from the server and process them according to the business
// rules.
SynchronizeChanges(new FolderId(WellKnownFolderName.Calendar));
}
catch (Exception ex)
{
Console.WriteLine("Failed to synchronize items. Error: {0}", ex);
}
// Since the SyncFolderItems operation is a
// rather expensive operation, only do this every 10 minutes
Thread.Sleep(TimeSpan.FromMinutes(10));
}
}
public void SynchronizeChanges(FolderId folderId)
{
bool moreChangesAvailable;
do
{
Debug.WriteLine("Synchronizing changes...");
// Get all changes since the last call. The synchronization cookie is stored in the _SynchronizationState field.
// Only the the ids are requested. Additional properties should be fetched via GetItem calls.
var changes = _ExchangeService.SyncFolderItems(folderId, PropertySet.FirstClassProperties, null, 512,
SyncFolderItemsScope.NormalItems, _SynchronizationState);
// Update the synchronization cookie
_SynchronizationState = changes.SyncState;
// Process all changes
foreach (var itemChange in changes)
{
// This example just prints the ChangeType and ItemId to the console
// LOB application would apply business rules to each item.
Console.WriteLine("ChangeType = {0}", itemChange.ChangeType);
Console.WriteLine("Subject = {0}", itemChange.Item.Subject);
}
// If more changes are available, issue additional SyncFolderItems requests.
moreChangesAvailable = changes.MoreChangesAvailable;
} while (moreChangesAvailable);
}
The _SynchronizationState field is like a Cookie that contains some informations about your last sync process . So next time the thread will synchronize all the items since the last sync .
Hope it helps
I have application that host WCF service:
namespace ServiceLibrary
{
public delegate void StatusEventHandler(Capture capture);
// You have created a class library to define and implement your WCF service.
// You will need to add a reference to this library from another project and add
// the code to that project to host the service as described below. Another way
// to create and host a WCF service is by using the Add New Item, WCF Service
// template within an existing project such as a Console Application or a Windows
// Application.
[ServiceContract()]
public interface IService1
{
[OperationContract]
string ClientMsg(string str);
}
[ServiceBehavior(
ConcurrencyMode = ConcurrencyMode.Multiple,
InstanceContextMode = InstanceContextMode.PerSession)]
public class service1 : IService1
{
public event StatusEventHandler CapturingEvent;
public event StatusEventHandler OnProcessExitedEvent;
public event StatusEventHandler OnFinishEvent;
public string ClientMsg(string str)
{
return DoWork(str);
}
private DoWork(string str)
}
MyClass obj = New MyClass();
obj.Start(str); /// Do my job
}
}
}
The client send string to my server and i am opening instance of my class and this class open process, do my job and return to the client the process id number.
this server received messages from multiple clients so i wonder if i need to open new thread each time i received client message to avoid situation that several clients send message to the server in the same time.
This is how i am open the server connection in main form:
private void connect()
{
try
{
if (!isConnected)
{
// Returns a list of ipaddress configuration
IPHostEntry ips = Dns.GetHostEntry(Dns.GetHostName());
// Get machine ipaddress
IPAddress _ipAddress = IPAddress.Parse(tbServerIp.Text);
// Create the url that is needed to specify where the service should be started
urlService = "net.tcp://" + _ipAddress.ToString() + ":8000/CapturesService";
// Instruct the ServiceHost that the type that is used is a ServiceLibrary.service1
host = new ServiceHost(typeof(ServiceLibrary.service1));
//ServiceLibrary.service1 serviceInstance = new ServiceLibrary.service1();
//serviceInstance.CapturingEvent += serviceInstance_StartCapturingEvent;
//serviceInstance.OnProcessExitedEvent += serviceInstance_OnProcessExitedEvent;
//host = new ServiceHost(serviceInstance);
host.Opening += new EventHandler(host_Opening);
host.Opened += new EventHandler(host_Opened);
host.Closing += new EventHandler(host_Closing);
host.Closed += new EventHandler(host_Closed);
// The binding is where we can choose what transport layer we want to use. HTTP, TCP ect.
NetTcpBinding tcpBinding = new NetTcpBinding();
tcpBinding.TransactionFlow = false;
tcpBinding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
tcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
tcpBinding.Security.Mode = SecurityMode.None; // <- Very crucial
// Add a endpoint
host.AddServiceEndpoint(typeof(ServiceLibrary.IService1), tcpBinding, urlService);
// A channel to describe the service. Used with the proxy scvutil.exe tool
ServiceMetadataBehavior metadataBehavior;
metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (metadataBehavior == null)
{
// Create the proxy object that is generated via the svcutil.exe tool
metadataBehavior = new ServiceMetadataBehavior();
metadataBehavior.HttpGetUrl = new Uri("http://" + _ipAddress.ToString() + ":8001/CapturesService");
metadataBehavior.HttpGetEnabled = true;
metadataBehavior.ToString();
host.Description.Behaviors.Add(metadataBehavior);
urlMeta = metadataBehavior.HttpGetUrl.ToString();
}
host.Open();
isConnected = true;
}
else
{
if (asyncWorker.IsBusy)
{
// Notify the worker thread that a cancel has been requested.
// The cancel will not actually happen until the thread in the
// DoWork checks the bwAsync.CancellationPending flag, for this
// reason we set the label to "Cancelling...", because we haven't
// actually cancelled yet.
asyncWorker.CancelAsync();
}
host.Close();
isConnected = false;
}
}
catch (Exception ex)
{
isConnected = false;
MessageBox.Show(ex.Message);
return;
}
}
private int StartTsharkProcess(Capture capture)
{
ProcessExitedEvent += Capture_ProcessExitedEvent;
string args = string.Format("-i {0} host {1} {2} -a duration:300 -w {3}",
Interface.interfaceNumber,
capture.machineIpAddress,
getTsharkFilter(),
Path.Combine(LocalPath.localPath, capture.fileName));
int processId = InvokeProcess(WiresharkProcesses.Tshark, args);
return processId;
}
this server received messages from multiple clients so i wonder if i need to open new thread each time i received client message to avoid situation that several clients send message to the server in the same time.
ServiceBehavior attribute has ConcurrencyMode Property.
This property indicates whether an instance of a service can handle one thread or multiple threads that execute concurrently, and if single-threaded, whether reentrancy is supported.
The default service behavior has ConcurrencyMode with ConcurrencyMode.Single value. So, if it is necessary to allow multiple calls at once, please use ConcurrencyMode.Multiple with notice:
No synchronization guarantees are made. Because other threads can change your service object at any time, you must handle synchronization and state consistency at all times.
Note: if service methods perform long-running tasks, clients might have a timeout.
I want to send a message from a server to all clients. There are 0-* clients. The server may or may not be running when a client is loaded. The functionality here works how I want it. I am trying to figure out if this can be done without Thread.Sleep()? Also note that the clients and the server are each in independant processes.
Server Portion
class NamedEventsServer
{
internal static void Main()
{
const string ewhName = "StickyNoteEwh";
EventWaitHandle ewh = null;
bool doesNotExist = false;
bool wasCreated;
// Attempt to open the named event.
try
{
ewh = EventWaitHandle.OpenExisting(ewhName);
}
catch (WaitHandleCannotBeOpenedException)
{
Console.WriteLine("Named event does not exist.");
doesNotExist = true;
}
if (doesNotExist)
{
// The event does not exist, so create it.
ewh = new EventWaitHandle(true,
EventResetMode.ManualReset,
ewhName,
out wasCreated);
if (wasCreated)
{
Console.WriteLine("Created the named event.");
}
else
{
Console.WriteLine("Unable to create the event.");
return;
}
}
ewh.Set();
Thread.Sleep(1000);//wait one second...giving threads enough time to all respond. Then stop triggering the event.
ewh.Reset();
//exit
}
}
Client Portion
class NamedEventsClient
{
internal static void Main()
{
const string ewhName = "StickyNoteEwh";
while (true)
{
EventWaitHandle ewh = null;
bool doesNotExist = false;
bool wasCreated;
// Attempt to open the named event.
try
{
ewh = EventWaitHandle.OpenExisting(ewhName);
}
catch (WaitHandleCannotBeOpenedException)
{
Console.WriteLine("Named event does not exist.");
doesNotExist = true;
}
if (doesNotExist)
{
// The event does not exist, so create it.
ewh = new EventWaitHandle(false,
EventResetMode.ManualReset,
ewhName,
out wasCreated);
if (wasCreated)
{
Console.WriteLine("Created the named event.");
}
else
{
Console.WriteLine("Unable to create the event.");
return;
}
}
Console.WriteLine("Wait on the event.");
ewh.WaitOne();
Console.WriteLine("Event was signaled.");
//Console.WriteLine("Press the Enter key exit.");
Thread.Sleep(1000);
//Console.ReadLine();
}
}
}
I guess it depends whether you're certain all clients are going to get their time-slice within on second. It sounds reasonable, but under extreme stress some clients might miss it. How crucial is that?
Anyway, I think this is exactly the kind of thing you should use ZeroMQ for. It's light-weight, and takes care of all the potential bugs for you.
I want to do this
for (int i = 0; i < 100; i++ )
{
Byte[] receiveBytes = receivingUdpClient.Receive(ref RemoteIpEndPoint);
}
But instead of using UdpClient.Receive, I have to use UdpClient.BeginReceive. The problem is, how do I do that? There aren't a lot of samples using BeginReceive, and the MSDN example is not helping at all. Should I use BeginReceive, or just create it under a separate thread?
I consistently get ObjectDisposedException exception. I only get the first data sent. The next data will throw exception.
public class UdpReceiver
{
private UdpClient _client;
public System.Net.Sockets.UdpClient Client
{
get { return _client; }
set { _client = value; }
}
private IPEndPoint _endPoint;
public System.Net.IPEndPoint EndPoint
{
get { return _endPoint; }
set { _endPoint = value; }
}
private int _packetCount;
public int PacketCount
{
get { return _packetCount; }
set { _packetCount = value; }
}
private string _buffers;
public string Buffers
{
get { return _buffers; }
set { _buffers = value; }
}
private Int32 _counter;
public System.Int32 Counter
{
get { return _counter; }
set { _counter = value; }
}
private Int32 _maxTransmission;
public System.Int32 MaxTransmission
{
get { return _maxTransmission; }
set { _maxTransmission = value; }
}
public UdpReceiver(UdpClient udpClient, IPEndPoint ipEndPoint, string buffers, Int32 counter, Int32 maxTransmission)
{
_client = udpClient;
_endPoint = ipEndPoint;
_buffers = buffers;
_counter = counter;
_maxTransmission = maxTransmission;
}
public void StartReceive()
{
_packetCount = 0;
_client.BeginReceive(new AsyncCallback(Callback), null);
}
private void Callback(IAsyncResult result)
{
try
{
byte[] buffer = _client.EndReceive(result, ref _endPoint);
// Process buffer
MainWindow.Log(Encoding.ASCII.GetString(buffer));
_packetCount += 1;
if (_packetCount < _maxTransmission)
{
_client.BeginReceive(new AsyncCallback(Callback), null);
}
}
catch (ObjectDisposedException ex)
{
MainWindow.Log(ex.ToString());
}
catch (SocketException ex)
{
MainWindow.Log(ex.ToString());
}
catch (System.Exception ex)
{
MainWindow.Log(ex.ToString());
}
}
}
What gives?
By the way, the general idea is:
Create tcpclient manager.
Start sending/receiving data using udpclient.
When all data has been sent, tcpclient manager will signal receiver that all data has been sent, and udpclient connection should be closed.
It would seem that UdpClient.BeginReceive() and UdpClient.EndReceive() are not well implemented/understood. And certainly compared to how the TcpListener is implemented, are a lot harder to use.
There are several things that you can do to make using the UdpClient.Receive() work better for you. Firstly, setting timeouts on the underlying socket Client will enable control to fall through (to an exception), allowing the flow of control to continue or be looped as you like. Secondly, by creating the UDP listener on a new thread (the creation of which I haven't shown), you can avoid the semi-blocking effect of the UdpClient.Receive() function and you can effectively abort that thread later if you do it correctly.
The code below is in three parts. The first and last parts should be in your main loop at the entry and exit points respectively. The second part should be in the new thread that you created.
A simple example:
// Define this globally, on your main thread
UdpClient listener = null;
// ...
// ...
// Create a new thread and run this code:
IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 9999);
byte[] data = new byte[0];
string message = "";
listener.Client.SendTimeout = 5000;
listener.Client.ReceiveTimeout = 5000;
listener = new UdpClient(endPoint);
while(true)
{
try
{
data = listener.Receive(ref endPoint);
message = Encoding.ASCII.GetString(data);
}
catch(System.Net.Socket.SocketException ex)
{
if (ex.ErrorCode != 10060)
{
// Handle the error. 10060 is a timeout error, which is expected.
}
}
// Do something else here.
// ...
//
// If your process is eating CPU, you may want to sleep briefly
// System.Threading.Thread.Sleep(10);
}
// ...
// ...
// Back on your main thread, when it's exiting, run this code
// in order to completely kill off the UDP thread you created above:
listener.Close();
thread.Close();
thread.Abort();
thread.Join(5000);
thread = null;
In addition to all this, you can also check UdpClient.Available > 0 in order to determine if any UDP requests are queued prior to executing UdpClient.Receive() - this completely removes the blocking aspect. I do suggest that you try this with caution as this behaviour does not appear in the Microsoft documentation, but does seem to work.
Note:
The MSDN exmaple code you may have found while researching this problem requires an additional user defined class - UdpState. This is not a .NET library class. This seems to confuse a lot of people when they are researching this problem.
The timeouts do not strictly have to be set to enable your app to exit completely, but they will allow you to do other things in that loop instead of blocking forever.
The listener.Close() command is important because it forces the UdpClient to throw an exception and exit the loop, allowing Thread.Abort() to get handled. Without this you may not be able to kill off the listener thread properly until it times out or a UDP packet is received causing the code to continue past the UdpClient.Receive() block.
Just to add to this priceless answer, here's a working and tested code fragment. (Here in a Unity3D context but of course for any c#.)
// minmal flawless UDP listener per PretorianNZ
using System.Collections;
using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;
void Start()
{
listenThread = new Thread (new ThreadStart (SimplestReceiver));
listenThread.Start();
}
private Thread listenThread;
private UdpClient listenClient;
private void SimplestReceiver()
{
Debug.Log(",,,,,,,,,,,, Overall listener thread started.");
IPEndPoint listenEndPoint = new IPEndPoint(IPAddress.Any, 1260);
listenClient = new UdpClient(listenEndPoint);
Debug.Log(",,,,,,,,,,,, listen client started.");
while(true)
{
Debug.Log(",,,,, listen client listening");
try
{
Byte[] data = listenClient.Receive(ref listenEndPoint);
string message = Encoding.ASCII.GetString(data);
Debug.Log("Listener heard: " +message);
}
catch( SocketException ex)
{
if (ex.ErrorCode != 10060)
Debug.Log("a more serious error " +ex.ErrorCode);
else
Debug.Log("expected timeout error");
}
Thread.Sleep(10); // tune for your situation, can usually be omitted
}
}
void OnDestroy() { CleanUp(); }
void OnDisable() { CleanUp(); }
// be certain to catch ALL possibilities of exit in your environment,
// or else the thread will typically live on beyond the app quitting.
void CleanUp()
{
Debug.Log ("Cleanup for listener...");
// note, consider carefully that it may not be running
listenClient.Close();
Debug.Log(",,,,, listen client correctly stopped");
listenThread.Abort();
listenThread.Join(5000);
listenThread = null;
Debug.Log(",,,,, listener thread correctly stopped");
}
I think you should not use it in a loop but instead whenever the BeginReceive callback is called you call BeginReceive once more and you keep a public variable for count if you want to limit the number to 100.
have look at MSDN first. They provide good example.
http://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.beginreceive.aspx
I would do network communication on a background thread, so that it doesn't block anything else in your application.
The issue with BeginReceive is that you must call EndReceive at some point (otherwise you have wait handles just sitting around) - and calling EndReceive will block until the receive is finished. This is why it is easier to just put the communication on another thread.
You have to do network operations, file manipulations and such things that are dependent to other things rather than your own program on another thread (or task) because they may freeze your program. The reason for that is that your code executes sequentially.
You have used it in a loop which is not fine. Whenever BeginRecieve callback is invoked you should call it again. Take a look at the following code:
public static bool messageReceived = false;
public static void ReceiveCallback(IAsyncResult ar)
{
UdpClient u = (UdpClient)((UdpState)(ar.AsyncState)).u;
IPEndPoint e = (IPEndPoint)((UdpState)(ar.AsyncState)).e;
Byte[] receiveBytes = u.EndReceive(ar, ref e);
string receiveString = Encoding.ASCII.GetString(receiveBytes);
Console.WriteLine("Received: {0}", receiveString);
messageReceived = true;
}
public static void ReceiveMessages()
{
// Receive a message and write it to the console.
IPEndPoint e = new IPEndPoint(IPAddress.Any, listenPort);
UdpClient u = new UdpClient(e);
UdpState s = new UdpState();
s.e = e;
s.u = u;
Console.WriteLine("listening for messages");
u.BeginReceive(new AsyncCallback(ReceiveCallback), s);
// Do some work while we wait for a message. For this example,
// we'll just sleep
while (!messageReceived)
{
Thread.Sleep(100);
}
}