【发布时间】: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.Connect 和socket.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 服务器请求路由来确定的,这将花费更长的时间,因为您首先必须将消息发送到服务器并在发送消息之前获得响应。