I have a simple WCF duplex TCP service that I am trying to stop programmatically. If I don't have any connected users, ServiceHost.Close() is very quick but if I even have one connected user, I find the Close() function to take quite a bit of time, sometimes >30seconds. Is this usual behavior?
On the other hand, Abort() is almost instantaneous and I am tempted to use that instead.
It may be. The docs state that
The Close method allows any unfinished
work to be completed before returning.
For example, finish sending any
buffered messages.
There is an overload to Close() which takes a TimeSpan (and throws if the timespan is exceeded)
Abort() looks like the best way to stop a WCF host without delay.
Make sure you're closing the client-side connection, like this:
var channel = factory.CreateChannel();
var channel.DoSomethingForMe();
(channel as ICommunicationObject).Close();
If you don't do this Close() on the channel, Close() on the server waits a very, very long time, even if you specify a short timeout.
If you are ok with killing any in-progress service calls then Abort() is the way to go. Close() is the polite way of closing down a service.
I could see the benefit of Abort() over Close() for expediency, but I would imagine something bad may happen. In my case, I want to wait for Close() so I can reuse the port(s). This code will wait for the services to be actually closed before resuming.
Semaphore cont=new Semaphore(0, 1);
foreach (ServiceHost s in WCFServices) {
try {
s.Closed += delegate(Object o, System.EventArgs n) {
cont.Release();
};
s.Close();
cont.WaitOne();
} catch (Exception) {
}//try
}//for
I noticed this problem as well.
My code looks originally looked like this :
[TestMethod]
[Timeout(2000)]
public void ApiClientTest()
{
bool ApiSuccessSet, ClientSuccessSet = ApiSuccessSet = false;
Api apiService = new ApiTestService();
var clientService = new ClientTestService();
ServiceHost clientHost = new ServiceHost(clientService, new Uri(PipeService));
ServiceHost apiHost = new ServiceHost(apiService, new Uri(PipeService));
//To let us know the services were successfully opened
clientHost.Opened += (s, e) => ClientSuccessSet = true;
apiHost.Opened += (s, e) => ApiSuccessSet = true;
clientHost.AddServiceEndpoint(typeof(IClientService), new NetNamedPipeBinding(), ClientPipeServiceName);
apiHost.AddServiceEndpoint(typeof(IApiService), new NetNamedPipeBinding(), ApiPipeServiceName);
clientHost.BeginOpen(OnOpen, clientHost);
apiHost.BeginOpen(OnOpen, apiHost);
//This allows both services to be open for communication.
while (!ApiSuccessSet || !ClientSuccessSet)
Thread.Yield();
EndpointAddress ApiEndpoint = new EndpointAddress(PipeService + #"/" + ApiPipeServiceName);
EndpointAddress clientEndpoint = new EndpointAddress(PipeService + #"/" + ClientPipeServiceName);
InstanceContext context = new InstanceContext((IClientCallback)new TestClientCallback());
var ClientChannelFactory = new DuplexChannelFactory<IClientService>(context, new NetNamedPipeBinding(), clientEndpoint);
var ApiChannelFactory = new ChannelFactory<IApiService>(new NetNamedPipeBinding(), ApiEndpoint);
var ClientChannel = ClientChannelFactory.CreateChannel();
var ApiChannel = ApiChannelFactory.CreateChannel();
clientHost.Close();
apiHost.Close();
}
void OnOpen(IAsyncResult ar)
{
ServiceHost service = (ServiceHost)ar.AsyncState;
service.EndOpen(ar);
}
I noticed that the this code took 20 secondsto run. I then decided to close the channel factories like this :
[TestMethod]
[Timeout(2000)]
public void ApiClientTest()
{
bool ApiSuccessSet, ClientSuccessSet = ApiSuccessSet = false;
Api apiService = new ApiTestService();
var clientService = new ClientTestService();
ServiceHost clientHost = new ServiceHost(clientService, new Uri(PipeService));
ServiceHost apiHost = new ServiceHost(apiService, new Uri(PipeService));
//To let us know the services were successfully opened
clientHost.Opened += (s, e) => ClientSuccessSet = true;
apiHost.Opened += (s, e) => ApiSuccessSet = true;
clientHost.AddServiceEndpoint(typeof(IClientService), new NetNamedPipeBinding(), ClientPipeServiceName);
apiHost.AddServiceEndpoint(typeof(IApiService), new NetNamedPipeBinding(), ApiPipeServiceName);
clientHost.BeginOpen(OnOpen, clientHost);
apiHost.BeginOpen(OnOpen, apiHost);
//This allows both services to be open for communication.
while (!ApiSuccessSet || !ClientSuccessSet)
Thread.Yield();
EndpointAddress ApiEndpoint = new EndpointAddress(PipeService + #"/" + ApiPipeServiceName);
EndpointAddress clientEndpoint = new EndpointAddress(PipeService + #"/" + ClientPipeServiceName);
InstanceContext context = new InstanceContext((IClientCallback)new TestClientCallback());
var ClientChannelFactory = new DuplexChannelFactory<IClientService>(context, new NetNamedPipeBinding(), clientEndpoint);
var ApiChannelFactory = new ChannelFactory<IApiService>(new NetNamedPipeBinding(), ApiEndpoint);
var ClientChannel = ClientChannelFactory.CreateChannel();
var ApiChannel = ApiChannelFactory.CreateChannel();
ClientChannelFactory.Close();
ApiChannelFactory.Close();
clientHost.Close();
apiHost.Close();
}
This leads me to believe that the long time it takes is disposing the client's instance context.
I suspect that there are 3 ways to handle this solution better.
The first is to create a function on the client that manages ending the session. This way you can call that method before the service plans to shut down and it will speed up the shut down time.
the second is to close asynchronously and do other processing while the connection is closing.
The third is to program into the client when to close the connection (especially if you control both the client and the service) so that the client can terminate the session itself and the service can shut down gracefully and quickly.
Related
We are using RabbitMQ for queuing messages in C# .Net (EasyNetQ Client).
i want one consumer app (C# Console App) listen to one queue and provide multiple handlers for each message type.
I implemented this scenario and my code is here :
using (var advancedBus = RabbitHutch.CreateBus("host=localhost;prefetchcount=100")
.Advanced)
{
var queue = advancedBus.QueueDeclare("MyQueue");
advancedBus.Consume(queue, x => x
.Add<MessageType1>((message, info) =>
{
Console.WriteLine("MessageType1 Body : " + message.Body.Body);
})
.Add<MessageType2>((message, info) =>
{
Console.WriteLine(" MessageType2 Body: " + message.Body.Body);
}).ThrowOnNoMatchingHandler = false);
}
My Problem :
But when i execute this consumer it does nothing. do not any thing happen.
i publish messages to that queue like this :
using (var advancedBus = RabbitHutch.CreateBus("host=localhost").Advanced)
{
var queue = advancedBus.QueueDeclare("MyQueue");
if (advancedBus.IsConnected)
advancedBus.Publish(Exchange.GetDefault(), queue.Name, false, false,
new Message<MessageType1>(change));
else
result = false;
}
What is the problem.
Ok, after testing this code, these are the problems:
First of all, you're disposing your advancedBus right after you register for consumption. You need to remember that when you invoke IAdvanceBus.Consume, you're only registering a callback for each message. If you dispose the bus immediately after registration, your delegate can't be invoked since the connection was already closed. So, you'll to remove the using statement around the rabbit declaration (don't forget to dispose it when you're done):
var advancedBus = RabbitHutch.CreateBus("host=localhost;prefetchcount=100").Advanced
Second, the immediate flag has been deprecated and shouldn't be used, the message doesn't seem to be getting to the queue. Change Publish to:
advancedBus.Publish(Exchange.GetDefault(), queue.Name, true, false,
new Message<MessageType1>(change));
Also, if you're running this from a console application, don't forget to use Console.ReadKey so your main thread doesn't terminate.
Here's a working code sample:
static void Main()
{
var change = new MessageType1();
var advancedBus = RabbitHutch.CreateBus("host=localhost").Advanced;
ConsumeMessage(advancedBus);
var queue = advancedBus.QueueDeclare("MyQueue");
if (advancedBus.IsConnected)
{
advancedBus.Publish(Exchange.GetDefault(), queue.Name, true, false,
new Message<MessageType1>(change));
}
else
{
Console.WriteLine("Can't connect");
}
Console.ReadKey();
}
private static void ConsumeMessage(IAdvancedBus advancedBus)
{
var queue = advancedBus.QueueDeclare("MyQueue");
advancedBus.Consume(queue, registration =>
{
registration.Add<MessageType1>((message, info) =>
{
Console.WriteLine("Body: {0}", message.Body);
});
});
}
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've written some code that uses named pipes to send a string from one application to another. It works fine once, but when the client tries to send the to the server application for a second time it freezes when it tries to connect to the client.
The server code is this:
static void StartServer()
{
Task.Factory.StartNew(() =>
{
var server = new NamedPipeServerStream("MyPipe");
server.WaitForConnection();
StreamReader reader = new StreamReader(server);
while (true)
{
var line = reader.ReadLine();
if (line != null)
{
System.Windows.Forms.MessageBox.Show("Data: : " + line);
}
}
});
}
The client code is:
private void Test()
{
using (var client = new NamedPipeClientStream("MyPipe"))
{
client.Connect();
StreamWriter writer = new StreamWriter(client);
writer.WriteLine("INCOMING:1234567");
writer.Flush();
client.Dispose();
}
}
Tracking the code through, I can see that loop in the server code is continuously checking for any lines being read in but not finding any. The client is hanging on the client.Connect() call when the Test() method is called for a second time. No exceptions are raised.
Can anyone see where I'm going wrong?
Your server stays connected to the first pipe instance used to send the first string.
However, your client is disposing its pipe instance after each test, so the second test creates and tries to connect on a new pipe instance, but the server is no longer listening, so the Connect call blocks waiting for a server.
You need to either:
make your server multithreaded, so that it continues listening while servicing instances already connected; or
Refactor your client so that it connects once, then reuses that connected instance.
Following on from what #Chris Dickson answered, I solved my problem with the following code:
Task.Factory.StartNew(() =>
{
var server = new NamedPipeServerStream("MyPipe", PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
StreamReader reader = new StreamReader(server);
Boolean connectedOrWaiting = false;
while (true)
{
if (!connectedOrWaiting)
{
server.BeginWaitForConnection((a) => { server.EndWaitForConnection(a); }, null);
connectedOrWaiting = true;
}
if (server.IsConnected)
{
var line = reader.ReadLine();
if (line != null)
System.Windows.Forms.MessageBox.Show("Data: : " + line);
server.Disconnect();
connectedOrWaiting = false;
}
}
});
We're building a WCF server (.NET 4.0). It will only use net.tcp transport.
When a client closes the TCP connection, the server gets unhandled CommunicationException, and terminates.
Q1. How do I handle the CommunicationException so the server does not terminate and continues serving other clients?
Q2. In the handler, how do I get SessionId of the session that was aborted? I need this to do clean up some session-specific data.
Thanks in advance!
P.S. The connection is over the Internet, so the socket may be closed anytime, regardless on whether the client disconnects gracefully, or not.
Any WCF channel implements ICommunicationObject , which provides events for the channel lifetime.
You should listen to the Faulted event
The sessionId can be accessed as always from the OperationContext.Current property.
When your client open the channel (on the first operation), register to the adequate events:
OperationContext.Current.Channel.Faulted += new EventHandler(Channel_Faulted);
OperationContext.Current.Channel.Closed += new EventHandler(Channel_Faulted);
and:
void Channel_Faulted(object sender, EventArgs e)
{
Logout((IContextChannel)sender);
}
protected void Logout(IContextChannel channel)
{
string sessionId = null;
if (channel != null)
{
sessionId = channel.SessionId;
}
[...]
}
ICommunicationObject obj = (ICommunicationObject)callback;
obj.Closed += new EventHandler((a, b) =>
{
if (list.Exists(cli => cli.CallbackService == (ITecnobelRemoteServiceCallback)a))
{
var query = (from cc in list where cc.CallbackService == (ITecnobelRemoteServiceCallback)a select cc).ToList();
query.ForEach(
delegate (Client ccc)
{
ccc.CallbackService = null;
});
}
});
I need to make some connections on startup of a server. I'm using the wcf technology for this client-server application. The problem is that the constructor of the server isn't called at any time, so for the moment, i initialize the connections when the first client makes a connection. But this generates problems in a further part.
This is my server setup:
private static ServiceHost _svc;
static void Main(string[] args)
{
NetTcpBinding binding = new NetTcpBinding(SecurityMode.Message);
Uri address = new Uri("net.tcp://localhost:8000");
_svc = new ServiceHost(typeof(MonitoringSystemService), address);
publishMetaData(_svc, "http://localhost:8001");
_svc.AddServiceEndpoint(typeof(IMonitoringSystemService), binding, "Monitoring Server");
_svc.Open();
Console.WriteLine("Listener service gestart op net.tcp://localhost:8000/Monitoring");
Console.ReadLine();
}
private static void publishMetaData(ServiceHost svc, string sEndpointAddress)
{
ServiceMetadataBehavior smb = svc.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb != null)
{
smb.HttpGetEnabled = true;
smb.HttpGetUrl = new Uri(sEndpointAddress);
}
else
{
smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.HttpGetUrl = new Uri(sEndpointAddress);
svc.Description.Behaviors.Add(smb);
}
}
How can i start the server without waiting for a client to logon so i can initialize it.
Thanks in advance.
WCF will instantiate your MonitoringSystemService class as needed. It won't instantiate it until the first client makes a connection, and if you get a lot of client connections all at once, it will instantiate a few MonitoringSystemServices to deal with the load.
You can disable this behaviour, and instead just use one instance of MonitoringSystemService that gets created when your program starts. Instead of telling WCF which type it should be automatically instantiating, you just instantiate it yourself and pass it in:
_svc = new ServiceHost(new MonitoringSystemService()), address);
You gain control of when the MonitoringSystemService contructor runs, at the expense of scalability.
Alternatively (if you need the scalability), you could "initialize the connections" in your Main method, but be aware that WCF could instantiate multiple MonitoringSystemServices that would need to share those connections.
There are two ways I can immediately think of.
One - you can implement your solution as windows service
and Second - let a dummy client program call your server at start-up.