【问题标题】:Are TCP setup times this slow (1 second), typically?TCP 建立时间通常这么慢(1 秒)吗?
【发布时间】:2018-03-17 20:15:48
【问题描述】:

我的情况是我有一百个小文本文件,我想加载、解析和存储在一个 DLL 中。 DLL 的客户端是临时的(命令行程序),我不希望在每次命令行调用时都重新加载数据。

所以,我想我会编写一个 Windows 服务器来存储数据并让客户端使用 TCP 查询服务器。但是,TCP 性能真的很慢。我使用Stopwatch 编写了以下代码来测量套接字设置时间。

    // time the TCP interaction to see where the time goes
    var stopwatch = new Stopwatch();
    stopwatch.Start();

    // create and connect socket to remote host
    client = new TcpClient (hostname, hostport); // auto-connects to server
    Console.WriteLine ("Connected to {0}",hostname);

    // get a stream handle from the connected client
    netstream = client.GetStream();

    // send the command to the far end
    netstream.Write(sendbuf, 0, sendbuf.Length);
    Console.WriteLine ("Sent command to far end: '{0}'",cmd);
    stopwatch.Stop();
    sendTime = stopwatch.ElapsedMilliseconds;

令我惊讶的是,那一小段代码需要 1,037 毫秒(1 秒)来执行。我预计时间会更短。这是在现代 Windows 10 localhost 上运行的客户端和服务器之间的正常套接字设置时间吗?

为了比较,我编写了一个循环,每个循环加载 10 个文件 x 100 行,而这个实验只用了 1 毫秒。因此,从磁盘(SSD)读取数据比使用套接字连接服务器快 1000 倍。

我知道在我的场景中该做什么(在每次调用时使用文件读取),但我想知道是否有人可以确认这些类型的套接字设置时间。或者,本地机器可能有更快的进程间通信机制,可以与文件读取/解析相媲美。我真的不想相信File.ReadAllLines(filepath) 是分布在数百个命令行客户端调用中的最快方式。

编辑 - 使用显式 IPEndPoint 地址避免 DNS 查找

按照下面的 cmets,我将“localhost”替换为 IPEndpoint 方法来建立连接。更改将 1037 毫秒减少到大约 20 毫秒,但是 (1) TcpClient 不会自动连接,以及 (2) 文本发送无法到达服务器。因此,原始方法和 IPEndPoint 方法之间肯定有一些不同。

// new IPEndPoint method
// fast at 20ms, but the server never sees the sent text
string serverIP = "127.0.0.1";
IPAddress address = IPAddress.Parse (serverIP);
IPEndPoint remoteEP = new IPEndPoint(address, hostport);
client = new TcpClient(remoteEP);
client.Connect (remoteEP);  // new; required w IPEndPoint method

// send text command to the far end
netstream = client.GetStream();
netstream.Write(sendbuf, 0, sendbuf.Length);
Console.WriteLine ("Sent command to far end: '{0}'",cmd);
stopwatch.Stop();
sendTime = stopwatch.ElapsedMilliseconds;
Console.WriteLine ($"Milliseconds for sending by TCP:  '{sendTime}'");

// unfortunately, the server never sees the sent text now

我不知道为什么使用 IPEndPoint 作为 TcpClient 的输入参数需要显式连接,而 TcpClient 之前会自动连接。而且我不知道为什么netstream.Write 现在也失败了。网络上的示例总是使用socket.Connectsocket.Send 与IPEndPoints。

编辑 #2 - 将 IPEndPoint 与套接字一起使用,而不是流

// use sockets, not streams
// This code takes 3 seconds to send text to the server
// But at least this code works. The original code was faster at 1 second.       
string serverIP = "127.0.0.1";
IPAddress address = IPAddress.Parse(serverIP);
IPEndPoint remoteEP = new IPEndPoint(address, hostport);
socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream,
                 ProtocolType.Tcp);
socket.Connect (remoteEP);
socket.Send (sendbuf);

编辑 #3 - 基于 Evk cmets 的实验之后:

使用上面Evk提供的信息,我做了如下几个实验。使用了三个客户端和两个服务器。

Client 1: DNS returns only IPv4 using new TcpClient().
Client 2: DNS returns only Ipv6 using new TcpClient(AddressFamily.InternetworkV6)
Client 3: DNS returns IPv4 and IPv6 using new TcpClient(“localhost”,port)
Server 1: IPv4 new TcpListener(IPAddress.Loopback, port)
Server 2: IPv6 new TcpListener(IPAddress.IPv6Loopback, port)

从最差到最好,6 个可能的配对返回以下结果:

c4xs6 - 客户端 1 ip4 与服务器 2 ip6 - 连接被主动拒绝。

c6xs4 - 客户端 2 ip6 与服务器 1 ip4 - 连接被主动拒绝。

c46xs4 - 带有服务器 1 ip4 的客户端 3(两者)总是延迟 1000 毫秒,因为客户端在超时之前尝试使用 IPv6 并尝试使用 ip4,这始终有效。这是这篇文章的原始代码。

C46xs6 - 带有服务器 2 ip6 的客户端 3(两者)在重新启动两者后,在第一次尝试(21 毫秒)和随后的密集尝试中很快。但在等待一三分钟后,下一次尝试是 3000 毫秒,然后在间隔很近的后续尝试中快速 20 毫秒。

C4xs4 – 与上述相同的行为。重新启动后的第一次尝试很快,随后的密集尝试也是如此。但在等待一两分钟后,下一次尝试是 3000 毫秒,然后是快速(20 毫秒)间隔很近的后续尝试。

C6xS6 – 与上述相同的行为。重新启动服务器后速度很快,但在一两分钟后,延迟尝试(3000 毫秒),然后快速(20 毫秒)响应紧密间隔的尝试。

我的实验表明,随着时间的推移并没有始终如一的快速响应。当连接空闲时,必须有某种延迟或超时或睡眠行为。我每次尝试都使用netstream.Close; client.Close(); 关闭每个连接。 (对吗?)我不知道是什么原因导致在一两分钟的空闲 no-active-connection 时间后响应延迟。

知道一两分钟空闲收听时间后可能导致延迟的原因是什么吗?客户端应该是系统内存不足,退出了控制台程序。服务器应该没有做任何新的事情,只是在监听另一个连接。

【问题讨论】:

  • 要排除 DNS 请求,你能用client = new TcpClient(new IPEndPoint( new IPAddress(...), hostport)试试你的代码
  • 我很确定这是因为 DNS 响应缓慢。尝试测量 第二个 连接,就在第一个解析主机名并且本地缓存可用于下一个连接之后。
  • 命名管道可能是仅用于本地 IPC 的最快方式。但是,是的,连接到本地运行的服务器似乎需要很长时间。
  • 感谢您的帮助。我尝试了new IPEndPoint()机制如上所述建立连接,时间最终从1037ms下降到20ms。但是,当我使用 IPE 方法时,客户端没有自动连接,并且在“未连接”套接字上尝试GetStream 时收到错误消息。在创建新的 TcpClient 对象后,我必须调用 client.Connect。也许这也起到了作用,因为服务器从未收到传出的发送文本。我正在寻找可以遵循的示例。
  • 使用IP地址时,使用IP路由和ARP路由到服务器。当您启用 DNS 并使用 HOST NAME 时,路由是通过从 DNS 服务器请求路由来确定的,这将花费更长的时间,因为您首先必须将消息发送到服务器并在发送消息之前获得响应。

标签: c# sockets tcp tcpclient


【解决方案1】:

不,与 localhost 建立连接需要 1 秒的时间,这不是预期的性能。您的问题不是 DNS 查找本身。本地主机的 DNS 查找不需要时间(可能需要几毫秒),当然也不会花费 1 秒。下面我假设你的 TCP 服务器只绑定到 IpV4 环回 (127.0.0.1),例如这样:

var server = new TcpListener(IPAddress.Loopback, port);

当你像这样初始化客户端时:

new TcpClient("localhost", port)

它查询 DNS(不需要时间)并且 DNS 返回 2 个 IP 地址:::1(IpV6 localhost)和127.0.0.1(IpV4 localhost)。它不知道是否需要使用 IpV4 或 IpV6 地址。所以它会尝试两者(首选 IpV6)。您观察到的 1 秒延迟是它需要意识到与 ::1(IpV6 本地主机)的连接失败的时间。

如果你这样初始化客户端:

var client = new TcpClient();

同理:

// InterNetwork means IpV4
var client = new TcpClient(AddressFamily.InterNetwork);

这两个版本都将客户端绑定到本地 IpV4 套接字。这意味着当你以后这样做时:

client.Connect("localhost", port);

客户端不需要尝试IpV6 localhost地址,因为本地套接字是IpV4。这两个版本都将消除您观察到的 1 秒延迟。消除延迟的另一个选择是将您的 服务器 绑定到 ipv6 环回(到 IPAddress.IPv6Loopback)。

请注意:

IPEndPoint remoteEP = new IPEndPoint(address, hostport);
client = new TcpClient(remoteEP);

错了。 TcpClient 构造函数的重载需要 local 端点,而不是远程端点。在您的示例中,应该只在客户端或服务器上抛出异常(端口已在使用中),因为您试图在服务器和客户端上绑定到相同的 ip 和端口。如果您想直接连接而无需 DNS 查找(无论如何,这需要 0 时间的 localhost,但在连接到真实服务器时可能很重要),请执行以下操作:

IPEndPoint remoteEP = new IPEndPoint(address, hostport);
client = new TcpClient();
client.Connect(remoteEP);

【讨论】:

  • 是的,这是微软的一个荒谬的设计选择,在 TcpClient 构造函数参数中存在这种不一致,如果是字符串,它们表示要连接的端点,但如果预先解析它会绑定。糟糕,糟糕的设计。
  • 非常感谢您的冗长而翔实的回答。不用担心 localEP 和 remoteEP 名称,我正在切换它们以查看看起来最好的名称。有趣的是,您提到了 ipv6 与 ipv4。我在其中一个网页中读到了一行文字,首先要尝试 ip6,如果超时后仍然无效,则会尝试 ip4。在我的本地主机文件中,我定义了 127.0.0.1 和 ::1。这周我会回去尝试你的一些编码建议并在这里报告。再次感谢您——您一定花了很长时间才写完! -凯文
  • @Kevin 的问题不是名字(remoteEPlocalEP),而是它确实是远程端点,所以你不能将它传递给TcpClient 的构造函数。在您的案例中没有 ip\port 可以传递给该构造函数,因此您不应该使用它。至于 localhost 文件 - 在那里定义什么并不重要。重要的是您绑定的 ip server (TcpListener)。如果您将其绑定到“::1” - 您的任何形式的代码都将立即运行。如果您绑定到“127.0.0.1”(就像您现在所做的那样) - 有些方法会延迟,有些则不会(如答案中所述)。
  • @Kevin 如此正确的方法(如果您的服务器绑定到“127.0.0.1”)是var client = new TcpClient(); client.Connect("localhost", port);var client = new TcpClient(); client.Connect(new IPEndPoint(IPAddress.Loopback, port)); 两者都不会延迟(您测量的整个代码将在几毫秒内完成)。
  • 您正确地推测服务器(当前)正在使用 ip4 进行侦听。当我读到文档中的一个小句子(数千个)时,我想知道是否尝试使用 ip6。但是我没有时间进行实验,因为我找不到使用 ip6 的示例。我会在这周完成它。感谢您在之前的评论中明确说明什么会起作用(或不起作用)。既然我在网上找不到宝贵的东西,你到底在哪里学到了这么多关于这方面的知识。我也想了解它... :-)
猜你喜欢
  • 1970-01-01
  • 2013-07-22
  • 2015-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-27
  • 1970-01-01
相关资源
最近更新 更多