TcpClient write 方法是否保证数据被传送到服务器?

Does TcpClient write method guarantees the data are delivered to server?

我在客户端和服务器上都有一个单独的线程,它们是 reading/writing 数据 to/from 套接字。

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

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

我阅读了 Socket 和 TcpClient 的文档 class,其中 none 描述了这种情况。

TcpClient 使用本身保证数据传输的 TCP 协议。如果未交付数据,您将获得异常。如果没有抛出异常 - 数据已经传送。

TCP协议的说明请看这里:http://en.wikipedia.org/wiki/Transmission_Control_Protocol

每次发送数据时,发送计算机等待确认包到达,如果没有到达,则重新尝试发送,直到成功、超时或永久网络故障已检测到(例如,电缆断开连接)。后两种情况会抛出异常

因此,TCP 提供保证 数据传输,因为您总是 知道目的地是否收到您的数据

所以,为了回答你的问题,你不需要在使用 TcpClient 时实现自定义 ACK 逻辑,因为它是多余的

保证永远不可能。您可以知道的是,数据已经离开您的计算机,按照数据发送的顺序传送到另一端。

如果你想要一个非常可靠的系统,你应该根据你的需要自己实现确认逻辑。

我同意丹尼斯的观点。

根据文档(和我的经验):此方法将阻塞,直到所有字节都被写入或在错误时抛出异常(例如断开连接)。如果方法 returns 您可以保证字节已被另一 端在 TCP 级别 .[=12 传送和读取=]

Vojtech - 我认为您错过了文档,因为您需要查看正在使用的 Stream。

参见:MSDN NetworkStream.Write method,在备注部分:

The Write method blocks until the requested number of bytes is sent or a SocketException is thrown.

备注

  • 确保监听应用程序正确读取消息是另一个问题,框架无法保证这一点。
  • 在异步方法(例如 BeginWrite 或 WriteAsync)中,这是一个不同的球赛,因为方法 returns 立即和确保完成的机制不同(EndWrite 或任务完成与代码配对)。

所有 returning send() 调用意味着(或您使用的任何包装器,如 SocketTcpClient)在流媒体上,阻塞互联网套接字是字节放在发送机器的缓冲区中。

MSDN Socket.Send():

A successful completion of the Send method means that the underlying system has had room to buffer your data for a network send.

并且:

The successful completion of a send does not indicate that the data was successfully delivered.

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

The successful completion of a send function does not indicate that the data was successfully delivered and received to the recipient. This function only indicates the data was successfully sent.

send() returning 的调用 而不是 意味着数据已成功传送到另一端并由消费应用程序读取。

当数据没有及时确认,或者对方发送RST时,Socket(或者wrapper)会进入故障状态,使得下一个 send()recv() 失败。

所以为了回答你的问题:

Does it mean that when .Write() method does not throw the data were delivered correctly to the other party or do I need to implement custom ACK logic?

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

例如,如果服务器发送的消息指示客户端发生某种状态更改,客户端必须申请保持同步,就会出现这种情况。如果客户端不确认该消息,则服务器无法确定客户端是否处于最新状态。

在那种情况下,您可以更改您的协议,以便某些消息具有接收方必须 return 所要求的响应。请注意,实施应用程序协议非常容易出错。如果您愿意,可以使用 state machine 实现各种协议规定的消息流, 对于服务器和客户端。

当然还有其他解决该问题的方法,例如给每个状态一个唯一的标识符,在尝试涉及该状态的任何操作之前与服务器进行验证,触发重试先前失败的同步。

另见 How to check the capacity of a TCP send buffer to ensure data delivery, Finding out if a message over tcp was delivered, C socket: does send wait for recv to end?

@CodeCaster 的回答是正确的,并突出显示了指定 .Write() 行为的 .NET 文档。这里有一些完整的测试代码来证明他是对的,而其他答案说 "TCP guarantees message delivery" 是明确错误的:

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();
        }
    }
}

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

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

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

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

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

如果我重复上述过程但禁用我的无线网卡而不是断开连接,那么第二个 .Write 将引发异常。

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