如何在C#中检查(TCP)套接字是否已连接或断开?

36

我应该如何检查一个(TCP)套接字以确定它是否已连接?

我已经阅读了MSDN中关于Socket.Connected属性的内容,但它仅显示最后一次I/O的状态。这对我来说没有用,因为我想在尝试从套接字中读取数据之前进行检查。备注部分还指出:

如果您需要确定当前连接的状态,请进行非阻塞的零字节Send调用。如果调用成功返回或引发WAEWOULDBLOCK错误代码(10035),则套接字仍处于连接状态;否则,套接字不再连接。

同一页上的示例展示了如何实现。(1) 但是Ian Griffiths的一篇文章说我应该从套接字中读取,而不是通过套接字发送

Pete Duniho的另一篇文章说:

在调用Shutdown()之后,调用Receive()直到它返回0(假设远程端点不会实际发送任何内容,这将在远程端点接收到您的所有数据后发生)。除非您这样做,否则无法保证远程端点实际上已经接收到您发送的所有数据,即使使用了延迟套接字也是如此。
我真的不理解他关于调用Receive()以确保远程端点实际上已经接收到我发送的所有数据的声明。(套接字是否阻塞接收直到发送缓冲区为空?)
我对提出的不同方法感到困惑。你能解释一下吗?

(1) 我想知道为什么关于 Socket.Connected 属性的 example 分配了一个1字节的数组,即使它调用了长度为0的 Send 方法?


检查这个链接...在最后你可以找到一个关于此的流程 https://dev59.com/xHM_5IYBdhLWcg3waSb9 - Operagust
4个回答

22

一个socket的关闭会在几个方面改变它的行为,所以这两种方法都是有效的:)

使用这两种方法,您实际上检查了断开连接后socket行为发生更改的那些部分。

我真的不明白他关于调用Receive()来确保远程端点是否已经接收到我发送的所有数据的说法。(socket是否会堵塞接收直到发送缓冲区为空?)

TCP是可靠的协议,这意味着您发送的每个数据包必须得到确认。确认意味着发送具有设置ACK位的数据包。这些数据包可能包含额外的(有效载荷)数据,也可能不包含。

当套接字连接时,Receive()将阻塞,直到套接字接收到非空负载的数据包。但是,当套接字断开连接时,Receive()将在最后一个ACK数据包到达时立即返回。

调用Receive()可以确保您要么从远程端点接收 最后一个ACK数据包, 要么等待断开超时并且您无法在该套接字上再接收任何内容。

同一页上的示例说明了如何做到这一点。(我想知道为什么它分配了一个1字节的数组,即使它调用长度为0的Send()?)但 Ian Griffiths 的帖子说我应该从套接字读取,而不是通过它发送。

当向套接字send()时,您实际上尝试将一些数据附加到套接字队列的末尾。如果缓冲区中还有剩余空间,则Send()立即返回;如果没有,则Send()阻塞,直到有空间。

当套接字处于断开连接状态时,TCP/IP堆栈会阻止所有进一步的缓冲区操作,这就是为什么Send()会返回错误的原因。

Send() 实现了基本的指针检查,这意味着当传递给它一个 NULL 指针时,它会失败。你可能会将任何非空常量作为指针传递,但最好分配 1 字节而不是随意构造一个常量。


你可以使用任何你喜欢的方法,因为它们都不会消耗资源。只要用于套接字连接检查,它们就是相同的。

对于我来说,我更喜欢 Receive(),因为这通常是你在循环中等待运行的程序。 如果从 Receive() 得到一个非零值,你就会处理数据;如果得到一个零,你就会处理断开连接。


非常感谢!不过我仍需要一些澄清。如果我没有通过套接字发送任何内容,那么在尝试从中读取之前,哪种方法最好用来检查套接字是否存活?我应该尝试接收,如果收到0,则表示套接字已关闭吗? - Hosam Aly
1
还有,为什么示例要分配一个1字节的数组,然后将其传递给长度为0的send()函数?这与分配new byte [0]有什么不同吗? - Hosam Aly

6

如果您需要确定连接的当前状态,请进行非阻塞、零字节发送调用。如果该调用成功返回或抛出 WAEWOULDBLOCK 错误代码(10035),那么套接字仍然连接;否则,套接字已经断开连接。但不幸的是,这种方式甚至无法正常工作!

mySocket.Blocking = false;
byte[] buffer = new byte[1];
int iSent = mySocket.Send(buffer, 0, SocketFlags.None);
bConnected = mySocket.Connected;
bConnected 总是返回 true,即使以太网电缆已拔出,调用总是成功返回。此外,不幸的是,发送任何实际数据也无法检测到连接中断。
buffer[0] = 0xff ;
int iSent = mySocket.Send(buffer, 1, SocketFlags.None);

重复返回1,就好像实际上已经发送了某些内容一样。然而,所涉及的设备已经不再连接。


2
我认为从(一般的)MSDN示例中可以推断出,在发生异常时,你必须处理异常并手动返回false。也就是说,在异常情况下,Socket.Connected的值可能会变得无效。从你的代码片段中无法确定你是否正在手动处理异常。 - Tod

1
通常情况下,人们会使用Socket.Select方法来确定一组socket的状态(对于单个socket则使用Socket.Poll)。
这两种方法都允许你查询一个socket的状态。 假设首先跟踪了socket连接,然后在尝试读取之前通常会在socket上调用Select/Poll。 如果Select/Poll指示socket可读,则表示:
- 要么socket中有可读数据,此时Receive返回可读取的数据。 - socket已关闭,在这种情况下,当您调用Receive时,将立即返回0字节(即如果Select/Poll指示socket可读且您调用Receive但它立即返回0字节,则说明连接已关闭、重置或终止)。
就我个人而言,我从未使用过Poll - 我总是使用Select,但MSDN似乎认为Poll基本上与Select相同,只不过适用于单个socket。
我还要补充说,大多数情况下,使用Select是处理Socket连接最有效和最好的方式。

1
谢谢。但是根据MSDN的说法,这种方法无法检测到某些连接问题,例如断开的网络电缆或远程主机不正常关闭。您必须尝试发送或接收数据以检测这些错误。... - Hosam Aly
你能举个如何处理这种情况的例子吗? - Hosam Aly

1
我实际上不明白他关于调用Receive()以确保远端节点实际接收了我发送的所有数据的陈述。 @PeteDuniho的帖子并不是关于建立连接状态,而是关于以这种方式终止连接,以便您知道同行已收到您的所有数据。
套接字不会阻塞接收,直到发送缓冲区为空吗? 不会,但是如果您关闭套接字,然后读取直到EOS,您要等待对等方读取所有数据,直到他获得EOS然后关闭套接字。 因此,您可以保证所有数据都进入了对等应用程序。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接