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






4.80/5 (42投票s)
一篇关于在本地网络上定位 MS SQL Server 实例并检索其信息的文章。
- 下载源代码 - 6.84 Kb (包含带 VS2003 和 VS2005 项目文件的演示)
引言
本文介绍如何检索本地网络上运行 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
默认为 true
,TimeOut
默认为 2 秒,因此如果这些对您有用,则无需执行任何其他操作。设置 UserId
或 Password
中的任何一个都会将 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 初始发布。