我的方案是我有一百个小文本文件,我想加载,解析和存储在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本地主机上运行的客户端和服务器之间的正常套接字设置时间吗?
为了进行比较,我写了一个循环,每个文件加载10个文件x 100行,而该实验只用了1ms。因此,从磁盘(SSD)读取的速度比使用套接字到服务器快1000倍。
我知道在我的场景中要做什么(在每次调用时使用文件读取),但我想知道是否有人可以确认套接字设置时间的这些时间。或者可能有更快的进程间通信机制,本地机器可以与文件读取/解析相媲美。我真的不想相信File.ReadAllLines(filepath)
是分布在数百个命令行客户端调用上的最快方式。
编辑 - 使用明确的IPEndPoint地址避免DNS查找
根据以下评论,我更换了#34; 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评论的实验后:
使用上面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 - 客户端3(两者)与服务器1 ip4,总是延迟1000毫秒,因为客户端在超时和尝试ip4之前尝试使用IPv6,这是一致的。这是本文中的原始代码。
C46xs6 - 客户端3(两者)与服务器2 ip6,在两次重新启动后,在第一次尝试(21ms)和随后的紧密间隔尝试时快速。但是等了一两分钟后,接下来的尝试是3000毫秒,然后在紧密间隔的后续尝试中快速20毫秒。
C4xs4 - 与上述行为相同。首次尝试重新启动后很快,后续尝试间隔很短。但等了一两分钟后,接下来的尝试是3000毫秒,接着是快速(20毫秒)紧密间隔的后续尝试。
C6xS6 - 与上述行为相同。重新启动服务器后快速运行,但一两分钟后,尝试延迟尝试(3000毫秒),然后快速(20毫秒)响应紧密间隔的尝试。
我的实验表明随着时间的推移没有始终如一的快速反当连接空闲时,必须存在某种延迟或超时或休眠行为。我使用netstream.Close; client.Close();
在每次尝试时关闭每个连接。 (是吗?)我不知道在一两分钟的闲置无活动连接时间之后可能导致延迟响应的原因。
知道在一两分钟的空闲收听时间之后可能导致延迟的原因是什么?据说客户端退出系统内存,退出了控制台程序。据说服务器没什么新意,只是在听另一个连接。
答案 0 :(得分:3)
不,1秒建立与localhost的连接不是预期的性能。您的案例中的问题不是DNS查找本身。 localhost的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 localhost)的连接失败的时间。
如果您像这样初始化客户端:
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
构造函数的这种重载需要本地端点,而不是远程端点。在您的示例中,应该只在客户端或服务器上抛出异常(端口已在使用中),因为您尝试绑定到服务器和客户端上的相同IP和端口。如果你想在没有DNS查找的情况下直接连接(无论如何都需要0次本地主机,但在连接到真实服务器时可能很重要),请执行以下操作:
IPEndPoint remoteEP = new IPEndPoint(address, hostport);
client = new TcpClient();
client.Connect(remoteEP);