65.9K
CodeProject 正在变化。 阅读更多。
Home

在本地网络上定位 SQL Server 实例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (42投票s)

2005年11月24日

CPOL

6分钟阅读

viewsIcon

316473

downloadIcon

5050

一篇关于在本地网络上定位 MS SQL Server 实例并检索其信息的文章。

引言

本文介绍如何检索本地网络上运行 MS SQL Server 的 PC 列表,并获取有关实例的信息,例如服务器名称、实例名称、版本和数据库。

背景

要在命令提示符下获取附近的 SQL Server 列表,只需键入“osql /L”。但是,在应用程序中以编程方式获取它们会更棘手。最常见的用途是创建一个托管代码中的连接字符串构建窗体,这将是我下一篇文章的主题。

另外请注意,尽管主题是 SQL Server,但本文的前半部分主要涉及套接字。

要创建 SQL Server 实例列表,我首先尝试了几种不同的方法,它们都有两个共同点:耗时很长,并且在查找网络上的 SQL Server 实例方面仅取得微乎其微的成功。最终,我想到在运行数据包嗅探器(我使用了 Ethereal)的情况下运行“osql -L”,以查看 osql 是如何实现的。

它的方法是在端口 1434 上发送一个广播 UDP 数据包,其中仅包含一个字节 0x02,每个服务器都会响应。发送这样的消息非常简单

Socket socket = new Socket(AddressFamily.InterNetwork, 
              SocketType.Dgram, ProtocolType.Udp );
//  For .Net v 1.1 the options are cumbersome & hidden.
    socket.SetSocketOption(SocketOptionLevel.Socket, 
              SocketOptionName.Broadcast, 1);
    socket.SetSocketOption(SocketOptionLevel.Socket, 
              SocketOptionName.ReceiveTimeout, 3000);
    
//  For .Net v 2.0 it's a bit simpler
//  socket.EnableBroadcast = true;    // for .Net v2.0
//  socket.ReceiveTimeout = 3000;     // for .Net v2.0

IPEndPoint ep = new IPEndPoint(IPAddress.Broadcast, 1434);
byte[] msg = new byte[] { 0x02 };
socket.SendTo(msg, ep);

设置超时非常重要。否则,Receive 方法将一直等待(永远!),直到收到东西为止。但是,由于我们将等待来自未知数量服务器的响应,因此我们必须决定在某个时候放弃。我最初将这一点设置为 3 秒。在我意识到 Socket 类具有(隐藏的)广播和超时选项之前,我有一个派生自 UdpClient 并实现异步接收的复杂类。

结果证明,在设置了正确的选项后,获取响应就像您希望的那样简单。

byte[] bytBuffer = new byte[256];
socket.Receive(bytBuffer);

Receive 方法将获取一个服务器的响应,因此我们需要将该代码放入一个循环中以获取所有响应。当然,一旦收到响应,我们就需要对其进行解释。为此,我创建了一个名为 SqlServerInfo 的简单类,我将在稍后进一步解释。

在接收、处理和存储响应之后,我们只需继续循环,直到在超时时间内未收到响应为止。使用超时的缺点是,当达到超时时,Socket 会引发 SocketException,这意味着我们每次调用此方法时都必须承担处理 throw 的开销。这 P 并且还 P 超时延迟本身。我设法找到了一种最小化这一点的方法。

我最初将超时设置为 3 秒。这里的问题是,在每个响应之后,我都会再次调用 Receive,并且超时计时器会重新开始。因此,该方法总共需要的时间是:总的实际处理时间 + 超时时间 + throw 开销。在我的测试中,这大约需要 5 秒。对我来说,这似乎太长了——请记住,这是我们将在窗体的 Page_Load 中执行的操作。我注意到的一件事是,在我发送广播消息后,会有一个短暂的延迟,因为消息发送出去,远程服务器接收到它并准备好响应。但是,在那之后,所有响应都会打包到来。因此,我决定在处理完第一个响应后,将超时重置为 0.3 秒。这 P 将总时间 P 缩短到大约 1-2 秒,这是一个更合理 P 的延迟。(但是,如果网络上根本没有服务器,它仍然需要 3 秒以上才能意识到这一点。)

现在,回到解释响应,这非常简单。前三个字节是 0x05,后两个字节给出其余部分的长度,其余部分是纯 ASCII 文本,由分号分隔的对组成。

data item 1 name ; data item 1 value ; data item 2 name  ; data item 2 value;

或者更具体地说

ServerName;DATA001;InstanceName;MSSQLSERVER;IsClustered;No;Version;8.00.194;tcp;1433

为了利用这一点,我们将不得不从 ASCII 字符的字节数组转换为 .NET 的 UNICODE 字符字符串,框架很乐意为此提供一个方法(尽管它将其隐藏在 System.Text.ASCIIEncoding.ASCII 下)。然后,分离各部分,并将数据值移动到可以更轻松访问的属性中。

当然,虽然广播响应中提供的信息很有用,但它没有提供一个至关重要的数据:该服务器上的数据库列表。要获取该信息,我们需要使用更传统的数据库访问方法。

但是,要做到这一点,我们将不得不连接到服务器,这意味着需要一个连接字符串,这又意味着需要用户名和密码。SqlServerInfo 为这些提供了读写属性,以及一个布尔型 IntegratedSecurity 选项(其他属性是只读的)。

一旦我们建立了连接,获取目录列表就是 OleDbConnection 对象内置的方法。

DataTable schemaTable = 
   myConnection.GetOleDbSchemaTable(OleDbSchemaGuid.Catalogs, null);

使用代码

所有这些都打包在 SqlServerInfo 类中。它有一个静态方法 Seek(),该方法获取有关网络上 SQL Server 实例的信息,并返回一个 SqlServerInfo 对象数组。

SqlServerInfo[] servers = SqlServerInfo.Seek();

每个 SqlServerInfo 对象都包含描述特定 MS SQL Server 实例的多个属性。其中每个属性都是只读的。

  • public string ServerName
  • public string InstanceName
  • public bool IsClustered
  • public string Version
  • public int TcpPort
  • public string NamedPipe
  • public IPAddress Address

InstanceName 通常是“MSSQLSERVER”,这是默认值,如果安装时未指定特定名称。版本应为 SQL Server 2000 的“8.0.xxx”。TcpPort 通常为 1433。

请注意,尽管 Address 是一个属性,但在当前实现中,它将始终为 null --- 直到我找到一种方法来找出服务器的 IP 地址。Socket.Receive 方法不会说明数据来自哪个计算机——它只是假定为刚发送数据的计算机——当发送广播消息时,这种天真性就 P 显而易见了。(如果有人知道如何从 Socket 中获取该信息,请告诉我。)

接下来是四个读写属性,必须设置它们才能从该类中获取更多信息。

  • public string UserId
  • public string Password
  • public bool IntegratedSecurity
  • public int TimeOut

这些 P 确定我们将如何尝试连接到该服务器。IntegratedSecurity 默认为 trueTimeOut 默认为 2 秒,因此如果这些对您有用,则无需执行任何其他操作。设置 UserIdPassword 中的任何一个都会将 IntegratedSecurity 设置为 false

最后一个属性检索服务器上可用的数据库列表。

  • public StringCollection Catalogs

第一次调用时,它将尝试连接到服务器,因此需要正确设置上述属性。

最后,有两个方法

  • public bool TestConnection()
  • public override string ToString()

TestConnection 顾名思义——它测试您是否可以使用给定的 userID/password 进行连接。

ToString 返回 ServerName(如果 InstanceName 是默认值)或 ServerName / InstanceName。无论哪种情况,这都是您需要在连接字符串中指定的 Data Source。作为 ToString,您可以将 ListBox 或类似控件的 DataSource 属性设置为 SqlServerInfo 对象数组,然后将显示相应的值。

SqlServerInfo Data = servers[0];
Console.WriteLine(Data.ToString());
Console.WriteLine("Version:", Data.Version);

Data.IntegratedSecurity = true;
foreach(string Catalog in Data.Catalogs)
{
    Console.WriteLine("    {0}", Catalog);
}

历史

  • 2005 年 11 月 20 日 - v 1.0 初始发布。
© . All rights reserved.