据我了解,TcpListener
会在您拨打Start()
后对连接进行排队。每次拨打AcceptTcpClient
(或BeginAcceptTcpClient
)时,它都会从队列中将一个项目出列。
如果我们通过一次发送1,000个连接加载测试我们的TcpListener
应用程序,那么队列构建的速度远远超过我们清除它的速度,导致(最终)从客户端超时,因为它没有获得响应,因为它的连接仍然在队列中。但是,服务器似乎没有太大的压力,我们的应用程序不会消耗太多CPU时间,并且机器上的其他受监控资源也不会让人痛苦。感觉我们现在还没有足够高效地运行。
我们正在调用BeginAcceptTcpListener
,然后立即切换到ThreadPool
线程以实际完成工作,然后再次调用BeginAcceptTcpClient
。所涉及的工作似乎没有对机器施加任何压力,它基本上只是一个3秒的睡眠,然后是字典查找,然后是对TcpClient
的流的100字节写入。
以下是我们正在使用的TcpListener
代码:
// Thread signal.
private static ManualResetEvent tcpClientConnected = new ManualResetEvent(false);
public void DoBeginAcceptTcpClient(TcpListener listener)
{
// Set the event to nonsignaled state.
tcpClientConnected.Reset();
listener.BeginAcceptTcpClient(
new AsyncCallback(DoAcceptTcpClientCallback),
listener);
// Wait for signal
tcpClientConnected.WaitOne();
}
public void DoAcceptTcpClientCallback(IAsyncResult ar)
{
// Get the listener that handles the client request, and the TcpClient
TcpListener listener = (TcpListener)ar.AsyncState;
TcpClient client = listener.EndAcceptTcpClient(ar);
if (inProduction)
ThreadPool.QueueUserWorkItem(state => HandleTcpRequest(client, serverCertificate)); // With SSL
else
ThreadPool.QueueUserWorkItem(state => HandleTcpRequest(client)); // Without SSL
// Signal the calling thread to continue.
tcpClientConnected.Set();
}
public void Start()
{
currentHandledRequests = 0;
tcpListener = new TcpListener(IPAddress.Any, 10000);
try
{
tcpListener.Start();
while (true)
DoBeginAcceptTcpClient(tcpListener);
}
catch (SocketException)
{
// The TcpListener is shutting down, exit gracefully
CheckBuffer();
return;
}
}
我假设答案与使用Sockets
代替TcpListener
或至少使用TcpListener.AcceptSocket
有关,但我想知道我们该怎么做呢?< / p>
我们的一个想法是将AcceptTcpClient
和Enqueue
立即TcpClient
调用到多个Queue<TcpClient>
对象中的一个。这样,我们可以在不同的线程(每个线程一个队列)上轮询这些队列,而不会在等待其他Dequeue
操作时遇到可能阻塞线程的监视器。然后,每个队列线程可以使用ThreadPool.QueueUserWorkItem
在ThreadPool
线程中完成工作,然后将其队列中的下一个TcpClient
出列。你会推荐这种方法,还是我们的问题,我们正在使用TcpListener
而且没有多少快速出列会解决这个问题?
答案 0 :(得分:3)
我已经编写了一些直接使用套接字的代码,但我缺乏使用1000个客户端执行负载测试的方法。您能否尝试测试此代码与当前解决方案的对比情况?我对结果非常感兴趣,因为我现在正在构建一个需要接受大量连接的服务器。
static WaitCallback handleTcpRequest = new WaitCallback(HandleTcpRequest);
static void Main()
{
var e = new SocketAsyncEventArgs();
e.Completed += new EventHandler<SocketAsyncEventArgs>(e_Completed);
var socket = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Loopback, 8181));
socket.Listen((int)SocketOptionName.MaxConnections);
socket.AcceptAsync(e);
Console.WriteLine("--ready--");
Console.ReadLine();
socket.Close();
}
static void e_Completed(object sender, SocketAsyncEventArgs e)
{
var socket = (Socket)sender;
ThreadPool.QueueUserWorkItem(handleTcpRequest, e.AcceptSocket);
e.AcceptSocket = null;
socket.AcceptAsync(e);
}
static void HandleTcpRequest(object state)
{
var socket = (Socket)state;
Thread.Sleep(100); // do work
socket.Close();
}
答案 1 :(得分:2)
除非我遗漏了某些东西,否则你正在调用异步的BeingAcceptTcpClient,但是你正在调用WaitOne()以等待异步代码完成,这有效地使进程同步。您的代码一次只能接受一个客户端。还是我完全疯了?至少,这看起来像很多上下文切换。
答案 2 :(得分:2)
在其他问题中提到过,但我建议在你的tcpListener.Start()方法中,使用允许你将积压设置为高于你期望的最大连接数的数据的重载在一次:
public void Start()
{
currentHandledRequests = 0;
tcpListener = new TcpListener(IPAddress.Any, 10000);
try
{
tcpListener.Start(1100); // This is the backlog parameter
while (true)
DoBeginAcceptTcpClient(tcpListener);
}
catch (SocketException)
{
// The TcpListener is shutting down, exit gracefully
CheckBuffer();
return;
}
}
基本上,此选项设置允许等待调用Accept的“挂起”TCP连接数。如果您没有足够快地接受连接,并且此待办事项已填满,则TCP连接将自动被拒绝,您甚至无法处理它们。
正如其他人所提到的,另一种可能性是加快处理传入连接的速度。但是,您仍然应该将积压设置为更高的值,即使您可以加快接受时间。
答案 3 :(得分:1)
首先要问自己的是“一次性合理的1000个连接”。我个人认为你不太可能进入那种情况。您很可能在短时间内发生了1000次连接。
我有一个用于测试我的服务器框架的TCP测试程序,它可以在Y批次中总共执行X连接,每个批次之间的间隔为Z ms;我个人发现它比现实世界更“真实”。它是免费的,它可能会有所帮助,你可以从这里得到它:http://www.lenholgate.com/blog/2005/11/windows-tcpip-server-performance.html
正如其他人所说,增加监听积压,更快地处理连接,尽可能使用异步接受......
答案 4 :(得分:1)
只是一个建议:为什么不同步接受客户端(使用AcceptTcpClient
而不是BeginAcceptTcpClient
),然后在新线程上处理客户端?这样,您就不必等待客户端处理,然后才能接受下一个客户端。