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()
调用意味着(或您使用的任何包装器,如 Socket
或 TcpClient
)在流媒体上,阻塞互联网套接字是字节放在发送机器的缓冲区中。
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 的答案在所有层面上都是明确正确的,这里的其他答案不止一个是不正确的。
我在客户端和服务器上都有一个单独的线程,它们是 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()
调用意味着(或您使用的任何包装器,如 Socket
或 TcpClient
)在流媒体上,阻塞互联网套接字是字节放在发送机器的缓冲区中。
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 的答案在所有层面上都是明确正确的,这里的其他答案不止一个是不正确的。