我在客户端和写入C#的服务器之间一次性发送大量数据。当我在本地计算机上运行客户端和服务器时它工作正常但是当我将服务器放在互联网上的远程计算机上时,它似乎丢弃了数据。
我使用socket.Send()方法发送20000个字符串,并使用一个执行socket.Receive()的循环接收它们。每个字符串由唯一字符分隔,我用它来计算收到的数字(如果你愿意,这是协议)。该协议已经过验证,即使使用碎片消息,每个字符串也会被正确计算。在我的本地机器上,我得到全部20000,通过互联网我得到17000-20000之间的任何东西。远程计算机的连接速度似乎更慢。为了增加混乱,打开Wireshark似乎减少了丢弃的消息数量。
首先,是什么原因造成的?这是TCP / IP问题还是我的代码有问题?
其次,我怎样才能绕过这个?接收所有20000个字符串至关重要。
套接字接收代码:
private static readonly Encoding encoding = new ASCIIEncoding();
///...
while (socket.Connected)
{
byte[] recvBuffer = new byte[1024];
int bytesRead = 0;
try
{
bytesRead = socket.Receive(recvBuffer);
}
catch (SocketException e)
{
if (! socket.Connected)
{
return;
}
}
string input = encoding.GetString(recvBuffer, 0, bytesRead);
CountStringsIn(input);
}
套接字发送代码:
private static readonly Encoding encoding = new ASCIIEncoding();
//...
socket.Send(encoding.GetBytes(string));
答案 0 :(得分:4)
你的代码开头有一个问题,如果你计算完成Receive
的来电次数:你似乎假设你会看到尽可能多的Receive
在您拨打Send
电话时,通话结束。
TCP是基于流的协议 - 您不应该担心单个数据包或读取;你应该关心阅读数据,期望有时你不会在一个数据包中得到一个完整的消息,有时你可能在一次读取中得到多个消息。 (一次读取也可能与一个数据包不对应。)
您应该在发送之前为每个方法添加长度前缀,或者在消息之间分隔。
答案 1 :(得分:3)
这绝对不是TCP的错。 TCP保证有序,准确一次交付。
哪些字符串“丢失”?我打赌它是最后一个;尝试从发送端刷新。
此外,你的“协议”在这里(我正在考虑你正在发明的应用层协议)是缺乏的:你应该考虑发送#个对象和/或它们的长度,以便接收者知道他什么时候实际完成收到他们。
答案 2 :(得分:3)
如果您丢弃数据包,您将看到传输延迟,因为它必须重新传输丢弃的数据包。这可能是非常重要的,尽管有一个称为选择性确认的TCP选项,如果双方支持,它将仅触发那些被丢弃的数据包的重发,而不是每个数据包的重新发送。在代码中无法控制它。默认情况下,您始终可以假设每个数据包都是为TCP发送的,并且如果有某些原因它无法按顺序传送每个数据包,则连接将会丢失,无论是超时还是连接的一端发送RST包。
你所看到的很可能是Nagle算法的结果。它所做的不是在发布时发送每一位数据,而是发送一个字节,然后等待来自另一方的ack。在等待时,它会聚合您要发送的所有其他数据,并将其合并为一个大数据包然后发送。由于TCP的最大大小为65k,它可以将相当多的数据合并到一个数据包中,尽管这种情况发生的可能性极小,特别是因为winsock的默认缓冲区大小约为10k左右(我忘记了确切的数量)。此外,如果接收器的最大窗口大小小于65k,它将仅发送与接收器的最后一个广告窗口大小一样多的数量。窗口大小也影响Nagle的算法,因为它在发送之前可以聚合多少数据,因为它不能发送超过窗口大小的数据。
您之所以看到这一点,是因为在互联网上,与您的网络不同,第一个ack需要更多时间才能返回,因此Naggle算法会将更多数据聚合到一个数据包中。在本地,返回实际上是瞬时的,所以它能够尽快发送数据到将其发布到套接字。您可以使用SetSockOpt(winsock)或Socket.SetSocketOption(.Net)在客户端禁用Naggle算法,但我强烈建议您不要在套接字上禁用Naggling,除非您100%确定您知道自己在做什么。这是有充分理由的。
答案 3 :(得分:1)
每个字符串有多长?如果它们不是1024字节,它们将被远程TCP / IP堆栈合并为一个大流,您可以在接收呼叫中读取大块数据。
例如,使用三个发送调用发送“A”,“B”和“C”很可能以“ABC”的形式进入远程客户端(因为远程堆栈或您自己的堆栈将缓冲字节直到他们被阅读)。如果您需要在不与其他字符串合并的情况下使用每个字符串,请查看添加带有标识符的“协议”以显示每个字符串的开头和结尾,或者configure the socket以避免缓冲和组合数据包。