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

时间:2015-06-08 07:35:37

标签: 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类的文档,但没有一个描述这种情况。

5 个答案:

答案 0 :(得分:7)

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

MSDN Socket.Send()

  

成功完成Send方法意味着底层系统有空间缓冲数据以进行网络发送。

  

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

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

  

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

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

如果未及时确认数据,或者另一方发送RST,则Socket(或任何一个包装器)将处于故障状态,使 next {{ 1}}或send()失败。

所以为了回答你的问题:

  

这是否意味着当.Write()方法不抛出数据时会传递   正确地对方或我是否需要实现自定义ACK逻辑?

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

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

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

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

另请参阅How to check the capacity of a TCP send buffer to ensure data deliveryFinding out if a message over tcp was deliveredC socket: does send wait for recv to end?

答案 1 :(得分:4)

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

需要注意的是,执行正常通过程序进行,而serverStream没有指示第二个.Write不成功。尽管事实上无法将第二条消息传递给其接收方。要更详细地了解正在进行的操作,您可以使用更长的路径替换IPAddress.Loopback(例如,将路由器路由端口4411连接到开发计算机并使用外部可见的IP地址)调制解调器)并监视Wireshark中的端口。这是输出的样子:

Wireshark screen capture of port activity

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

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

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

Wireshark activity when wireless is disconnected

基本上,.Write返回时没有Exception,即使没有调度PSH数据包,更不用说收到了ACK。

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

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

答案 2 :(得分:0)

TcpClient使用TCP协议,它本身保证数据传输。如果数据未送达,您将收到异常。如果没有抛出异常 - 数据已经传递。

请在此处查看TCP协议的说明:http://en.wikipedia.org/wiki/Transmission_Control_Protocol

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

因此,TCP 保证数据传递,即总是知道目的地是否收到您的数据

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

答案 3 :(得分:-1)

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

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

答案 4 :(得分:-2)

我同意丹尼斯的意见。

从文档(以及我的经验):此方法将阻塞,直到写入所有字节或在出错时抛出异常(例如断开连接)。如果方法返回,则保证字节是由TCP级别的其他 端传递和读取的

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

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

  

Write方法将一直阻塞,直到发送请求的字节数或抛出SocketException为止。

注释

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