我在客户端和服务器上都有一个单独的线程,它正在向/从套接字读取/写入数据。
我正在使用同步TcpClient im(如文档中所示): https://msdn.microsoft.com/cs-cz/library/system.net.sockets.tcpclient%28v=vs.110%29.aspx
连接关闭时.Read()/。Write()抛出异常。这是否意味着当.Write()方法没有将数据正确传递给另一方或我是否需要实现自定义ACK逻辑时?
我阅读了Socket和TcpClient类的文档,但没有一个描述这种情况。
答案 0 :(得分:7)
在流式阻塞互联网套接字上,返回的send()
调用意味着(或者您使用的任何包装器,如Socket
或TcpClient
)的所有内容都是字节放在发送中机器的缓冲区。
成功完成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 delivery,Finding out if a message over tcp was delivered,C 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中的端口。这是输出的样子:
端口51380是一个随机选择的端口,代表上面代码中的客户端TcpClient。有两个数据包,因为此设置在我的路由器上使用NAT。所以,第一个SYN数据包是我的电脑 - >我的外部IP。第二个SYN数据包是我的路由器 - >我的电脑。第一个PSH数据包是第一个serverStream.Write。第二个PSH数据包是第二个serverStream.Write。
有人可能声称客户端在TCP级别使用RST数据包进行确认,但是1)这与使用TcpClient无关,因为这意味着TcpClient正在使用已关闭的连接进行确认,并且2)考虑当在下一段中完全禁用了连接。
如果我注释掉处理流并关闭客户端的行,而是在Thread.Sleep期间断开与无线网络的连接,则控制台会输出相同的输出,我从Wireshark获取此信息:
基本上,.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为止。
注释