SignalR Core disconnects on Android client - c#

I encountered connection issue while developing chat application on Android, it uses SignalR Core on the server side. Connection with server is established properly, client invokes for the first time server's method, server then invokes client's method successfully and in the end of execution of Android's listener connection drops. Each subsequent request to the server after the first one needs reconnection, becuase hubConnection.connectionState == HubConnectionState.DISCONNECTED. Reconnecting after each request is obviously bad. SignalR docs don't mention such case.
What am I doing wrong?
Kotlin code:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val hubConnection = HubConnectionBuilder.create("http://192.168.0.171:6000/chathub").build()
hubConnection.start()
hubConnection.on("MessageAdded", {msg ->
chat_view_text.text = msg.from+": "+msg.content
}, ChatMessage::class.java)
chat_view_send_button.setOnClickListener{
if (hubConnection.connectionState == HubConnectionState.CONNECTED){
hubConnection.send("SendMessage", Message("android", "hello world"));
}
}
}
.NET code:
public class ChatHub : Hub<IClientChatActions>, IServerChatActions
{
public override Task OnConnectedAsync()
{
Console.WriteLine("connected");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
Console.WriteLine("disconnected");
return base.OnDisconnectedAsync(exception);
}
public async Task SendMessage(Message msg)
{
await Clients.All.MessageAdded(msg);
}
}

Android loses the connection while updating the UI this way ...
Try to update your UI in the main thread using Kotlin Coroutines.
Dispatchers.Main is the recommended dispatcher for performing UI-related events.
To do so, add to your build.gradle:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
In your activity.kt file, change it to something like:
private var job: Job = Job()
private val scope = CoroutineScope(job + Dispatchers.Main)
...
hubConnection.on("MessageAdded", {msg ->
scope.launch(Dispatchers.Main) {
chat_view_text.text = msg.from+": "+msg.content
}
}, ChatMessage::class.java)

Related

Handle network issues when wi-fi is switched off

I am testing .NET version of gRPC to understand how to handle network failures. I put the server to one external machine and debugging the client. The server ticks with a message onnce a second and the client just shows it on the console. So when I stop my local Wi-Fi connection for seconds, then gRPC engine automatically recovers and I even get remaining values. However, if I disable Wi-Fi for longer time like a minute, then it just gets stuck. I don't even get any exceptions so that I can just handle this case and recover manually. This scenario works fine when I close the server app manually, then an exception will occur on the client. This is what I have on the client:
static async Task Main(string[] args)
{
try
{
await Subscribe();
}
catch (Exception)
{
Console.WriteLine("Fail");
Thread.Sleep(1000);
await Main(args);
}
Console.ReadLine();
}
private static async Task Subscribe()
{
using var channel = GrpcChannel.ForAddress("http://x.x.x.x:5555");
var client = new Greeter.GreeterClient(channel);
var replies = client.GerReplies(new HelloRequest { Message = "Test" });
while (await replies.ResponseStream.MoveNext(CancellationToken.None))
{
Console.WriteLine(replies.ResponseStream.Current.Message);
}
Console.WriteLine("Completed");
}
This works when the server app stopped but it doesn't work if I just disable loca Wi-Fi connection on the client side. How can I handle such a case and similar ones?
I've managed to solve it by KeepAlivePingDelay setting:
var handler = new SocketsHttpHandler
{
KeepAlivePingDelay = TimeSpan.FromSeconds(5),
KeepAlivePingTimeout = TimeSpan.FromSeconds(5),
};
using var channel = GrpcChannel.ForAddress("http://x.x.x.x:5555", new GrpcChannelOptions
{
HttpHandler = handler
});
This configuration force gRPC fail after 10 seconds in case of no connection.

HttpClient throws httprequest exception on network restore

I am using System.Net.Http.HttpClient to make postaysnc request. While request is in progress I unplug the network cable, receive HttpRequestException.
After some time plug the network cable again and make the postasync request, getting the HttpRequestException - sometimes i get the response server not available,sometimes timeout
Do i need to dispose the httpclient on exception and recreate when the request is made? How to make the query successful on network restore.
private async Task<string> GetServerResult()
{
try
{
var response = await myHttpClient.PostAsync("https://google.com", httpContent);
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex)
{
throw new HttpRequestException(ex.Message, ex.InnerException);
}
}
As per your requirement, you have to change implement some sort of implementation in that case. My proposed solution is use to a caching mechanism at WCF Client and update it periodically.
The very simple implementation could be as: You have a very simple singleton class of and a periodic Timer fetches the data from your mentioned endpoint. It stores the last cached data so that you have a copy of the data and when the hits are failed you can configure a fallback mechanism for that. For instance you have an implementation like
//You single Cache class
public sealed class ClientCache
{
#region Singleton implementation
private static ClientCache _clientCache = new ClientCache();
private ClientCache()
{
}
public static ClientCache Instance => _clientCache;
#endregion
//Timer for syncing the data from Server
private Timer _timer;
//This data is the cached one
public string data = string.Empty;
internal void StartProcess()
{
//Initializing the timer
_timer = new Timer(TimeSpan.FromMinutes(1).TotalMilliseconds); //This timespan is configurable
//Assigning it an elapsed time event
_timer.Elapsed += async (e, args) => await SyncServerData(e, args);
//Starting the timer
_timer.Start();
}
//In this method you will request your server and fetch the latest copy of the data
//In case of failure you can maintain the history of the last disconnected server
private async Task ProcessingMethod(object sender, ElapsedEventArgs e)
{
//First we will stop the timer so that any other hit don't come in the mean while
timer.Stop();
//Call your api here
//Once the hit is completed or failed
//On Success you will be updating the Data object
//data = result from your api call
//Finally start the time again as
timer.Start();
}
}
Now coming to Step two where to initialize the ClientCache Class. The best options are to initialize it in Global.asax class
protected void Application_Start()
{
//As
ClientCache.Instance.StartProcess();
}
Now whenever your frontend calls the method you don't need to go back to the server. Just send back the result from your cache as:
private Task<string> GetServerResult()
{
return Task.FromResult(ClientCache.Instance.data);
}

Connecting to a SignalR server

On the server I create a Hub
public class SGHub : Hub
{
public static List<string> Users = new List<string>();
public void Send(string name, string message)
{
Clients.All.broadcastMessage(name, message);
Console.WriteLine(SGHub.Users.Count);
}
}
On the client I connect to the Hub
void Start()
{
hubConnection = new HubConnection(serverURL);
hubConnection.Error += HubConnectionError;
iHubProxy = hubConnection.CreateProxy("SGHub");
Subscription subscription = iHubProxy.Subscribe("broadcastMessage");
hubConnection.Start();
}
If the server is not running, and the client tries to connect to the Hub, the application hangs, how to avoid this?
You can try to start flow with an http request to serverURL
and only if server returns Ok, you start signalr connection flow.
I would suggest to put you clientside connection code in a thread. That's how I did it. Every time you have a long running task (like waiting for a timeout in your case) and you do this in the UI thread the app will freeze.

Prevent GC from disposing a WebSocket when the task is finished running?

I'm trying to maintain a list of WebSockets for a server which only needs to send messages to the client and not receive any replies. When the WebSocket is created initially all I want to do is just add the socket reference to a list for later use.
...
static Dictionary<int,WebSocket> wsDict = new Dictionary<int,WebSocket>();
...
private Task ProcessWS(AspNetWebSocketContext context)
{
wsDict[id] = (context.WebSocket);
...
}
(Finishes running the method and returns)
The issue I'm having is that I believe GC is disposing of the WebSocket, so when I try to use it at a later time I receive 'System.ObjectDisposedException'. Is there any way of permanently stopping GC from disposing of the socket?
Edit:
Sorry for not being clear originally, the whole class which inherits ApiController is (as the name suggests) is a control. A client initiates the WebSocket via a GET request and it calls the method above where it tries to store the socket into a dictionary (mapped to a int). The dictionary itself is Static.
It saves it into the Dict fine and in the debugger everything looks great. Its just literally disposing it after ProccessWS is complete and I can't seem to find a way to stop it. If I add a loop/sleep timer to the bottom of the ProcessWS it works fine - but thats not a viable solution.
The is a problem which originally I wasn't sure if it would be possible as each of the REST calls are stateless yet I need to maintain and overall list of all the connections which seems to contradict the original statement.
You have to await reads while the WS is still connected:
public class WSHandler : IHttpHandler
{
public bool IsReusable { get { return false; } }
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
{
context.AcceptWebSocketRequest(ProcessWS);
}
}
private async Task ProcessWS(AspNetWebSocketContext context)
{
WebSocket socket = context.WebSocket;
...
while (socket.State == WebSocketState.Open)
{
WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None)
.ConfigureAwait(false);
...
}
}
}

How to use signalr as a client in asp.net website?

Signalr is usually used in "server" side asp.net through hubs and connections. What I'm trying to accomplish (if possible) is using signalr as a "client" in a website.
I have 2 websites, one as a server and another as a client.
I tried this
public void Start()
{
new Task(this.Listen).Start();
//new Thread(this.Listen).Start();
}
private async void Listen()
{
try
{
using (var hubConnection = new HubConnection("http://localhost:8081"))
{
var myHub = hubConnection.CreateHubProxy("mediaHub");
hubConnection.StateChanged += change => System.Diagnostics.Debug.WriteLine(change.OldState + " => " + change.NewState);
myHub.On<string>(
"log",
message =>
System.Diagnostics.Debug.WriteLine(message + " Notified !"));
await hubConnection.Start();
}
}
catch (Exception exc)
{
// ...
}
}
The server is calling the client like this:
var hub = GlobalHost.ConnectionManager.GetHubContext<MediaHub>() ;
hub.Clients.All.log("Server: " + message);
Nothing reaches the client !
As soon as your connection is ready, you actually dispose it, so it does not stay around. As a quick test, remove the using stuff and make your hubConnection a static member of your class, and check if you get called. If you do, then from there you can refine it, but at least you have clearer what's going on.

Categories

Resources