当前位置: 首页 > 知识库问答 >
问题:

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

汤嘉平
2023-03-14

我在客户机和服务器上都有一个独立线程,用于向套接字读/写数据。

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

当连接关闭时。read()/.write()引发异常。这是否意味着.write()方法不会将正确交付的数据抛给另一方,或者我需要实现自定义的ACK逻辑?

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

共有2个答案

赫连泰宁
2023-03-14

@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没有任何迹象表明第二次写入未成功。这是尽管事实是不可能有第二个消息可以传递给它的接收者。要更详细地了解情况,您可以将ipaddress.loopback替换为到计算机的更长路由(例如,让路由器将端口4411路由到开发计算机,并使用调制解调器的外部可见IP地址),然后在Wireshark中监视该端口。输出如下所示:

端口51380是一个随机选择的端口,在上面的代码中代表客户端TcpClient。因为此安装程序在我的路由器上使用NAT,所以会出现双重数据包。所以,第一个SYN数据包是我的计算机->我的外部IP。第二个SYN数据包是my Router->my Computer。第一个PSH数据包是第一个ServerStream.Write。第二个PSH数据包是第二个ServerStream.Write。

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

如果在Thread.sleep期间注释掉处理流并关闭客户端的行,而不是断开与无线网络的连接,则控制台将打印相同的输出,并且从Wireshark获得以下内容:

基本上,.write无一例外地返回,即使没有发送PSH数据包,更不用说收到ACK了。

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

总之,@codecaster的答案在所有层面上都是正确的,这里的其他答案不止一个是不正确的。

子车高歌
2023-03-14

返回的send()调用(或您使用的任何包装器,如sockettcpclient)在流式阻塞internet套接字上的全部含义是,字节被放置在发送计算机的缓冲区中。

MSDNsocket.send():

成功完成发送方法意味着基础系统有空间缓冲您的数据以进行网络发送。

和:

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

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

成功完成发送功能并不表示数据已成功传递和接收到收件人。此函数仅表示数据已成功发送。

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

当数据没有及时确认,或者当另一方发送RST时,套接字(或任意一个包装器)将处于故障状态,使得下一个send()recv()失败。

所以为了回答你的问题:

这是否意味着.write()方法不会将正确交付的数据抛给另一方,或者我需要实现自定义的ACK逻辑?

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

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

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

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

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

 类似资料:
  • 给出了一种Spring-MVC控制器的方法: 如果请求URL中缺少参数,则报告一个错误,例如: JBWeb000068:message Required int参数“param1”不存在 如果省略,则可以执行此操作,但问题是,如果缺少参数,则不会报告任何错误。另一方面,如果包含,则在GET请求中需要一个名为“model”的参数。有没有一种方法可以使模型参数成为强制性的,同时保持相同的请求URL?

  • 我目前正在开发一个Android应用程序,我想包括Firebase云消息传递。我计划让树莓派每5分钟左右检查一个网站,并在发生变化时发送推送通知。在官方留档中,他们说我需要一个应用服务器才能通过Firebase发送消息。 这是否意味着我需要让我的Raspi全天候作为服务器运行,并且需要一个静态的IP/域?还是让我的Raspi通过Api(https://fcm.googleapis.com/fcm/

  • 我们有两个班级。 我试图弄清楚函数 是否会被视为或。我尝试使用关键字,它说该方法未被覆盖。但我想知道 关键字是否仅在父类中的函数被编写为函数时才有效,并且在上述情况下是否可以将该函数视为被覆盖。 另外,我想知道重写方法是否总是意味着后期绑定/运行时多态?

  • 问题内容: 我有类似的服务 我想从控制器做类似 getCurrentPosition可以正常工作,并且城市也已确定,但是我不知道如何在控制器中访问城市。 怎么了? 调用getCurrentCity时,它将调用getCurrentPosition来确定gps坐标。该坐标作为参数传递给onSuccess方法,对吗?因此,这与我要在getCurrentCity方法中执行的操作完全相同,但我不知道如何做。

  • 问题陈述: null https://docs.mongodb.com/manual/core/causal-consistency-read-write-concessions/ https://docs.mongodb.com/manual/core/read-election-consistency-recency/#会话