简体   繁体   English

使用 .NET Core 3.1 在网络上搜索所有 SQL 服务器实例

[英]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.

相关问题 .NET Core 3.1 和 SQL 服务器hierarchyid - 多个异常 - .NET Core 3.1 and SQL Server hierarchyid - multiple exceptions 如何使用 NLog in.Net Core 3.1 登录 SQL 服务器? - How to Logging To SQL Server With NLog in .Net Core 3.1? .NET Core 3.1 Linq 和 SQL 服务器无法转换 DateTime - .NET Core 3.1 Linq and SQL Server can not translate DateTime 如何在 Net core 3.1 上路由所有子域 - How to route all subdomain on Net core 3.1 .net核心和ef核心sql server错误提供程序:SQL网络接口,错误:8 - 不支持协议 - .net core and ef core sql server error provider: SQL Network Interfaces, error: 8 - Protocol not supported Migration .NET Core 2.2 to .NET Core 3.1- LINQ LEFT JOIN - Calling Take() on DefaultIfEmpty does not translate to server side SQL - Migration .NET Core 2.2 to .NET Core 3.1- LINQ LEFT JOIN - Calling Take() on DefaultIfEmpty does not translate to server side SQL .Net Core 3.1 - 在连接 Sql Server 时卡在 Linux 中的 connection.open() - .Net Core 3.1 - Stuck on connection.open() in the Linux while connecting Sql Server 无法从 Docker 中的 ASP.NET Core 3.1 连接到本地 SQL Server - Cant connect to on premise SQL Server from ASP.NET Core 3.1 in Docker "如何编写参数为字符串列表的存储过程 [ SQL Server,ASP.NET Core 3.1 ]" - How to write a stored procedure in which the parameters are a list of strings [ SQL Server, ASP.NET Core 3.1 ] 无法从 Docker Z9E0DA8438E1E38A1C30F4B176CE7 Core 连接到 Docker SQL 服务器 - Can not connect to Docker SQL Server from Docker ASP.NET Core 3.1
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM