我使用TcpListener编写了一个服务器,它应该处理数千个并发连接。
因为我知道大多数时候大多数连接都是空闲的(偶尔有乒乓以确保对方仍在那里)异步编程似乎是解决方案。
然而,在前几百个客户表现迅速恶化之后。事实上,我几乎无法达到1000个并发连接。
CPU未达到最大值(平均值为~4%),RAM使用率<100MB,并且网络流量不会很多。
当我在Visual Studio中暂停服务器并查看“任务”窗口时,有无数(数百个)任务状态为“已调度”且只有少数(少于30个)“正在运行/活动”任务。< / p>
我尝试使用Visual Studio以及dotTrace Peformacne进行配置文件,但我找不到任何错误。没有锁争用,没有使用大量CPU的“热路径”。 似乎应用程序整体上放慢了速度。
我有一个简单的while(true)
,里面有这个:
var client = await tcpListener.AcceptTcpClientAsync().ConfigureAwait(false);
Task.Run(() => OnClient(client));
为了处理连接,我提出了一些方法来封装连接的不同阶段。
例如,在OnClient
上面await HandleLogin(...)
,然后它进入while(client.IsConnected)
循环,只执行await stream.ReadBuffer(1)
。 stream
只是从TcpClient.GetStream获得的普通NetworkStream,而ReadBuffer是这样实现的自定义方法:
public static async Task<byte[]> ReadBuffer(this Stream stream, int length)
{
byte[] buffer = new byte[length];
int read = 0;
while (read < length)
{
int remaining = length - read;
int readNow = await stream.ReadAsync(buffer, read, remaining).ConfigureAwait(false);
read += readNow;
if (readNow <= 0)
throw new SocketException((int)SocketError.ConnectionReset);
}
return buffer;
}
我在await
任何地方使用.ConfigureAwait(false),因为我需要任何类型的同步上下文,我不想支付retreiving /创建同步的性能开销无处不在。
我注意到的一件事是,当我从我的测试工具中生成50个连接然后随机关闭它(因此它所做的所有连接应该在服务器上接收到ConnectionReset SocketException)时,服务器需要很长时间才能做出反应通常都会完全挂起,直到新的连接到来。
难道某种延续想要以某种方式同步并在某个特定线程上运行吗? 有可能(在正确的时刻断开连接)使服务器应用程序几乎无法使用,只需20个连接。
我做错了什么?
如果它是一些错误(我认为它是),我将如何找到它?
我将问题缩小到只是坐在NetworkStream.ReadAsync(...)
的许多任务,即使他们应该立即收到SocketException(ConnectionReset)。
我尝试在远程计算机和本地启动我的测试工具(只是使用TcpClient),我得到了相同的结果。
我的OnClient定义为async Task OnClient(TcpClient client)
。在其中,它等待连接的不同阶段:身份验证,一些设置协商,然后进入等待消息的循环。
我使用Task.Run
因为我不想等到一个客户端完成,但我希望尽快接受所有客户端,为每个客户端生成一个新任务。然而,我不确定是否不能/不应该只在没有Task.Run的情况下编写OnClient(client)
而且还没有等待OnClient(会导致提示不会消失但是这就是我想要的我想,我不想等到客户端完成。)
连接在验证后进入的最后阶段,设置协商是服务器等待来自客户端的消息的循环。
然而,在此之前,服务器还执行另一个Task.Run()
(与while(已连接)并等待Task.Delay ...)发送ping数据包和一些其他“管理”事物。
通过使用Nito AsyncEx库中的锁定机制来同步对NetworkStream的所有写入,以确保没有数据包以某种方式交错。
如果在任何地方发生任何异常(在读或写时),我总是在TcpClient上调用.Close以确保所有其他未决的不完整读写都会抛出异常。
答案 0 :(得分:3)
我将问题缩小到只是坐在NetworkStream.ReadAsync(...)的许多任务,即使他们应该立即收到SocketException(ConnectionReset)。
这是一个不正确的假设。 You have to write to the socket to detect dropped connections.
这是许多 TCP / IP编程的陷阱之一,这就是为什么我建议人们尽可能使用SignalR。
从您的代码/说明中跳出的其他陷阱:
OnClient
。所以它仍在进行线程跳转。这可能是理想的,也可能不是。 (假设async
是while(client.IsConnected)
方法;如果它使用sync-over-async,那么它肯定不是一个好的模式。)IsConnected
是一种常见的错误模式。您应该同时运行读循环和写队列处理器。特别是,IsConnected
绝对没有意义 - 它实际上只意味着套接字已连接在过去的某个点。 不意味着仍然连接。如果代码有#include <stdio.h>
#include <math.h>
int inputData(int [][500]); //inputs the data and returns the rows by
columns.
int main(void){
int n = 0;
int data[n][n];
printf("Grid size: %dx%d", inputData(data),inputData(data));
return 0;
}
int inputData(int data[][500]){
int i;
int j;
for(i = 1; i <= 500; i++){
for(j = 0; j <= 500; j++){
scanf("%d", &data[i][j]);
}
}
return j;
}
,则会出现错误。