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.
Related
I developed a game lift back-end server using a websocket sharp. The local test function provided by GameLift worked fine, but Web socket communication is not possible with the game session of Fleet created by the GameLift server.
To be exact, the connection is initially shown on the client as successful, but it is said that the connection was immediately disconnected. And even if the connection is marked as successful, no messages reach the server.
I wonder if there is anything I need to communicate with the AWS game lift server using a web socket. Please help me.
Fleet type : spot instance
Not use TLS
EC2 Port was set as |2022|TCP|0.0.0.0/0
Client Code for Connection
public static void AddService(string hostName, int port, string serviceName, WebSocketClientSession socketSession, bool overTls = false)
{
Dictionary<string, WebSocket> serviceMap = _instance.mServiceMap;
string uri = overTls
? $"wss://{hostName}:{port}/{serviceName}"
: $"ws://{hostName}:{port}/{serviceName}";
Debug.Log(uri);
WebSocket webSocket = new WebSocket(uri);
if (overTls)
{
webSocket.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls;
}
webSocket.OnOpen += socketSession.OnOpen;
webSocket.OnClose += socketSession.OnClose;
webSocket.OnMessage += socketSession.OnMessage;
webSocket.OnError += socketSession.OnError;
serviceMap[serviceName] = webSocket;
}
public static async void ConnectAsync(string serviceName, Action<bool> onCompleted)
{
Dictionary<string, WebSocket> serviceMap = _instance.mServiceMap;
WebSocket webSocket = serviceMap[serviceName];
await Task.Run(webSocket.Connect);
if (webSocket.IsAlive)
{
// webSocket.EmitOnPing = true;
onCompleted?.Invoke(true);
return;
}
onCompleted?.Invoke(false);
}
Server Code for listening
public static void StartServer()
{
_instance.mServer.Start();
_instance.mHasStarted = true;
}
public static void StopServer()
{
_instance.mServer.Stop();
_instance.mHasStarted = false;
}
public static void AddService<T>(string serviceName) where T : WebSocketServerSession, new()
{
WebSocketServer server = _instance.mServer;
server.AddWebSocketService<T>($"/{serviceName}");
}
public static void RemoveService(string serviceName)
{
WebSocketServer server = _instance.mServer;
server.RemoveWebSocketService($"/{serviceName}");
}
At first, communication was attempted using TCP and UDP, but the same phenomenon occurred as a web socket.
Various searches and attempts were made for a week, but there was no harvest. There were so few questions about the game lift that there were no similar cases.
Even if it is not a game lift service, I would like to know if there are additional elements necessary for AWS connection.
It could be lots of different things. TLS mismatches, WebSocket parameter negotiation mismatches, authentication etc.
I'd suggest digging deeper into what is happening on the wire. Hopefully, it'll make things clearer, and lead you to a solution.
A Man-In-The-Middle proxy, like Burp, would be able to see into the HTTP connection, and the follow-up WebSocket negotiation. All you need to do is point your app at it via the proxy settings, and install the Burp CA certificate into your app platform (so it can intercept the TLS).
Otherwise, if that's not an option, you can always use Wireshark (though you won't be able to see inside the TLS).
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.
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)
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);
}
I have a service running as local SYSTEM that launches another application with the user credentials.
That second app is only a tray icon that shows balloon tips to the user with the string received using the callback method. This second application connects to the WCF in duplex mode.
My problem is that for some reason the connection to the WCF is finalized at the end of the method Main. So I cannot send a callback message to the app right after the execution, included in the last line "kiosk.MyStart(args);". there the callback is still pointing to null.
Any idea how could I solve this issue?
static void Main(string []args)
{
if (Environment.UserInteractive)
{
// Start the WCf service
var host = new ServiceHost(typeof(WcfService));
host.Open();
//Launch the Kiosk Agent which connects to the WCF
bool ret = ProcessAsUser.Launch("C:\\Program Files (x86)\\KIOSK\\KioskAgent.exe");
WinService kiosk = new WinService(args);
// some checks and a welcome message is sent to the user.
kiosk.MyStart(args);
//...
//...
}
}
Edit: to clarify a bit more, inside kiosk.MyStart method is where I try to execute the callback to show a welcome message, but the callback is still NULL.
As a result I assume that the client was not properly started for any reason and I launch it once again...
if (WcfService.Callback != null)
WcfService.Callback.UIMessageOnCallback(UIMessage);
else
ProcessAsUser.Launch("C:\\Program Files (x86)\\KIOSK\\KioskAgent.exe");
Add a try catch block over the callback method, if the client not reachable it falls in the catch you can unsubscribe it. Is also good practice send a keepalive message to your client, to check if it available.
private void InformClient(ClientInfo clientInfo)
{
var subscribers = this._subscriberRepository.GetAll();
foreach (var subscriber in subscribers)
{
try
{
if (subscriber.Callback.FireInformClient(clientInfo));
{
//If subscriber not reachable, unsubscribe it
this._subscriberRepository.Unsubscribe(subscriber.ClientId);
}
}
catch (Exception exception)
{
//If subscriber not reachable, unsubscribe it
this._subscriberRepository.Unsubscribe(subscriber.ClientId);
Log.Error(nameof(InformClient), exception);
}
}
}
IClientCallback
public interface IClientCallback
{
[OperationContract]
bool FireInformClient(ClientInfo clientInfo);
}
If you have more subscribers for example a terminal, server create a subscriberRepository to manage all subscribers.
var callback = OperationContext.Current.GetCallbackChannel<IClientCallback>();
if (this._subscriberRepository.Subscribe(clientId, callback))
{
return true;
}