I have a duplex WCF channel used in a publish/subscribe pattern. I can not figure out how to cleanly disconnect clients without producing an error in the tracing. The error does not affect the application but shows up in the tracing logs. Since this expected error message is filling up our logs it is hard to figure other issues.
Error that shows up in the server side logs :
"An existing connection was forcibly closed by the remote host"
System.ServiceModel.Channels.SocketConnection.HandleReceiveAsyncCompleted()
System.ServiceModel.Channels.SocketConnection.OnReceiveAsync(Object sender, SocketAsyncEventArgs eventArgs)
System.Net.Sockets.SocketAsyncEventArgs.FinishOperationAsyncFailure(SocketError socketError, Int32 bytesTransferred, SocketFlags flags)
System.Net.Sockets.SocketAsyncEventArgs.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
server code:
public static void StartHost()
{
var startTime = DateTime.Now;
var duplex = new ServiceHost(typeof(HostChannel));
try
{
duplex.AddServiceEndpoint(typeof(IHostChannel),
CreateTpcBinding(),
AppConfiguration.SchedulerHostChannel);
duplex.Open();
_duplex = duplex;
}
catch (Exception e)
{
log.LogError("Error acquiring HostChannel:" + e);
if (duplex.State == CommunicationState.Faulted)
duplex.Abort();
duplex.Close();
throw;
}
}
private static NetTcpBinding CreateTpcBinding()
{
return new NetTcpBinding()
{
ReliableSession = new OptionalReliableSession() { InactivityTimeout = TimeSpan.FromDays(5) },
ReceiveTimeout = TimeSpan.FromDays(5),
ReaderQuotas = new XmlDictionaryReaderQuotas() { MaxStringContentLength = 5242880 }
};
}
/// <summary>
/// First method the client calls
/// </summary>
public void EstablishConnection()
{
var channelBinder = ServiceLocator.Current.GetInstance<RemoteResourceManager>();
var remoteChannel = OperationContext.Current.GetCallbackChannel<IRemoteChannel>();
_processHandle = channelBinder.RegisterRemoteChannel(remoteChannel);
}
/// <summary>
/// Last method that is called
/// </summary>
public void NotifyComplete()
{
_processHandle.Events.OnCompleted(_processHandle.RemoteChannel, new EventArgs());
log.LogInfo("Try to clean callback channel");
((IClientChannel)_processHandle.RemoteChannel).Close();
((IClientChannel)OperationContext.Current.GetCallbackChannel<IRemoteChannel>()).Close();
log.LogInfo("Cleaned callback channel");
}
Client callback interface :
public interface IRemoteChannel
{
[OperationContract(IsOneWay = true)]
void StartTask(TaskDefinition taskDefinition);
[OperationContract(IsOneWay = true)]
void RequestCancelTask();
[OperationContract(IsOneWay = true)]
void HealthCheck();
}
Client clean up code after calling NotifyComplete
if (_proxy is IClientChannel)
channel = ((IClientChannel)_proxy);
try
{
if (channel != null)
{
if (channel.State != CommunicationState.Faulted)
{
channel.Close();
}
else
{
channel.Abort();
}
}
}
catch (CommunicationException e)
{
channel.Abort();
}
catch (TimeoutException e)
{
channel.Abort();
}
catch (Exception e)
{
channel.Abort();
throw;
}
finally
{
_proxy = null;
}
Related
The addMessage method asynchronously sends messages to clients.
The problem is that those who send it receive it themselves
That is, the client hears himself
public void AddMessage(byte[] message)
{
IAsyncMessageCallback other = OperationContext.Current.GetCallbackChannel<IAsyncMessageCallback>();
other.BeginOnMessageAdded(message, DateTime.Now, delegate (IAsyncResult ar)
{
other.EndOnMessageAdded(ar);
}, null);
}
I tried to implement it in the following way:
public void AddMessage(byte[] message)
{
lock (subscribers)
{
var connection = OperationContext.Current.GetCallbackChannel<IAsyncMessageCallback>();
string user;
if (!subscribers.TryGetValue(connection, out user))
return;
foreach (var other in subscribers.Keys)
{
//Console.WriteLine(other + " " + connection);
if (other == connection)
continue;
Console.WriteLine(other + " " + connection);
other.BeginOnMessageAdded(message, DateTime.Now, delegate (IAsyncResult ar)
{
other.EndOnMessageAdded(ar);
}, null);
Console.WriteLine(message.Length);
}
}
}
But this leads to terrible brakes
Thank you in advance
The essence of the problem:
1.When more than two users enter, the chat starts to hang (a very long delay appears), and then the connection is broken: The communication object System.ServiceModel.Channels.ServiceChannel cannot be used for communication, since its operation is interrupted
If you remove the foreach loop, there will be no breaks and the chat will work calmly with ten users, but the message will be sent to everyone (that is, I will hear my voice in the chat)
Everything works, but with two users. If more come in then System.ServiceModel.Channels.ServiceChannel cannot be used for communication, since its operation is interrupted
public void SendMessage(byte[] message)
{
var connection = OperationContext.Current.GetCallbackChannel<IChatClient>();
string user;
if (!_users.TryGetValue(connection, out user))
return;
foreach (var other in _users.Keys)
{
if (other == connection)
continue;
other.RecievMessage(user, message);
Console.WriteLine(message.Length);
}
}
If you remove the foreach loop, ten users can come in, but they will hear themselves because the server sends this message to everyone indiscriminately.
if the person who sent the video is the current subscriber they don't send the video to THAT subscriber.
How to implement it?
public class StackOverflow_5979252
{
[ServiceContract(Name = "IMessageCallback")]
public interface IAsyncMessageCallback
{
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginOnMessageAdded(string msg, DateTime timestamp, AsyncCallback callback, object asyncState);
void EndOnMessageAdded(IAsyncResult result);
}
[ServiceContract(CallbackContract = typeof(IAsyncMessageCallback))]
public interface IMessage
{
[OperationContract]
void AddMessage(string message);
}
[ServiceBehavior(IncludeExceptionDetailInFaults = true, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service : IMessage
{
public void AddMessage(string message)
{
IAsyncMessageCallback callback = OperationContext.Current.GetCallbackChannel<IAsyncMessageCallback>();
callback.BeginOnMessageAdded(message, DateTime.Now, delegate(IAsyncResult ar)
{
callback.EndOnMessageAdded(ar);
}, null);
}
}
class MyClientCallback : IAsyncMessageCallback
{
public IAsyncResult BeginOnMessageAdded(string msg, DateTime timestamp, AsyncCallback callback, object asyncState)
{
Action<string, DateTime> act = (txt, time) => { Console.WriteLine("[{0}] {1}", time, txt); };
return act.BeginInvoke(msg, timestamp, callback, asyncState);
}
public void EndOnMessageAdded(IAsyncResult result)
{
Action<string,DateTime> act = (Action<string,DateTime>)((System.Runtime.Remoting.Messaging.AsyncResult)result).AsyncDelegate;
act.EndInvoke(result);
}
}
static Binding GetBinding()
{
return new NetTcpBinding(SecurityMode.None);
}
public static void Test()
{
string baseAddress = "net.tcp://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(IMessage), GetBinding(), "");
host.Open();
Console.WriteLine("Host opened");
InstanceContext instanceContext = new InstanceContext(new MyClientCallback());
DuplexChannelFactory<IMessage> factory = new DuplexChannelFactory<IMessage>(instanceContext, GetBinding(), new EndpointAddress(baseAddress));
IMessage proxy = factory.CreateChannel();
proxy.AddMessage("Hello world");
Console.Write("Press ENTER to close the host");
Console.ReadLine();
((IClientChannel)proxy).Close();
factory.Close();
host.Close();
}
}
We're building a WCF server (.NET 4.5). It will only use net.pipe transport.
When a client closes the PIPE 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!
contract
[ServiceContract(CallbackContract = typeof(IContractCallback))]
public interface IContractServer
{
[OperationContract(IsOneWay = true)]
void Connect(bool status);
[OperationContract(IsOneWay = false)]
void Disconnect(IContractServer _channelCallback);
[OperationContract(IsOneWay = true)]
void Play(bool status);
}
service
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Service : IContractServer
{
public List<IContractCallback> _channeList = new List<IContractCallback>();
public void Connect(bool status)
{
IContractCallback a = OperationContext.Current.GetCallbackChannel<IContractCallback>();
int call = 0;
foreach (var callBack in _channeList)
{
if (callBack == a)
{
call++;
}
}
if (call == 0)
{
_channeList.Add(a);
a.ConnectCallback(true);
}
else
{
a.ConnectCallback(false);
}
}
public void Disconnect(IContractServer _channelCallback)
{
foreach (var contractCallback in _channeList)
{
if (contractCallback == _channelCallback)
{
_channeList.Remove(contractCallback);
}
}
}
public void Play(bool status)
{
foreach (var contractCallback in _channeList)
{
contractCallback.PlayCallback(status);
}
}
}
client
using System.ComponentModel;
using System.ServiceModel;
using System.Windows;
using Host;
namespace VideoPlayer
{
public partial class MainWindow : Window, IContractCallback
{
private IContractServer Proxy = null;
public MainWindow()
{
InitializeComponent();
InstanceContext context = new InstanceContext(this);
DuplexChannelFactory<IContractServer> factory = new DuplexChannelFactory<IContractServer>(context, new NetNamedPipeBinding(), "net.pipe://localhost");
Proxy = factory.CreateChannel();
Proxy.Connect(true);
}
public void ConnectCallback(bool status)
{
MessageBox.Show(status ? "connected" : "no connected");
}
public void PlayCallback(bool status)
{
if (status)
{
MessageBox.Show("status true");
}
else
{
MessageBox.Show("status false");
}
}
private void ButtonPlay(object sender, RoutedEventArgs e)
{
Proxy.Play(true);
}
private void MainWindow_OnClosing(object sender, CancelEventArgs e)
{
//хочу отправить сообщение о закрытии
Proxy.Disconnect(Proxy);
}
I faced with this problem before in my duplex services when an event raised from server side the exception occurred if there was no alive channel between client and server so server dropped to Fault state and all next requests won't be responded,
For this reason I came to this conclusion to check the channel and if it was alive then let call back methods to be raised.
In service side the trick would be like ↓
bool IsChannelAlive()
{
Logging logging = new Logging(LogFile);
try
{
if (((ICommunicationObject)_callbackChannel).State == CommunicationState.Opened)
{
logging.Log(LoggingMode.Prompt, "Channeld is still alive, can raise events...");
return true;
}
}
catch (Exception exp)
{
logging.Log(LoggingMode.Error, "IsChannelAlive()=> failed, EXP: {0}", exp);
}
logging.Log(LoggingMode.Warning, "Channeld is not alive so events won't raised...");
return false;
}
and in one of my events I use it like :
void stran_OperationTimedOut(object sender, EventArgs e)
{
if (IsChannelAlive())
_callbackChannel.OnOperationTimedOut();
}
But for a while I use this trick to know closed channel to do something:
public ImportService()
{
//Handle ContextClose to Audit all actions made in session
OperationContext.Current.InstanceContext.Closed += delegate
{
//Here
};
}
which is not reliable.
I am still using that IsAliveChannel() in my services.
Hope this answer resolve your problem or give you the clue.
I have a wcf callback programm and it can send a message to a client.
If i try to send a second message the whole programm freezes and i get a timeoutexception.
Here is the servercode:
public void SendMessageToClient(string computerName, string message)
{
foreach (var session in connectedClients.Values)
{
if (session.ComputerName == computerName)
{
var asyncResult = session.Callback.BeginOnMessageReceived(message, new AsyncCallback(OnPushMessageComplete), session.Callback);
if (asyncResult.CompletedSynchronously)
CompletePushMessage(asyncResult);
}
}
}
void OnPushMessageComplete(IAsyncResult asyncResult)
{
CompletePushMessage(asyncResult);
}
void CompletePushMessage(IAsyncResult asyncResult)
{
var callbackChannel = (IServiceCallback)asyncResult.AsyncState;
try
{
callbackChannel.EndOnMessageReceived(asyncResult);
}
catch { }
}
And this is the Callbackinterface:
[OperationContract(IsOneWay = true, AsyncPattern = true)]
IAsyncResult BeginOnMessageReceived(string message, AsyncCallback acb, object state);
void EndOnMessageReceived(IAsyncResult iar);
And this is the client code:
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
public class ServiceHandler : IServiceCallback
{
public delegate void MessageReceivedHandler(string message);
public event MessageReceivedHandler OnMessageReceivedEvent;
public void OnMessageReceived(string message)
{
if (this.OnMessageReceivedEvent != null)
this.OnMessageReceivedEvent(message);
}
}
void callback_OnMessageReceivedEvent(string message)
{
setlb_info(message)
}
public void setlb_info(string wert)
{
if (this.lb_info.InvokeRequired)
{
setlb_infoCallback d = new setlb_infoCallback(setlb_info);
this.Invoke(d, new object[] { wert });
}
else
{
this.lb_info.Text = wert;
}
}
And if i try this:
Service.CurrentInstance.SendMessageToClient(client_name, message);
the client will get the message but if i call the same method a second time i get the timeoutexception (which is set to 1 minute).
I'm using code from this project(german):
http://www.flexbit.at/blog/wcf-duplex-zwischen-windows-sevice-und-gui-frontend/
I hope someone can help me because i can't finish my work if this function won't work.
Best regards
EDIT: I forgot a code on the client side:
var callback = new ServiceHandler();
callback.OnMessageReceivedEvent += new ServiceHandler.MessageReceivedHandler(callback_OnMessageReceivedEvent);
var callbackInstanceContext = new InstanceContext(callback);
client = new ServiceClient(callbackInstanceContext);
client.Subscribe(System.Environment.MachineName);
Try change
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
to
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
My code at the moment looks like this:
Server side:
#region IClientCallback interface
interface IClientCallback
{
[OperationContract(IsOneWay = true)]
void ReceiveWcfElement(WcfElement wcfElement);
}
#endregion
#region IService interface
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IClientCallback))]
interface IService
{
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void ReadyToReceive(string userName, int source, string ostatniTypWiadomosci);
[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = false)]
bool SendWcfElement(WcfElement wcfElement);
[OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]
List<int> Login(Client name, string password, bool isAuto, bool isSuperMode);
}
#endregion
#region Public enums/event args
public delegate void WcfElementsReceivedFromClientEventHandler(object sender, WcfElementsReceivedFromClientEventArgs e);
public class WcfElementsReceivedFromClientEventArgs : EventArgs
{
public string UserName;
}
public class ServiceEventArgs : EventArgs
{
public WcfElement WcfElement;
public Client Person;
}
#endregion
#region Service
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service : IService
{
#region Instance fields
//thread sync lock object
private static readonly Object SyncObj = new Object();
//callback interface for clients
IClientCallback _callback;
//delegate used for BroadcastEvent
public delegate void ChatEventHandler(object sender, ServiceEventArgs e);
public static event ChatEventHandler ChatEvent;
private ChatEventHandler _myEventHandler;
//holds a list of clients, and a delegate to allow the BroadcastEvent to work
//out which chatter delegate to invoke
static readonly Dictionary<Client, ChatEventHandler> Clients = new Dictionary<Client, ChatEventHandler>();
//current person
private Client _client;
#endregion
#region Helpers
private bool CheckIfPersonExists(string name)
{
return Clients.Keys.Any(p => p.UserName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
private ChatEventHandler getPersonHandler(string name)
{
foreach (var c in Clients.Keys.Where(c => c.UserName.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
ChatEventHandler chatTo;
Clients.TryGetValue(c, out chatTo);
return chatTo;
}
return null;
}
private Client GetPerson(string name)
{
return Clients.Keys.FirstOrDefault(c => c.UserName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
#endregion
#region IService implementation
public List<int> Login(Client client, string password, bool isAuto, bool isSuperMode)
{
if (client.ElementsVersions == null)
{
client.ElementsVersions = new WcfElement(WcfElement.RodzajWiadomosci.VersionControl, client.UserName);
}
//create a new ChatEventHandler delegate, pointing to the MyEventHandler() method
_myEventHandler = MyEventHandler;
lock (SyncObj)
{
if (!CheckIfPersonExists(client.UserName))
{
_client = client;
Clients.Add(client, _myEventHandler);
}
else
{
_client = client;
foreach (var c in Clients.Keys.Where(c => c.UserName.Equals(client.UserName)))
{
ChatEvent -= Clients[c];
Clients.Remove(c);
break;
}
Clients[client] = _myEventHandler;
}
_client.LockObj = new object();
}
_callback = OperationContext.Current.GetCallbackChannel<IClientCallback>();
ChatEvent += _myEventHandler;
var rValue = isAuto ? bazaDanych.Login(client.UserName, isSuperMode) : bazaDanych.Login(client.UserName, password);
return rValue;
}
public void PerformDataSync(Client c)
{
WcfElement wcfDelete = null;
WcfElement wcfUpdate = null;
//...
//this method prepares elements for client
//when done it adds them to clients queue (List<WcfElement)
try
{
var counter = 0;
if (wcfDelete != null)
{
foreach (var wcf in WcfElement.SplitWcfElement(wcfDelete, false))//split message into small ones
{
c.AddElementToQueue(wcf, counter++);
}
}
if (wcfUpdate != null)
{
foreach (var wcf in WcfElement.SplitWcfElement(wcfUpdate, true))
{
c.AddElementToQueue(wcf, counter++);
}
}
SendMessageToGui(string.Format("Wstępna synchronizacja użytkownika {0} zakończona.", c.UserName));
c.IsSynchronized = true;
}
catch (Exception e)
{
}
}
private void SendMessageToClient(object sender, EventArgs e)
{
var c = (Client) sender;
if (c.IsReceiving || c.IsSending)
{
return;
}
c.IsReceiving = true;
var wcfElement = c.GetFirstElementFromQueue();
if (wcfElement == null)
{
c.IsReceiving = false;
return;
}
Clients[c].Invoke(this, new ServiceEventArgs { Person = c, WcfElement = wcfElement });
}
public void ReadyToReceive(string userName)
{
var c = GetPerson(userName);
c.IsSending = false;
c.IsReceiving = false;
if (c.IsSynchronized)
{
SendMessageToClient(c, null);
}
else
{
PerformDataSync(c);
}
}
public bool SendWcfElement(WcfElement wcfElement)
{
var cl = GetPerson(wcfElement.UserName);
cl.IsSending = true;
if (wcfElement.WcfElementVersion != bazaDanych.WcfElementVersion) return false;
//method processes messages and if needed creates creates WcfElements which are added to every clients queue
return ifSuccess;
}
#endregion
#region private methods
private void MyEventHandler(object sender, ServiceEventArgs e)
{
try
{
_callback.ReceiveWcfElement(e.WcfElement);
}
catch (Exception ex)
{
}
}
#endregion
}
#endregion
Client side in a moment
#region Client class
[DataContract]
public class Client
{
#region Instance Fields
/// <summary>
/// The UserName
/// </summary>
[DataMember]
public string UserName { get; set; }
[DataMember]
public WcfElement ElementsVersions { get; set; }
private bool _isSynchronized;
public bool IsSynchronized
{
get { return _isSynchronized; }
set
{
_isSynchronized = value;
}
}
public bool IsSending { get; set; }
public bool IsReceiving { get; set; }
private List<WcfElement> ElementsQueue { get; set; }
public object LockObj { get; set; }
public void AddElementToQueue(WcfElement wcfElement, int position = -1)
{
try
{
lock (LockObj)
{
if (ElementsQueue == null) ElementsQueue = new List<WcfElement>();
if (position != -1 && position <= ElementsQueue.Count)
{
try
{
ElementsQueue.Insert(position, wcfElement);
}
catch (Exception e)
{
}
}
else
{
try
{
//dodaje na koncu
ElementsQueue.Add(wcfElement);
}
catch (Exception e)
{
}
}
}
}
catch (Exception e)
{
}
}
public WcfElement GetFirstElementFromQueue()
{
if (ElementsQueue == null) return null;
if (ElementsQueue.Count > 0)
{
var tmp = ElementsQueue[0];
ElementsQueue.RemoveAt(0);
return tmp;
}
return null;
}
#endregion
#region Ctors
/// <summary>
/// Assign constructor
/// </summary>
/// <param name="userName">The userName to use for this client</param>
public Client(string userName)
{
UserName = userName;
}
#endregion
}
#endregion
ProxySingletion:
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
public sealed class ProxySingleton : IClientCallback
{
#region Instance Fields
private static ProxySingleton _singleton;
public static bool IsConnected;
private static readonly object SingletonLock = new object();
private ServiceProxy _proxy;
private Client _myPerson;
private delegate void HandleDelegate(Client[] list);
private delegate void HandleErrorDelegate();
//main proxy event
public delegate void ProxyEventHandler(object sender, ProxyEventArgs e);
public static event ProxyEventHandler ProxyEvent;
//callback proxy event
public delegate void ProxyCallBackEventHandler(object sender, ProxyCallBackEventArgs e);
public static event ProxyCallBackEventHandler ProxyCallBackEvent;
#endregion
#region Ctor
/// <summary>
/// Blank constructor
/// </summary>
private ProxySingleton()
{
}
#endregion
#region Public Methods
#region IClientCallback implementation
public void ReceiveWcfElement(WcfElement wcfElement)
{
//process received data
//...
ReadyToReceive();
}
#endregion
public void ReadyToReceive()
{
try
{
if (bazaDanych.Dane.Client.IsSending) return;
var w = bazaDanych.Dane.Client.GetFirstElementFromQueue();
if (w != null)
{
SendWcfElement(w);
return;
}
_proxy.ReadyToReceive(bazaDanych.Dane.Client.UserName, source, ostatniTypWiadomosci);
}
catch (Exception)
{
IsConnected = false;
}
}
public static WcfElement CurrentWcfElement;
public bool SendWcfElement(WcfElement wcfElement)
{
if (bazaDanych.Dane.Client.IsReceiving)
{
bazaDanych.Dane.Client.AddElementToQueue(wcfElement);
return true;
}
bazaDanych.Dane.Client.IsSending = true;
foreach (var wcfElementSplited in WcfElement.SplitWcfElement(wcfElement, true))
{
CurrentWcfElement = wcfElementSplited;
try
{
var r = _proxy.SendWcfElement(wcfElementSplited);
CurrentWcfElement = null;
}
catch (Exception e)
{
IsConnected = false;
return false;
}
}
bazaDanych.Dane.Client.IsSending = false;
ReadyToReceive();
return true;
}
public void ListenForConnectOrReconnect(EventArgs e)
{
SendWcfElement(WcfElement.GetVersionElement());//send wcfelement for perform PerformDataSync
ReadyToReceive();
}
public static bool IsReconnecting;
public bool ConnectOrReconnect(bool shouldRaiseEvent = true)
{
if (IsReconnecting)
{
return IsConnected;
}
if (IsConnected) return true;
IsReconnecting = true;
bazaDanych.Dane.Client.IsReceiving = false;
bazaDanych.Dane.Client.IsSending = false;
bazaDanych.Dane.Client.IsSynchronized = false;
try
{
var site = new InstanceContext(this);
_proxy = new ServiceProxy(site);
var list = _proxy.Login(bazaDanych.Dane.Client, bazaDanych.Dane.UserPassword, bazaDanych.Dane.UserIsAuto, bazaDanych.Dane.UserIsSuperMode);
bazaDanych.Dane.UserRights.Clear();
bazaDanych.Dane.UserRights.AddRange(list);
IsConnected = true;
if (shouldRaiseEvent) ConnectOrReconnectEvent(null);
}
catch (Exception e)
{
IsConnected = false;
}
IsReconnecting = false;
return IsConnected;
}
}
#endregion
At the moment my app works like this:
After successful login every client sends WcfElements(which contains bunch of list with ids and versions of elements). Then it sends ReadyToReceive one way message which after login fires performsync method. That method prepares data for client and sends first of them using one way receive method. IF there is more than one wcfelement to send then only last one is marked as last. Client responds with ReadyToReceive after every successful receive from Server. All up to this point works quite well. Problem starts later. Mostly packages are lost (method receiveWcfElement). Server has marked that client is receiving and maybe processing message and is waitng for readytoreceive packet, which will never be send because of lost element.
I've made it like this because as far as I know client can't send and receive at the same time. I've tried this and got this problem:
If client send wcfElement with SendWcfElement method and server due to processing this element created another element which was supposed to be ssend back to client then client whoud have faulted proxy if callback was send before sendWcfElement returned true indicating that method was completed.
Now I wonder if it is possible for client to send and receive at the same time using two way methods ?
I ended up with to services(two connections). One for connection from client to server and another with callback which handles connection from server to client.
I have a small WCF pub/sup service running, and remote clients subscribe and send messages (tried will all sorts of complex objects) and that works fine. All interfaces reflect(ed) the type of object being used. Switching to another object type requires that the interfaces be adjusted to accommodate that object type. All subscribers get a copy of the message.
Now I am trying to do the same thing, but with Message class messages. Client creates a new message and encapsulates its object in the message, and sends it to the (remote) service, where it is received properly (inspected the object). However, when the server replies by resending (callback) the message back to the originating client, the client receives the following message:
“The server did not provide a meaningful reply; this might be caused by a contract mismatch, a premature session shutdown or an internal server error.”
Sequence of events (Client):
Client creates Message,
(DuplexChannelFactory)AddMessage,
-Catch above error
Sequence of events (Server):
Service host receives message,
Message inspected (copy and recreate),
Perform callback,
No errors.
Switching back to a basic, or user defined, type and all problems go away. I have been struggling with this for a week now and not closer to any solution. Tried manipulating headers, recreating the message, switching to Message Contracts, and trying to interpret the contents of the trace logs etc. Hope I will find some answers here.
Primary code used (stripped of most of the error handling):
Client interfaces:
namespace WCFSQL
{
public class ClientInterfaces
{
[ServiceContract(Namespace = "WCFServer", Name = "CallBacks")]
public interface IMessageCallback
{
[OperationContract(Name = "OnMessageAdded", Action = "WCFServer/IMessageCallback/OnMessageAdded", IsOneWay = true)]
void OnMessageAdded(Message SQLMessage, DateTime timestamp);
}
[ServiceContract(Namespace = "WCFServer", CallbackContract = typeof(IMessageCallback))]
public interface IMessage
{
[OperationContract(Name = "AddMessage", Action = "WCFServer/IMessage/AddMessage")]
void AddMessage(Message SQLMessage);
[OperationContract(Name = "Subscribe", Action = "WCFServer/IMessage/Subscribe")]
bool Subscribe();
[OperationContract(Name = "Unsubscribe", Action = "WCFServer/IMessage/Unsubscribe")]
bool Unsubscribe();
}
}
}
Server interfaces:
namespace WCFSQL
{
public class ServerInterfaces
{
[ServiceContract(Namespace = "WCFServer")]
public interface IMessageCallback
{
[OperationContract(Name = "OnMessageAdded", Action = "WCFServer/IMessageCallback/OnMessageAdded", IsOneWay = true)]
void OnMessageAdded(Message SQLMessage, DateTime timestamp);
}
[ServiceContract(Namespace = "WCFServer", CallbackContract = typeof(IMessageCallback), SessionMode = SessionMode.Required)]
public interface IMessage
{
[OperationContract(Name = "AddMessage", Action = "WCFServer/IMessage/AddMessage")]
void AddMessage(Message SQLMessage);
[OperationContract(Name = "Subscribe", Action = "WCFServer/IMessage/Subscribe")]
bool Subscribe();
[OperationContract(Name = "Unsubscribe", Action = "WCFServer/IMessage/Unsubscribe")]
bool Unsubscribe();
}
}
}
Message creation:
// client proxy instance created and opened before
public static bool WCFSqlLogger(string Program, WCFSQLErrorLogMessage SQLErrorMessage, WCFSqlClientProxy client)
{
MessageVersion ver = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.WSAddressing10);
Message Out = Message.CreateMessage(ver, "WCFServer/IMessage/AddMessage", SQLErrorMessage);
if (!client.SendMessage(Out))
{
Console.WriteLine("Client Main: Unable to send");
return false;
}
return true;
}
Client proxy:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
[CallbackBehavior(IncludeExceptionDetailInFaults = true, ConcurrencyMode = ConcurrencyMode.Single, UseSynchronizationContext = false)]
public class WCFSqlClientProxy : ClientInterfaces.IMessageCallback, IDisposable
{
public ClientInterfaces.IMessage pipeProxy = null;
DuplexChannelFactory<ClientInterfaces.IMessage> pipeFactory;
public bool Connect()
{
NetTcpBinding newBinding = new NetTcpBinding(SecurityMode.TransportWithMessageCredential);// NetTcpBinding newBinding = new NetTcpBinding(SecurityMode.Transport)
newBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
EndpointAddress newEndpoint = new EndpointAddress(new Uri("net.tcp://host:8000/ISubscribe"), EndpointIdentity.CreateDnsIdentity("Domain"));
pipeFactory = new DuplexChannelFactory<ClientInterfaces.IMessage>(new InstanceContext(this), newBinding, newEndpoint);
pipeFactory.Credentials.Peer.PeerAuthentication.CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust;
pipeFactory.Credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
pipeFactory.Credentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "somestring");
try
{
pipeProxy = pipeFactory.CreateChannel();
pipeProxy.Subscribe();
return true;
}
catch (Exception e)
{
Console.WriteLine("Error opening: {0}", e.Message);
return false;
}
}
public void Close()
{
pipeProxy.Unsubscribe();
}
public bool SendMessage(Message SQLMessage)
{
try
{
Console.WriteLine("Proxy Sending:");
pipeProxy.AddMessage(SQLMessage); // This is where the eror occurs !!!!!!!!!!!!!!!!!!
return true;
}
catch (Exception e)
{
Console.WriteLine("Client Proxy: Error sending: {0}", e.Message);
}
return false;
}
public void OnMessageAdded(Message SQLMessage, DateTime timestamp)
{
WCFSQLErrorLogMessage message = SQLMessage.GetBody<WCFSQLErrorLogMessage>();
Console.WriteLine(message.LogProgram + ": " + timestamp.ToString("hh:mm:ss"));
}
public void Dispose()
{
Console.WriteLine("Dispose: Unsubscribe");
pipeProxy.Unsubscribe();
}
}
Service:
namespace WCFSQL
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
[CallbackBehavior(IncludeExceptionDetailInFaults = true, ConcurrencyMode = ConcurrencyMode.Single, UseSynchronizationContext = false)] // or ConcurrencyMode.Reentrant
public class WCFSqlServerProxy : ServerInterfaces.IMessage
{
private static List<ServerInterfaces.IMessageCallback> subscribers = new List<ServerInterfaces.IMessageCallback>();
private static Uri target;
private static ServiceHost serviceHost;
public WCFSqlServerProxy(Uri Target) // Singleton
{
target = Target;
}
public bool Connect()
{
serviceHost = new ServiceHost(typeof(WCFSqlServerProxy), target);
NetTcpBinding newBinding = new NetTcpBinding(SecurityMode.TransportWithMessageCredential);
newBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;
newBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
serviceHost.Credentials.ClientCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck; // Non-domain members cannot follow the chain?
serviceHost.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.TrustedPeople, X509FindType.FindByThumbprint, "somestring");
serviceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust;
serviceHost.AddServiceEndpoint(typeof(ServerInterfaces.IMessage), newBinding, "ISubscribe");
return true;
}
public bool Open()
{
serviceHost.Open();
return true;
}
public bool Close()
{
serviceHost.Close();
return true;
}
public bool Subscribe()
{
try
{
ServerInterfaces.IMessageCallback callback = OperationContext.Current.GetCallbackChannel<ServerInterfaces.IMessageCallback>();
if (!subscribers.Contains(callback))
{
subscribers.Add(callback);
return true;
}
else
{
return false;
}
}
catch (Exception e)
{
return false;
}
}
public bool Unsubscribe()
{
try
{
ServerInterfaces.IMessageCallback callback = OperationContext.Current.GetCallbackChannel<ServerInterfaces.IMessageCallback>();
if (subscribers.Contains(callback))
{
subscribers.Remove(callback);
return true;
}
return false;
}
catch (Exception e)
{
Console.WriteLine("WCFSqlServerProxy: Unsubscribe - Unsubscribe error {0}", e);
return false;
}
}
private string GetData()
{
MessageProperties messageProperties = ((OperationContext)OperationContext.Current).IncomingMessageProperties;
RemoteEndpointMessageProperty endpointProperty = messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
string computerName = null;
try
{
string[] computer_name = Dns.GetHostEntry(endpointProperty.Address).HostName.Split(new Char[] { '.' });
computerName = computer_name[0].ToString();
}
catch (Exception e)
{
computerName = "NOTFOUND";
Console.WriteLine("WCFSqlServerProxy: Hostname error: {0}", e);
}
return string.Format("{0} - {1}:{2}", computerName, endpointProperty.Address, endpointProperty.Port);
}
public void AddMessage(Message SQLMessage) //Go through the list of connections and call their callback funciton
{
subscribers.ForEach(delegate(ServerInterfaces.IMessageCallback callback)
{
if (((ICommunicationObject)callback).State == CommunicationState.Opened)
{
MessageVersion ver = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.WSAddressing10);
MessageBuffer buffer = SQLMessage.CreateBufferedCopy(4096);
Message msgCopy = buffer.CreateMessage();
//System.Xml.XmlDictionaryReader xrdr = msgCopy.GetReaderAtBodyContents();
WCFSQLErrorLogMessage p = msgCopy.GetBody<WCFSQLErrorLogMessage>();
SQLMessage = buffer.CreateMessage();
buffer.Close();
Message In = Message.CreateMessage(ver, "WCFServer/IMessage/AddMessage", p); // Tried recreating messsage, with same results
//Console.WriteLine("Message: Header To: {0}", In.Headers.To);
//Console.WriteLine("Message: Header From: {0}", In.Headers.From);
//Console.WriteLine("Message: Header Action: {0}", In.Headers.Action);
//Console.WriteLine("Message: Header ReplyTo: {0}", In.Headers.ReplyTo);
//Console.WriteLine("Message: IsFault: {0}", In.IsFault);
//Console.WriteLine("Message: Properties {0}", In.Properties);
//Console.WriteLine("Message: State {0}", In.State);
//Console.WriteLine("Message: Type {0}", In.GetType());
//Console.WriteLine("Proxy Sending: Copy created");
//Console.WriteLine("Remote: {0}, Hash: {1}", GetData(), callback.GetHashCode());
callback.OnMessageAdded(SQLMessage, DateTime.Now); // This should echo the message back with a timeslot.
}
else
{
Console.WriteLine("WCFSqlServerProxy:addmessage connected state: {0}", ((ICommunicationObject)callback).State == CommunicationState.Opened);
subscribers.Remove(callback);
}
});
}
}
I just got an answer to my question from Tanvir Huda, on the microsoft WCF forum.
"Using the Message Class in Operations
You can use the Message class as an input parameter of an operation, the return value of an operation, or both. If Message is used anywhere in an operation, the following restrictions apply:
•The operation cannot have anyoutorrefparameters.
•There cannot be more than oneinputparameter. If the parameter is present, it must be either Message or a message contract type.
•The return type must be either void,Message, or a message contract type."
I cannot beleive I missed that; must have read it at least three times, but never applied those rules to the callback. The callback in my interfaces described, do have a return type of void, but it has a Message and a DateTime parameter.
After removing the DateTime parameter, the callback did (try) to re-serialize the original Message, but failed because of on invalid action (action was still set for the AddMessage, while now it should be OnMessageAdded). After changing the action on the callback to Action="*" it workt perfectly. A (maybe annoying) detail it that i do not really require a Message type on the callback, but I was very frustrated that I did not get it to work