使用 TcpClient、Socket 和异常处理实现 PortQry





5.00/5 (1投票)
通过 TCP 查询端点服务,并确定它是否可用于连接、拒绝连接或被防火墙过滤。
引言
正如 Razan Paul 在之前的一篇文章中提到的,在使用 System.Net.Sockets.TcpClient
进行同步连接时,WinSock 中存在一个相当长的超时。 如果您尝试连接到因路径中的过滤机制(防火墙)而不可用的服务,这可能会导致相当长的等待时间(最多 30 秒)。
如果没有先阅读 Razan 是如何做到的,我永远无法达到这一步。非常感谢他这篇文章。
背景
最近,我一直在从事几个需要知道服务是否可用的项目。 我们大多数人都编写了 Ping 应用程序来跟踪设备是否启动,但在这种情况下,我需要确保端口正在响应 TCP 套接字,然后再调用一些其他的函数,这些函数在返回结果之前会执行各种冗长的子程序。 为了解决这个问题,我选择使用 Sockets.TcpClient
类中提供的异步 BeginConnect
函数,并将进程线程化以管理可能发生的长时间等待。 事实证明,这在代码方面相当难看,效率不高,导致出现大量荒谬的异常捕获。
为了解决这个问题,我编写了一个相当小的 static
函数,其交互方式类似于 PortQry 或 NMap 工具。 我们只是尝试使用 BeginConnect
子例程进行连接,然后等待直到我们的超时发生,此时我们假设端口不再响应并将其标记为已过滤。 这不仅反应非常迅速,而且在 BackgroundWorker
中使用时,线程处理得非常好。
Using the Code
以下是该函数的基本实现
public static SocketResponse SocketPoll(string Node /*127.0.0.1*/,
int tcpPort /*135*/,
int tcpTimeout /*2500*/)
{
if (Node == null || Node.Trim() == "") Node = "127.0.0.1";
if (tcpPort < 0) tcpPort = 135;
if (tcpTimeout < 0) tcpPort = 2500;
IPAddress ipNode = DetermineIP(Node);
TcpClient tSocket = new TcpClient();
DateTime endTime = DateTime.Now.AddMilliseconds(tcpTimeout);
IAsyncResult ar = tSocket.BeginConnect(ipNode, tcpPort, null, tSocket);
请注意前一段代码中的 null
。 我选择不使用 CallBack
,因为我并不关心任何清理,直到我们回到主要的代码位。 我们实际上是将其视为同步函数,如果该函数未在我们分配的时间内完成,则终止请求。
如果您希望在连接完成后对打开的连接执行某些操作,您可以编写一个接受 AsyncCallback
的回调函数,对 IAsyncResult
执行某些操作,然后返回到调用函数。 我从这个方法开始,然后意识到重新实现 IAsyncResult
接口和 AsyncCallback
委托将是多么不可思议的任务。
while(DateTime.Now < endTime)
{
if (ar.IsCompleted)
{
Socket s = tSocket.Client;
try
{
if (!s.Poll(100, SelectMode.SelectError))
{
return SocketResponse.LISTENING;
}
s.EndConnect(ar);
tSocket.Close();
}
TcpClient.Client
公开了 TcpClient
构建于其之上的 Socket
类,允许我们使用 Socket.Poll
来获取 SocketError
(ICMP 响应),如果尝试失败。
如果我们成功连接,轮询失败,我们将返回 LISTENING SocketResponse
。
catch (System.Net.Sockets.SocketException SocketEx)
{
switch ((SocketError)Enum.ToObject(typeof(SocketError),
SocketEx.ErrorCode))
{
case SocketError.ConnectionRefused:
return SocketResponse.NOT_LISTENING;
case SocketError.HostUnreachable:
case SocketError.HostDown:
case SocketError.NetworkUnreachable:
return SocketResponse.UNREACHABLE;
default: //Else...
return SocketResponse.OTHER;
}
}
}
Poll(100, SelectMode.SelectError)
会抛出与收到的 ICMP 消息关联的 SocketException
。 Thread.Sleep(10);
}
//If connection times out without a ICMP message response...
return SocketResponse.FILTERED;
}
因此,如果尚未建立 TCP 连接,或者尚未因收到指示连接失败的 ICMP 消息而引发异常,则线程会休眠 10 毫秒,允许其他线程在此期间执行,然后我们重新开始,直到超过超时。
在超过超时时,我们只是将 FILTERED SocketException
返回到调用函数。
关注点
Windows XP Service Pack 3 之后不再支持原始套接字。 太棒了。
历史
- 2010 年 6 月 4 日:初始发布