Slow SignalR HubConnection Start - c#

My Question: Is there a way to speed up the initial connect to the hub?
Details:
I am using SignalR at a selfhosted Webservice #Win 8.1.
The Hub application got local clients and remote clients.
I granded SignalR access via DNS, localhost and 127.0.0.1 by:
'netsh http add urlacl url=http://<Replace>:<Port>/ user=Everyone'
Usually all clients even the local ones are using DNS. And it works.
My problem is one sporned child process (using c# Microsoft.AspNet.SignalR.Client.HubConnection). It usually connects withing 400ms. Thats ok. But sometimes it takes seconds.
I have tried to switch at this client to 127.0.0.1 and localhost but without any change.
Afterwards the initial connect, SignalR is pritty fast.
If there is no easy way I have to switch back to plain UDP.
SignalR Config at Hub:
using System;
using System.Web.Http;
using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Microsoft.Owin.FileSystems;
using Microsoft.Owin.StaticFiles;
using Owin;
[assembly: OwinStartup(typeof(SignalRStartUp))]
namespace Onsite
{
public class SignalRStartUp
{
// Any connection or hub wire up and configuration should go here
public void Configuration(IAppBuilder pApp)
{
try
{
pApp.UseCors(CorsOptions.AllowAll);
pApp.MapSignalR();
pApp.UseFileServer(true);
var lOptions = new StaticFileOptions
{
ContentTypeProvider = new CustomContentTypeProvider(),
FileSystem = new PhysicalFileSystem(Constants.Root)
};
pApp.UseStaticFiles(lOptions);
// Configure Web API for self-host.
var lConfig = new HttpConfiguration();
lConfig.Routes.MapHttpRoute("RemoteApi", "api/{controller}/{action}");
lConfig.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });
pApp.UseWebApi(lConfig);
}
catch (Exception lEx)
{
Logger.Error(lEx);
}
}
}
}
Startup at the Client:
public static string GetConnectionString(string pHost = null)
{
var lHost = pHost ?? GetMainClientDns();
return string.Format("http://{0}{1}", lHost, SignalRPort);
}
private void StartSignalR()
{
try
{
var lConnectionString = GetConnectionString("127.0.0.1");
var lStopWatch = new Stopwatch();
lStopWatch.Restart();
IsConnectingHost = true;
_connection = new HubConnection(lConnectionString, string.Format("AccessKey={0}&Role={0}", Constants.AccessKeyPlugIn));
_connection.Reconnected += SetConnected;
_connection.Reconnecting += SetDisConnected;
MTalkHub = _connection.CreateHubProxy("OnsiteHub");
MTalkHub.On("RequestSetNext", RequestSetNext);
MTalkHub.On("RequestSetPrevious", RequestSetPrevious);
MTalkHub.On("RequestEcho", RequestEcho);
_connection.TransportConnectTimeout = _transportConnectTimeout;
var lTask = _connection.Start();
lTask.Wait();
lStopWatch.Stop();
SetConnected();
}
catch (TargetInvocationException lEx)
{
IsDisconnected = true;
Task.Run(() => TryToConnect());
Logger.Fatal(string.Format("Server failed to start. Already running on: '{0}'", lConnectionString), lEx);
}
catch (Exception lEx)
{
IsDisconnected = true;
Task.Run(() => TryToConnect());
Logger.Fatal(string.Format("Connecting to: '{0}' failed!", lConnectionString.ToStringNs()));
}
finally
{
IsConnectingHost = false;
}
}

After a bugfix for another issue I the long initial connect was also gone.
What I have found: There was a race condition and rarely a lock with FileAccess via network share blocked the Hub by updating the local cached thumbnails.
Thx anyway!

Related

Search for all SQL Server instances on network with .NET Core 3.1

My application requires a user to select a SQL Server instance on their network to serve as the app's database server. I need to populate a list of all available SQL Server instances from which to make this selection.
The application is developed in .NET Core 3.1. I am unable to simply use SqlDataSourceEnumerator as it is not available in .NET Core.
Is there an alternative available to make this query?
I'd like to avoid importing PowerShell functionality to the application if possible, as it would likely require additional PowerShell modules to be installed on the machine, but will consider it if it's the only way.
The only way I've found to do this in .NET Core is to manually send and receive UDP messages.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using ReporterAPI.Utilities.ExtensionMethods;
using Serilog;
public class SqlServerScanner : ISqlServerScanner
{
private readonly ConcurrentDictionary<string, SqlServerInstance> serverInstances = new ConcurrentDictionary<string, SqlServerInstance>();
private DateTime lastScanAttemptTime = DateTime.MinValue;
private readonly TimeSpan cacheValidTimeSpan = TimeSpan.FromSeconds(15);
private readonly TimeSpan responseTimeout = TimeSpan.FromSeconds(2);
private readonly List<UdpClient> listenSockets = new List<UdpClient>();
private readonly int SqlBrowserPort = 1434; // Port SQL Server Browser service listens on.
private const string ServerName = "ServerName";
public IEnumerable<SqlServerInstance> GetList()
{
ScanServers();
return serverInstances.Values;
}
private void ScanServers()
{
lock (serverInstances)
{
if ((DateTime.Now - lastScanAttemptTime) < cacheValidTimeSpan)
{
Log.Debug("Using cached SQL Server instance list");
return;
}
lastScanAttemptTime = DateTime.Now;
serverInstances.Clear();
try
{
var networksInterfaces = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
foreach (var networkInterface in networksInterfaces.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork))
{
Log.Debug("Setting up an SQL Browser listen socket for {address}", networkInterface);
var socket = new UdpClient { ExclusiveAddressUse = false };
socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.Client.Bind(new IPEndPoint(networkInterface, 0));
socket.BeginReceive(new AsyncCallback(ResponseCallback), socket);
listenSockets.Add(socket);
Log.Debug("Sending message to all SQL Browser instances from {address}", networkInterface);
using var broadcastSocket = new UdpClient { ExclusiveAddressUse = false };
broadcastSocket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
broadcastSocket.Client.Bind(new IPEndPoint(networkInterface, ((IPEndPoint)socket.Client.LocalEndPoint).Port));
var bytes = new byte[] { 0x02, 0x00, 0x00 };
broadcastSocket.Send(bytes, bytes.Length, new IPEndPoint(IPAddress.Broadcast, SqlBrowserPort));
}
Thread.Sleep(responseTimeout);
foreach (var socket in listenSockets)
{
socket.Close();
socket.Dispose();
}
listenSockets.Clear();
}
catch (Exception ex)
{
Log.Warning(ex, "Failed to initiate SQL Server browser scan");
throw;
}
}
}
private void ResponseCallback(IAsyncResult asyncResult)
{
try
{
var socket = asyncResult.AsyncState as UdpClient;
var localEndpoint = socket.Client.LocalEndPoint as IPEndPoint;
var bytes = socket.EndReceive(asyncResult, ref localEndpoint);
socket.BeginReceive(new AsyncCallback(ResponseCallback), socket);
if (bytes.Length == 0)
{
Log.Warning("Received nothing from SQL Server browser");
return;
}
var response = System.Text.Encoding.UTF8.GetString(bytes);
Log.Debug("Found SQL Server instance(s): {data}", response);
foreach (var instance in ParseInstancesString(response))
{
serverInstances.TryAdd(instance.FullName(), instance);
}
}
catch (Exception ex) when (ex is NullReferenceException || ex is ObjectDisposedException)
{
Log.Debug("SQL Browser response received after preset timeout {timeout}", responseTimeout);
}
catch (Exception ex)
{
Log.Warning(ex, "Failed to process SQL Browser response");
}
}
/// <summary>
/// Parses the response string into <see cref="SqlServerInstance"/> objects.
/// A single server may have multiple named instances
/// </summary>
/// <param name="serverInstances">The raw string received from the Browser service</param>
/// <returns></returns>
static private IEnumerable<SqlServerInstance> ParseInstancesString(string serverInstances)
{
if (!serverInstances.EndsWith(";;")
{
Log.Debug("Instances string unexpectedly terminates");
yield break;
}
// Remove cruft from instances string.
var firstRecord = serverInstances.IndexOf(ServerName);
serverInstances = serverInstances[firstRecord..^2];
foreach (var instance in serverInstances.Split(";;"))
{
var instanceData = instance.Split(";");
yield return new SqlServerInstance
{
ServerName = instanceData[1],
InstanceName = instanceData[3],
IsClustered = instanceData[5].Equals("Yes"),
Version = instanceData[7]
};
}
}
}
This seems faster than the old SMO EnumerateServers method, probably because it doesn't test each instance for connectivity, which I kind of like. I want to know what servers are out there, even if I have to fix something before being able to actually connect.
This function sends from a randomly selected UDP port, so be aware of the need for a firewall rule to allow traffic for the executable, but you can limit to remote port 1434.
The accepted answer is incomplete (misses one class) and uses some unexplained variables like "ServerName" (assumably, the local server name? And what is "remove cruft"?)
Anyway, I found this version way better: https://stackoverflow.com/a/64980148/56621
(also a UDP scanner that works on NET Core)

Why is no message received in WS communication on a UWP device?

I have a WS server and I would like to broadcast messages from that server (using another web app) to all HoloLens devices that are connected to the session.
First I have implemented a MessageWebSocket client in the Hololens app that initiated a connection with a sample public WS server echo.websocket.org just to check if the setup is right on the client side. Here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if WINDOWS_UWP
using System.Threading.Tasks;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using Windows.Web;
using System;
#endif
public class WebSocketClient : MonoBehaviour
{
void Start()
{
#if WINDOWS_UWP
int msgTime = 5;
int fadeTime = 1;
guiPhraseReporter.QueueRaport("START", msgTime, fadeTime);
MessageWebSocket ws = new MessageWebSocket();
ws.Control.MessageType = SocketMessageType.Utf8;
ws.MessageReceived += (MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args) =>
{
guiPhraseReporter.QueueRaport("Trying to receive message...", msgTime, fadeTime);
try
{
using (DataReader dataReader = args.GetDataReader())
{
dataReader.UnicodeEncoding = UnicodeEncoding.Utf8;
string message = dataReader.ReadString(dataReader.UnconsumedBufferLength);
Debug.Log(message);
}
}
catch (Exception ex)
{
Debug.Log("Error occurred");
}
};
ws.Closed += (IWebSocket sender, WebSocketClosedEventArgs args) => {
Debug.Log("WS closed");
};
try
{
Task connectTask = ws.ConnectAsync(new Uri("ws://echo.websocket.org")).AsTask();
connectTask.ContinueWith(async _ =>
{
string message = "Hello, World!";
using (DataWriter dataWriter = new DataWriter(ws.OutputStream))
{
dataWriter.WriteString(message);
await dataWriter.StoreAsync();
dataWriter.DetachStream();
}
Debug.Log("Sending Hello World");
});
}
catch (Exception ex)
{
WebErrorStatus webErrorStatus = WebSocketError.GetStatus(ex.GetBaseException().HResult);
// Add additional code here to handle exceptions.
Debug.Log(ex);
}
#endif
}
}
And it works fine, I'm able to send a message to the server, and it is echoed back and received correctly by the client.
Things however mess up when I use the actual server I'll be testing on. On my server, I have replicated the behavior from the echo.websocket.org and I echo back any message sent. I'm able to connect, the connection is not closed (Closed is never called), but I don't receive any messages.
If I test both servers using the web browser (with chrome's Smart Websocket Extension), they both work. The only difference (and only possible lead I got) is that the sample server (the one that works on Hololens) sends more headers upon connection:
vs my server:
Maybe there is some easier way to do this, but so far I didn't find any good WS wrappers that would work on UWP. Any help appreciated.
It was faulty logic on my server app after all. So there was no problem with WS communication to begin with, thank you for your time.

Communication between an Azure web application and a windows form app on Azure VM

This is my first project using Azure. So if I am asking very basic question, please be patient with me.
I have a web application which runs on Azure server. I also have a windows form app which is hosted on Azure VM. According to the requirement, a web app will establish a connection with the windows form app whenever it is required, will send a notification to the form app, receive a response from it and will cut off the connection. So here Web app is like a client and a Windows form app is like a server.
I tried using SignalR. Activated the end point and a port for the Windows form app on Azure portal. I was able to establish the connection but never getting the confirmation of that connection back from the Windows Form app.
Am I using the proper technique or there is a better way to do this? I hope someone will suggest a proper solution.
Here is what I tried
Server side code in Windows form app
Installed the Microsoft.AspNet.SignalR package by Nuget
Activated the VM end point and port #12345 from Azure portal
DNS name of VM - abc.xyz.net
Endpoint port number - 12345
public partial class FormsServer : Form
{
private IDisposable SignalR { get; set; }
const string ServerURI = "http://abc.xyz.net:12345";
private void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
Task.Run(() => StartServer());
}
private void StartServer()
{
try
{
SignalR = WebApp.Start<Startup>(ServerURI);
}
catch (TargetInvocationException)
{ }
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR("/CalcHub", new HubConfiguration());
}
}
public class CalcHub : Hub
{
public async Task<int> AddNumbers(int no1, int no2)
{
MessageBox.Show("Add Numbers");
return no1 + no2;
}
}
Client side code in web application
Installed the Microsoft.AspNet.SignalR.Client by Nuget
public class NotificationAppClient
{
Microsoft.AspNet.SignalR.Client.HubConnection connectionFr;
IHubProxy userHubProxy;
public void InitiateServerConnection()
{
connectionFr = new Microsoft.AspNet.SignalR.Client.HubConnection("http:// abc.xyz.net:12345/CalcHub", useDefaultUrl: false);
connectionFr.TransportConnectTimeout = new TimeSpan(0, 0, 60);
connectionFr.DeadlockErrorTimeout = new TimeSpan(0, 0, 60);
userHubProxy = connectionFr.CreateHubProxy("CalcHub");
userHubProxy.On<string>("addMessage", param => {
Console.WriteLine(param);
});
connectionFr.Error += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await connectionFr.Start();
};
}
public async Task<int> AddNumbers()
{
try
{
int result = -1;
connectionFr.Start().Wait(30000);
if (connectionFr.State == ConnectionState.Connecting)
{
connectionFr.Stop();
}
else
{
int num1 = 2;
int num2 = 3;
result = await userHubProxy.Invoke<int>("AddNumbers", num1, num2);
}
connectionFr.Stop();
return result;
}
catch (Exception ex)
{ }
return 0;
}
}
There is actually no need to connect and disconnect constantly. The persistent connection will work as well.
Thanks for the reply
So the code works even if it is messy. Usually this is a firewall issue so I would make absolutely sure the port is open all the way between the two services. Check both the Windows firewall and the one in the Azure Network Security Group to make sure that the port is open. I recommend double checking the "Effective Security Rules". If there are multiple security groups in play it is easy to open the port in one group but forget the other.
In order to rule out a DNS issue, you can change const string ServerURI = "http://abc.xyz.net:12345"; to `"http://*:12345" and try connecting over the public IP.
Finally if the catch blocks are actually empty as opposed to just shortening the code either remove them or add something in them that allows you to see errors. As is any errors are just being swallowed with no idea if they are happening. I didn't get any running your code, but it would be good to be sure.
As far as the method of communication goes, if you are going to stick with SignalR I would move opening the connection on the client into the InitiateServerConnection() and leave it open as long as the client is active. This is hoq SignalR is designed to work as opposed to opening and closing the connection each time. If your end goal is to push information in real time from your forms app to the web app as opposed to the web app pulling the data this is fine. For what you are currently doing this is not ideal.
For this sort of use case, I would strongly suggest looking at WebAPI instead of SignalR. If you are going to add more endpoints SignalR is going to get increasingly difficult to work with in comparison to WebApi. You can absolutely use both in parallel if you need to push data to the web client but also want to be able to request information on demand.
The Startup method on the server changes just a bit as Microsoft.AspNet.WebApi is what is being setup instead of SignalR:
class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
app.UseCors(CorsOptions.AllowAll);
app.UseWebApi(config);
}
}
Instead of a Hub you create a controller.
public class AddController : ApiController
{
// GET api/add?num1=1&num2=2
public HttpResponseMessage Get(int num1, int num2)
{
var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
response.Content = new StringContent((num1 + num2).ToString());
return response;
}
}
The client side is where things get a lot simpler as you no longer need to manage what is usually a persistent connection. InitiateServerConnection() can go away completely. AddNumbers() becomes a simple http call:
public static async Task<int> AddNumbers(int num1, int num2)
{
try
{
using(var client = new HttpClient())
{
return Int32.Parse(await client.GetStringAsync($"http://<sitename>:12345/api/add?num1={num1}&num2={num2}"));
}
}
catch (Exception e)
{
//Do something with the exception
}
return 0;
}
If that doesn't end up resolving the issue let me know and we can continue to troubleshoot.

SignalR Remote connection to RemoteDesktop

Simple situation. How to connect to SignalR server app which is in my remote desktop server(Ports enabled) using client which is in my computer. Connection works perfect while in local host, as soon as I put my remote machine IP it gives error 400.
Server side:
namespace SignalRHub
{
class Program
{
static void Main(string[] args)
{
string url = #"http://localhost:8080/";
using (WebApp.Start<Startup>(url))
{
Console.WriteLine(string.Format("Server running at {0}", url));
Console.ReadLine();
}
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
[HubName("TestHub")]
public class TestHub : Hub
{
public void DetermineLength(string message)
{
Console.WriteLine(message);
string newMessage = string.Format(#"{0} has a length of: {1}", message, message.Length);
Clients.All.ReceiveLength(newMessage);
}
}
}
Client side
namespace SignalRClient
{
class Program
{
static void Main(string[] args)
{
IHubProxy _hub;
//string url = #"http://localhost:8080/";
string url = #"http://111.11.11.111:8080";
var connection = new HubConnection(url);
_hub = connection.CreateHubProxy("TestHub");
try
{
connection.Start().Wait();
Console.WriteLine("Connection OK. Connected to: "+url);
}
catch (Exception e)
{
Console.WriteLine(e);
Console.ReadLine();
throw;
}
_hub.On("ReceiveLength", x => Console.WriteLine(x));
string line = null;
while ((line = System.Console.ReadLine()) != null)
{
_hub.Invoke("DetermineLength", line).Wait();
}
Console.Read();
}
}
}
Error it gives:
"System.AggregateException: One or more errors occurred. ---> Microsoft.AspNet.SignalR.Client.HttpClientException: StatusCode: 400, ReasonPhrase: 'Bad Request'"
I know there are similar topics but since I am only familiar with C# console and Windows apps only, would be great to found a solution for connection for app to app kind of thing. My RDP server is fully reachable I have databases and other services running there, so the problem is obviously in code. I have changed the IP in post by the way so its not real..
P.S. if I use url = #"http://*8080/" in server side, the compiler gives "HttpListenerException: Access is denied" ...
Problem solved by opening connection in server side using CMD as administrator and putting:
netsh http add urlacl http://*:8080/ user=EVERYONE
Also make sure ports are opened in firewall.
.NET application development WebSocket services in ISS has to be enabled too.

c# websphere set local port on windows tcp

I'm using the IBM websphere XMS API to connect and send messages to the mainframe. However, every message sent is sent through a new local port. Is there a way to set this to a fixed port?
A new port is created locally when the following line is hit:
var connContext = new XMSConnectionContext(connFactory.CreateConnection(), sendQ, replyQ, mqProfile, DateTime.Now.AddSeconds(qm.MqConfiguration.ConnectionPoolExpiryTime));
The code I'm using is
public IMQMessage Message { get; set; }
public void Initialise(IMQMessage message, QueueSet queueSet, QueueManager queueManager)
{
Message = message;
if (_connContext.ContainsKey(message.MessageId)) return;
_connContext.TryAdd(message.MessageId, ConnectQueueSet(queueSet, queueManager));
_connContext[message.MessageId].Connection.Start();
}
private XMSConnectionContext ConnectQueueSet(MQQueueSet queueSet, QueueManager qm)
{
var mqProfile = GetProfile(queueSet);
var xmsFactory = XMSFactoryFactory.GetInstance(XMSC.CT_WMQ);
var connFactory = xmsFactory.CreateConnectionFactory();
connFactory.SetStringProperty(XMSC.WMQ_HOST_NAME, mqProfile.ServerName);
connFactory.SetIntProperty(XMSC.WMQ_PORT, mqProfile.Port);
connFactory.SetStringProperty(XMSC.WMQ_CHANNEL, mqProfile.ChannelName);
connFactory.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, mqProfile.QueueManagerName);
connFactory.SetIntProperty(XMSC.WMQ_FAIL_IF_QUIESCE, 1);
connFactory.SetIntProperty(XMSC.WMQ_SHARE_CONV_ALLOWED, XMSC.WMQ_SHARE_CONV_ALLOWED_YES);
connFactory.SetIntProperty(XMSC.WMQ_CONNECTION_MODE, XMSC.WMQ_CM_CLIENT);
We've tried
connFactory.SetStringProperty(XMSC.XMSC_WMQ_LOCAL_ADDRESS,"(45000,45010)");
We've also tried
connFactory.SetStringProperty(XMSC.XMSC_WMQ_LOCAL_ADDRESS,"localhost(45000,45010)");
We've also tried
connFactory.SetStringProperty(XMSC.XMSC_WMQ_LOCAL_ADDRESS,"192.168.12.156(45000,45010)");
End of tests and the rest below is as it was.
IDestination sendQ = xmsFactory.CreateQueue(string.Format("queue://{0}/{1}?targetClient=1", mqProfile.QueueManagerName, mqProfile.RequestQueue));
IDestination replyQ = xmsFactory.CreateQueue(string.Format("queue://{0}/{1}?targetClient=1", mqProfile.QueueManagerName, mqProfile.ReplyQueue));
var connContext = new XMSConnectionContext(connFactory.CreateConnection(), sendQ, replyQ, mqProfile, DateTime.Now.AddSeconds(qm.MqConfiguration.ConnectionPoolExpiryTime));
QueueManager.Log.DebugFormat("XMSConnectionContext-Instantiated: ProfileName={0} SendQ={1}, ReplyQ={2}, ConnectionMetaData={3}", connContext.ProfileName, connContext.SendQ, connContext.ReplyQ, connContext.Connection);
return connContext;
}
public void Close()
{
if (_connContext != null)
{
_connContext[Message.MessageId].Connection.Stop();
}
}
Any help would be appreciated. Thanks.
XMS .NET has a connection factory property, XMSC_WMQ_LOCAL_ADDRESS that lets you specify a local port to be used while connecting to a queue manager. More details here

Categories