How do I send subscribers information asynchronously?
RecievMessage-Callback method
public void SendMessage(string message, string name)
{
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);
}
}
Without asynchrony, the service cannot cope
Related
I have followed the documentation on how to use signalr in blazor server ,in the hub i have setup a method with this signature
public async Task SendMessage(string sender, string receiver, string message)
{
await Clients.Users(sender, receiver).SendAsync("ReceiveMessage", receiver, message);
}
And in the client side(the page on blazor server) i have set this up for the ReceiveMessage method
public async Task OnInitializeAsync()
{
if(hub is null)
hub = new HubConnectionBuilder()
.WithUrl(_NavigationManager.ToAbsoluteUri("/ConnectionsHub"))
.Build();
hub.On<string, string>($"ReceiveMessage", // this is never triggered
async (sender, message) =>
{
var encodedMsg = $"{sender}: {message}";
ChatBox.MessageList.Add(encodedMsg);
await ChatBox.ComponentStateHasChanged();
});
hub.ServerTimeout = TimeSpan.FromMilliseconds(100000);
await hub.StartAsync();
}
and how I send the message
private async Task SendMessage(string message)
{
if(string.IsNullOrEmpty(message))
{
await PrintMessage("Error", "Cannot send an empty message");
return;
}
if (hub is not null)
{
foreach (var user in _UserList)
{
await hub.SendAsync("SendMessage", ThisUser.Email, user.User.Email,ChatBox.Message);
}
}
}
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 sample service to test WCF net.tcp communication. It is very simple service and all it does is subscribing a client to the service and then calls callbackchannel to notify all connected clients about broadcasted message. The service is hosted inside IIS 7.5.
Here is service code and test client to test it.
[ServiceContract(CallbackContract = typeof(ISampleServiceCallBack), SessionMode = SessionMode.Required)]
public interface ISampleCuratioService
{
[OperationContract(IsOneWay = true)]
void SubcribeToService(string sub);
[OperationContract]
string GetData(int value);
[OperationContract(IsOneWay = true)]
void Broadcast(string message);
}
public interface ISampleServiceCallBack
{
[OperationContract(IsOneWay = true)]
void NotifyClient(string message);
}
Here is the service implementation:
[ServiceBehavior(Name = "CuratioCSMService", InstanceContextMode = InstanceContextMode.PerSession)]
public class Service1 : ISampleCuratioService
{
private static List<ISampleServiceCallBack> JoinedClien = new List<ISampleServiceCallBack>();
public void SubcribeToService(string sub)
{
var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
if (!JoinedClien.Contains(subscriber))
{
JoinedClien.Add(subscriber);
}
}
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public void Broadcast(string message)
{
JoinedClien.ForEach(c => c.NotifyClient("message was received " + message));
}
}
I can not understand the behavior I get when running it. After the first client runs everything works fine but as I close and open test client app, it throws exception notifying that channel can not be used for communication as it is in fault state.
This is sample test client:
static void Main(string[] args)
{
var callneckclient = new ServiceClientProxy();
var client = new SampleCuratioServiceClient(new InstanceContext(callneckclient));
client.SubcribeToService("me");
Console.ReadLine();
for (int i = 0; i < 15; i++)
{
Console.WriteLine(client.GetData(5));
client.Broadcast("this is from client me");
}
client.Close();
Console.Read();
}
public class ServiceClientProxy : ISampleCuratioServiceCallback, IDisposable
{
public void NotifyClient(string message)
{
Console.WriteLine(message);
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
The situation gets even buggy when I run 5 clients. Non of those send or receive messages.
When a client calls SubcribeToService you add its operation context to a List called JoinedClien.
When you call Broadcast in your server, you call the method NotifyClient on all collected operation contexts for every client that has ever connected.
The problem is, that a disconnected client won't get removed from your JoinedClien list.
When you try to call an operation method on a disconnected operation context, you get the channel is in faulted state error.
To work around, you should subscribe to the Channel_Closed and Channel_Faulted events and also catch the CommunicationException when calling back into your clients and remove the operation context of the faulted clients:
public void Broadcast(string message)
{
// copy list of clients
List<OperationContext> clientsCopy = new List<OperationContext>();
lock(JoinedClien) {
clientsCopy.AddRange(JoinedClien);
}
// send message and collect faulted clients in separate list
List<OperationContext> clientsToRemove = new List<OperationContext>();
foreach (var c in JoinedClien)
{
try {
c.NotifyClient("message was received " + message));
}
catch (CommunicationException ex) {
clientsToRemove.Add(c);
}
}
foreach (var c in clientsToRemove)
{
lock(JoinedClien) {
if(JoinedClien.Contains(c))
JoinedClien.Remove(c);
}
}
}
When adding new clients you have to lock that operation, too:
var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
lock(JoinedClien)
{
if (!JoinedClien.Contains(subscriber))
{
JoinedClien.Add(subscriber);
}
}
I have a simple WCF service serving up notifications to and from an app.
the service holds a static list of subscribers, and before it tries to send a message to any of them, it checks ...
((ICommunicationObject)callback).State == CommunicationState.Opened
unfortunately if the app consuming the WCF service falls over - thus not unsubscribing itself from the WCF service, the service will hang around and eventually timeout. far from ideal.
in an attempt to sort this I added
OperationContext.Current.Channel.Faulted += new EventHandler(Channel_Faulted);
in the subscription method, but it is never hit.
is there a reliable way to determine whether or not the callback channel is in fact open?
or maybe to asynchronously send the messages so that if a subscriber appears in the list but is actually dead, the service wont wait around like a geek being stood up on a date? :)
any help most happily received
thanks
nat
full code for service below...
private static readonly List<INotificationCallback> subscribers = new List<INotificationCallback>();
public void AddMessage(string SendingUser, string message, List<string> users, MessageType messageType, Guid? CaseID)
{
subscribers.ForEach(delegate(INotificationCallback callback)
{
if (((ICommunicationObject)callback).State == CommunicationState.Opened)
{
callback.OnMessageAdded(SendingUser, message, users, messageType, DateTime.Now, CaseID);
}
else
{
RemoveSubscriber(callback);
}
});
}
public bool Subscribe()
{
try
{
INotificationCallback callback = OperationContext.Current.GetCallbackChannel<INotificationCallback>();
OperationContext.Current.Channel.Faulted += new EventHandler(Channel_Faulted);
OperationContext.Current.Channel.Closed += new EventHandler(Channel_Closed);
if (!subscribers.Contains(callback))
subscribers.Add(callback);
return true;
}
catch
{
return false;
}
}
private void RemoveSubscriber(INotificationCallback callback)
{
if (subscribers.Contains(callback))
subscribers.Remove(callback);
}
void Channel_Closed(object sender, EventArgs e)
{
INotificationCallback machine = sender as INotificationCallback;
RemoveSubscriber(machine);
}
void Channel_Faulted(object sender, EventArgs e)
{
INotificationCallback machine = sender as INotificationCallback;
RemoveSubscriber(machine);
}
public bool Unsubscribe()
{
try
{
INotificationCallback callback = OperationContext.Current.GetCallbackChannel<INotificationCallback>();
RemoveSubscriber(callback);
return true;
}
catch
{
return false;
}
}
ended up ditching the HttpBinding and switched to NetTcpBinding which raises the the fault event immediately.