I am writing a client/server application in C# using WCF. All my testings went fine, but as soon as I deployed the service, I noticed random problems in communicating with the server. I enabled debugging and saw messages like this in the server:
The communication object, System.ServiceModel.Channels.S开发者_JAVA百科erverReliableDuplexSessionChannel, cannot be used for communication because it has been Aborted.
The pattern is like this:
- client is sending query
- service is processing the query
- service is sending something back
- Activity boundary is of level "Stop" - everything seems fine
- Add inactivityTimeout of reliable session to the datetime of last contact and you have the timestamp of the exception thrown by the service
The application goes like this: The service instance provides an API methods to interact with a database and is of type "netTcpBinding". Several clients (about 40) are connected and randomly calling methods from the service. The clients can stay open for several days, even without sending or receiving anything.
Here are the relevant bits:
Service:
[ServiceContract(CallbackContract = typeof(ISVCCallback), SessionMode = SessionMode.Required)]
[ExceptionMarshallingBehavior]
...
and
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext=true)]
public class SVCService : ISVC
...
Service configuration:
<behaviors>
<serviceBehaviors>
<behavior name="behaviorConfig">
<serviceMetadata httpGetEnabled="false" httpGetUrl="" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceThrottling maxConcurrentCalls="50" maxConcurrentSessions="1000"
maxConcurrentInstances="50" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="tcpBinding" closeTimeout="00:01:00" openTimeout="00:10:00"
receiveTimeout="23:59:59" sendTimeout="00:01:30" transferMode="Buffered"
listenBacklog="1000" maxBufferPoolSize="671088640" maxBufferSize="671088640"
maxConnections="1000" maxReceivedMessageSize="671088640" portSharingEnabled="true">
<readerQuotas maxStringContentLength="671088640" maxArrayLength="671088640"
maxBytesPerRead="671088640" />
<reliableSession inactivityTimeout="23:59:59" enabled="true" />
<security mode="None">
</security>
</binding>
</netTcpBinding>
</bindings>
Client configuration:
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_ISVC" closeTimeout="00:01:00" openTimeout="00:10:00"
receiveTimeout="23:59:59" sendTimeout="00:01:30" transactionFlow="false"
transferMode="Buffered" transactionProtocol="OleTransactions"
hostNameComparisonMode="StrongWildcard" listenBacklog="1000"
maxBufferPoolSize="671088640" maxBufferSize="671088640" maxConnections="1000"
maxReceivedMessageSize="671088640">
<readerQuotas maxStringContentLength="671088640" maxArrayLength="671088640"
maxBytesPerRead="671088640" />
<reliableSession ordered="true" inactivityTimeout="23:59:59"
enabled="true" />
<security mode="None">
<message clientCredentialType="Windows" />
</security>
</binding>
</netTcpBinding>
</bindings>
Is there anything wrong here? What is the best configuration for these kind of applications?
Update:
I encountered one thing:
In one service contract, I change something and notify all connected clients. It usually works fine, at least in my tests. But last "crash" or "freeze" I stepped through the log and saw that the latest function was where I use callback contracts to notify the clients.
What I want to do there: I save something to the database and at the end I notify all connected clients of the change. I think that the list of connected clients is not current anymore and it runs into a timeout at this step.
Now the question is how to avoid these timeouts.
- Should I use threading in the service? I think the threads will be killed as soon as the service call ends, am I right here?
- I could implement a static queue function which does all the callback-notification (this is something Marc_S suggested)
- Is there a way to reliably detect connection drops inside the server?
Just a wild thought: since your clients seem to be sending messages over a long period of times (days even), but don't seem to send them very frequently - could you maybe rearchitect your app to use something like a message queue instead of callback contracts?
Queues are a great way to decouple two systems and reduce potential for timeouts etc.
In a queue-based scenario, your clients would drop off a message into a queue (e.g. MSMQ which ships with every copy of Windows Server) and your service will listen on that incoming queue for messages. The service will grab the messages, process them, and typically put some kind of a response message back a second queue (to which the clients then listen).
The main benefits here are:
- you don't need the rather brittle and complicated call-back contract setup
- your systems are decoupled and will continue to work even if the connection between them is broken for a few seconds, minutes, hours
- your service can respond to incoming messages and send back more "targetted" responses, e.g. certain clients can listen on the "normal" response queue, others on a "priority" response queue etc. - the system just gives you more flexibility IMO
See more resources:
- Queues in Windows Communication Foundation
- Foundations: build a queued WCF response service
- WCF and MSMQ - take a message
- Message queueing in WCF
- Best practices for Queued Communication
and check out things like
- NServiceBus
and other systems (MassTransit et. al.) which favor queued communications to provide seriously scalable, reliable messaging between systems.
Ok, problem is solved.
The problem occurred whenever one client died unexpectedly withouth logging out. So whenever a client called a method from the service which itself wanted to broadcast a message to all connected clients, it ran into a timeout which in turn blocked the client. So wrapping all callbacks up into a delegate function helped to solve the problem.
Here is how I did it:
public enum CallbackType
{
callbackfunc1,
callbackfunc2,
callbackfunc3
};
public class CallbackEventArgs : EventArgs
{
public ISVCCallback callback;
public CallbackType type;
public string s1;
public string s2;
public string s3;
public List<string> ls1;
}
// Declare delegate
public delegate void SVCEventHandler(object sender, CallbackEventArgs e);
... now in the service definition
public string Login(...)
{
Client cl = new Client(MyCallbackHandler);
....
CallbackEventArgs ce = new CallbackEventArgs();
ce.callback = cl.CallbackChannel;
ce.type = CallbackType.callbackfunc1;
ce.s1 = "Parameter A";
ce.s2 = "Parameter B";
cl.MyCallBackHandler.BeginInvoke(this, ce, new AsyncCallback(EndAsync), null);
}
private void MyCallbackHandler(object sender, CallbackEventArgs e)
{
try
{
switch (e.type)
{
case CallbackType.callbackfunc1:
e.callback.callbackfunc1(e.s1, e.s2);
break;
default:
throw new MissingFieldException(e.type);
}
}
catch(TimeoutException tex){
// Remove current client, channel timed out
}
catch(Exception ex){
// Do something
}
}
private void EndAsync(IAsyncResult iar)
{
SVCEventHandler d = null;
try
{
System.Runtime.Remoting.Messaging.AsyncResult asres = (System.Runtime.Remoting.Messaging.AsyncResult)iar;
d = ((SVCEventHandler)asres.AsyncDelegate);
d.EndInvoke(ar);
}
catch(Exception ex)
{
// Do something
}
}
Thanks!!
精彩评论