[英]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.我的应用程序要求用户在其网络上使用 select 一个 SQL 服务器实例作为应用程序的数据库服务器。 I need to populate a list of all available SQL Server instances from which to make this selection.
我需要填充所有可用 SQL 服务器实例的列表,从中进行选择。
The application is developed in .NET Core 3.1.该应用程序是在 .NET Core 3.1 中开发的。 I am unable to simply use SqlDataSourceEnumerator as it is not available in .NET Core.
我无法简单地使用 SqlDataSourceEnumerator,因为它在 .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.如果可能的话,我想避免将 PowerShell 功能导入应用程序,因为它可能需要在机器上安装额外的 PowerShell 模块,但如果这是唯一的方法,我会考虑它。
The only way I've found to do this in .NET Core is to manually send and receive UDP messages.我发现在 .NET Core 中执行此操作的唯一方法是手动发送和接收 UDP 消息。
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.这似乎比旧的 SMO EnumerateServers 方法更快,可能是因为它不测试每个实例的连接性,我有点喜欢。 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.此 function 从随机选择的 UDP 端口发送,因此请注意需要防火墙规则以允许可执行文件的流量,但您可以限制为远程端口 1434。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.