TcpClient写入方法是否保证数据被传递到服务器?

Voj*_*h B 4 c# sockets stream tcpclient

我在客户端和服务器上都有一个单独的线程,它正在向/从套接字读取/写入数据.

我正在使用同步TcpClient im(如文档中所建议):https: //msdn.microsoft.com/cs-cz/library/system.net.sockets.tcpclient%28v=vs.110%29.aspx

当连接关闭时.Read()/.Write()抛出异常.是否意味着当.Write()方法没有将数据正确传递给另一方时,或者我是否需要实现自定义ACK逻辑

我阅读了Socket和TcpClient类的文档,但没有一个描述这种情况.

Cod*_*ter 7

所有返回的send()调用意味着(或者你使用的任何包装器,例如Socket或者TcpClient)在流式阻塞互联网套接字上是字节被放置在发送机器的缓冲区中.

MSDNSocket.Send():

成功完成Send方法意味着底层系统有足够的空间来缓冲网络发送的数据.

和:

成功完成发送并不表示数据已成功传送.

对于.NET,底层实现是WinSock2,文档:send():

成功完成发送功能并不表示数据已成功发送并接收到收件人.此功能仅表示数据已成功发送.

send()返回的调用并不意味着数据已成功传递到另一方并由消费应用程序读取.

如果未及时确认数据,或者另一方发送RST,则Socket(或任何包装器)将处于故障状态,进行下一次 send()recv()失败.

所以为了回答你的问题:

是否意味着当.Write()方法没有将数据正确传递给另一方时,或者我是否需要实现自定义ACK逻辑?

不,它不是,是的,你应该 - 如果它对你的应用程序很重要,它知道另一方已阅读该特定消息.

例如,如果服务器发送的消息指示客户端上的某种状态改变,客户端必须应用该状态改变以保持同步.如果客户端不确认该消息,则服务器无法确定客户端是否具有最新状态.

在这种情况下,您可以更改协议,以便某些消息具有接收方必须返回的必需响应.请注意,实施应用程序协议非常容易出错.如果您有意愿,可以使用状态机为服务器和客户端实现各种协议规定的消息流.

当然,对于该问题还有其他解决方案,例如为每个状态提供唯一标识符,在尝试涉及该状态的任何操作之前通过服务器验证该标识符,从而触发先前失败的同步的重试.

另请参阅如何检查TCP发送缓冲区的容量以确保数据传送,查明是否已通过tcp发送消息,C套接字:是否发送等待recv结束?

  • 计算机A向计算机B发送数据.计算机B接收数据,但在能够返回ACK消息之前,发生错误,并且无法将其发送回计算机A.问题是计算机B是否收到了消息或不?实际上,它已收到,但计算机A会认为因为它没有收到任何确认,所以没有收到.正如OP要求的那样,如果你想建立一个可靠的系统,你会依赖于此吗? (2认同)

Ben*_*Ben 6

@CodeCaster 的答案是正确的,并突出显示了指定 .Write() 行为的 .NET 文档。这里有一些完整的测试代码来证明他是对的,而其他答案中说“TCP 保证消息传递”之类的东西显然是错误的:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace TestEvents
{
    class Program
    {
        static void Main(string[] args)
        {
            // Server: Start listening for incoming connections
            const int PORT = 4411;
            var listener = new TcpListener(IPAddress.Any, PORT);
            listener.Start();

            // Client: Connect to listener
            var client = new TcpClient();
            client.Connect(IPAddress.Loopback, PORT);

            // Server: Accept incoming connection from client
            TcpClient server = listener.AcceptTcpClient();

            // Server: Send a message back to client to prove we're connected
            const string msg = "We are now connected";
            NetworkStream serverStream = server.GetStream();
            serverStream.Write(ASCIIEncoding.ASCII.GetBytes(msg), 0, msg.Length);

            // Client: Receive message from server to prove we're connected
            var buffer = new byte[1024];
            NetworkStream clientStream = client.GetStream();
            int n = clientStream.Read(buffer, 0, buffer.Length);
            Console.WriteLine("Received message from server: " + ASCIIEncoding.ASCII.GetString(buffer, 0, n));

            // Client: Close connection and wait a little to make sure we won't ACK any more of server's messages
            Console.WriteLine("Client is closing connection");
            clientStream.Dispose();
            client.Close();
            Thread.Sleep(5000);
            Console.WriteLine("Client has closed his end of the connection");

            // Server: Send a message to client that client could not possibly receive
            serverStream.Write(ASCIIEncoding.ASCII.GetBytes(msg), 0, msg.Length);
            Console.WriteLine(".Write has completed on the server side even though the client will never receive the message.  server.Client.Connected=" + server.Client.Connected);

            // Let the user see the results
            Console.ReadKey();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

需要注意的是,执行过程一直正常进行,serverStream 没有迹象表明第二个 .Write 不成功。尽管事实上不可能将第二条消息传递给其收件人。要更详细地了解发生了什么,您可以将 IPAddress.Loopback 替换为到您的计算机的更长的路由(例如,让您的路由器将端口 4411 路由到您的开发计算机并使用调制解调器的外部可见 IP 地址)并监视Wireshark 中的那个端口。输出如下所示:

Wireshark 端口活动的屏幕截图

端口 51380 是一个随机选择的端口,代表上面代码中的客户端 TcpClient。有双重数据包,因为此设置在我的路由器上使用 NAT。所以,第一个 SYN 数据包是我的电脑 -> 我的外部 IP。第二个 SYN 数据包是我的路由器 -> 我的电脑。第一个 PSH 数据包是第一个 serverStream.Write。第二个 PSH 数据包是第二个 serverStream.Write。

有人可能会声称客户端使用 RST 数据包在 TCP 级别进行 ACK,但是 1) 这与 TcpClient 的使用无关,因为这意味着 TcpClient 正在使用关闭的连接进行 ACK 和 2) 考虑当连接完全断开时会发生什么在下一段禁用。

如果我注释掉处理流的行并关闭客户端,而是在 Thread.Sleep 期间断开与无线网络的连接,控制台会打印相同的输出,我从 Wireshark 得到这个:

无线连接断开时的 Wireshark 活动

基本上,即使没有发送 PSH 数据包, .Write 也没有异常返回,更不用说收到 ACK 了。

如果我重复上面的过程但禁用我的无线卡而不是仅仅断开连接,那么第二个 .Write 会抛出一个异常。

最重要的是,@CodeCaster 的答案在所有层面上都是明确正确的,而且这里不止一个其他答案是不正确的。