繁体   English   中英

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

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

我的应用程序要求用户在其网络上使用 select 一个 SQL 服务器实例作为应用程序的数据库服务器。 我需要填充所有可用 SQL 服务器实例的列表,从中进行选择。

该应用程序是在 .NET Core 3.1 中开发的。 我无法简单地使用 SqlDataSourceEnumerator,因为它在 .NET Core 中不可用。

是否有替代方法可以进行此查询?

如果可能的话,我想避免将 PowerShell 功能导入应用程序,因为它可能需要在机器上安装额外的 PowerShell 模块,但如果这是唯一的方法,我会考虑它。

我发现在 .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]
            };
        }
    }
}

这似乎比旧的 SMO EnumerateServers 方法更快,可能是因为它不测试每个实例的连接性,我有点喜欢。 我想知道那里有哪些服务器,即使我必须先修复某些东西才能真正连接。

此 function 从随机选择的 UDP 端口发送,因此请注意需要防火墙规则以允许可执行文件的流量,但您可以限制为远程端口 1434。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM