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

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

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

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

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

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

MSDN Socket.Send()

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

和:

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

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

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

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

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

所以为了回答你的问题:

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

不,它不是,是的,你应该 – 如果它对你的应用程序很重要,它知道另一方已经阅读了那个特定的消息。

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

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

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

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

@ CodeCaster的答案是正确的,并突出显示指定.Write()行为的.NET文档。 这里有一些完整的测试代码来certificate他是对的,其他答案说“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屏幕捕获端口活动

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

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

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

无线断开时的Wireshark活动

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

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

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

TcpClient使用TCP协议,它本身保证数据传输。 如果数据未送达,您将获得例外。 如果没有抛出exception – 数据已经传递。

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

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

因此,TCP提供了保证数据传递的意义,即您始终知道目的地是否接收了您的数据

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

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

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

我同意丹尼斯的观点。

从文档(和我的经验):这个方法将阻塞,直到所有字节都被写入或在出错时抛出exception(例如断开连接)。 如果方法返回,则保证字节在TCP级别上由另一方传递和读取。

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

请参阅: MSDN NetworkStream.Write方法 ,在备注部分中:

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

笔记

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