我在单声道中使用 HttpListener 处理多线程 http 请求时遇到了问题。看起来 httpListener.BeginGetContext(_handleRequestCallback, null) 不能“同时”处理超过 2 个请求。我写了一个小测试:
using System;
using System.Net;
using NUnit.Framework;
using System.IO;
using log4net;
using System.Threading;
namespace Other
{
public class ListenerTest
{
private ILog _log;
private HttpListener _httpListener;
private const int PORT = 8080;
private const int REQUESTS = 20;
private AsyncCallback _handleRequestCallback;
[TestFixtureSetUp]
public void SetUp()
{
ServicePointManager.DefaultConnectionLimit = REQUESTS;
ThreadPool.SetMinThreads(REQUESTS, REQUESTS);
ThreadPool.SetMaxThreads(REQUESTS, REQUESTS);
_httpListener = new HttpListener();
_httpListener.Prefixes.Add(String.Format("http://*:{0}/", PORT));
_httpListener.Start();
_handleRequestCallback = new AsyncCallback(processAsync);
_log = LogManager.GetLogger(typeof(ListenerTest));
}
[Test]
public void SyncTest()
{
Thread serverThread = new Thread(() => processSyncThread());
serverThread.Start();
for(int i = 0; i < REQUESTS; ++i)
{
makeRequest(i.ToString(), HttpStatusCode.OK, 1000);
}
}
[Test]
public void AsyncTest()
{
int serverThreads = 5;
Thread serverThread = new Thread(() => processAsyncThread(serverThreads));
serverThread.Start();
for(int i = 0; i < REQUESTS; ++i)
{
int position = i;
Thread thread = new Thread(() => makeRequest(position.ToString(), HttpStatusCode.OK, 5000));
thread.Start();
}
Thread.Sleep(5000);
}
private void processAsyncThread(int threads)
{
for(int i = 0; i < threads; ++i)
_httpListener.BeginGetContext(_handleRequestCallback, null);
}
private void processAsync(IAsyncResult asyncResult)
{
Thread thread = new Thread(() => processSync(_httpListener.EndGetContext(asyncResult)));
thread.Start();
// processSync(_httpListener.EndGetContext(asyncResult));
_httpListener.BeginGetContext(_handleRequestCallback, null);
}
private void processSyncThread()
{
while(true)
{
var context = _httpListener.GetContext();
processSync(context);
}
}
private void processSync(HttpListenerContext context)
{
string request = context.Request.RawUrl.TrimStart('/');
_log.InfoFormat("Received request:{0}", request);
Thread.Sleep(500);
using(StreamWriter writer = new StreamWriter(context.Response.OutputStream))
writer.Write(request);
_log.InfoFormat("Sent response for:{0}", request);
}
private string makeRequest(string request, HttpStatusCode expectedCode, int timeout)
{
_log.InfoFormat("Sending {0} request.", request);
WebRequest webRequest = WebRequest.Create(String.Format("http://localhost:{0}/{1}", PORT, request));
webRequest.Timeout = timeout;
try
{
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
if(expectedCode != response.StatusCode)
throw new IOException(String.Format(
"Returned unexpected status code: {0} for request: {1} when expected: {2}",
response.StatusCode, request, expectedCode));
StreamReader reader = new StreamReader(response.GetResponseStream());
string responseContent = reader.ReadToEnd();
response.Close();
_log.InfoFormat("Received response:{0}", responseContent);
return responseContent;
}
catch(WebException exception)
{
if (exception.Response is HttpWebResponse
&& expectedCode == ((HttpWebResponse)exception.Response).StatusCode)
return "";
else if (exception.Status == WebExceptionStatus.Timeout
&& expectedCode == HttpStatusCode.RequestTimeout)
return "";
throw;
}
}
}
}
(这需要 Nunit 和 log4net 才能运行,但它们可能很容易被删除)使用 SyncTest 可以按预期工作。在 AsyncTest 中,我看到 httpListener 并行调用 _handleRequestCallback 的次数不超过 2 次。处理任何先前请求时,第三个请求开始处理:
01:24:23,788 [(null)-27] INFO Other.ListenerTest {(null)} - Sending 10 request.
01:24:23,788 [(null)-30] INFO Other.ListenerTest {(null)} - Sending 13 request.
...
01:24:23,788 [(null)-23] INFO Other.ListenerTest {(null)} - Sending 8 request.
01:24:23,793 [(null)-36] INFO Other.ListenerTest {(null)} - Sending 19 request.
01:24:23,885 [(null)-40] INFO Other.ListenerTest {(null)} - Received request:12
01:24:23,885 [(null)-39] INFO Other.ListenerTest {(null)} - Received request:5
01:24:24,400 [(null)-39] INFO Other.ListenerTest {(null)} - Sent response for:5
01:24:24,400 [(null)-40] INFO Other.ListenerTest {(null)} - Sent response for:12
01:24:24,413 [(null)-20] INFO Other.ListenerTest {(null)} - Received response:5
01:24:24,414 [(null)-29] INFO Other.ListenerTest {(null)} - Received response:12
01:24:24,414 [(null)-54] INFO Other.ListenerTest {(null)} - Received request:0
01:24:24,414 [(null)-53] INFO Other.ListenerTest {(null)} - Received request:15
01:24:24,915 [(null)-54] INFO Other.ListenerTest {(null)} - Sent response for:0
01:24:24,915 [(null)-53] INFO Other.ListenerTest {(null)} - Sent response for:15
01:24:24,917 [(null)-15] INFO Other.ListenerTest {(null)} - Received response:0
01:24:24,918 [(null)-32] INFO Other.ListenerTest {(null)} - Received response:15
01:24:24,919 [(null)-65] INFO Other.ListenerTest {(null)} - Received request:18
01:24:24,919 [(null)-66] INFO Other.ListenerTest {(null)} - Received request:3
01:24:25,419 [(null)-65] INFO Other.ListenerTest {(null)} - Sent response for:18
01:24:25,420 [(null)-66] INFO Other.ListenerTest {(null)} - Sent response for:3
01:24:25,458 [(null)-18] INFO Other.ListenerTest {(null)} - Received response:3
01:24:25,458 [(null)-77] INFO Other.ListenerTest {(null)} - Received request:10
01:24:25,558 [(null)-35] INFO Other.ListenerTest {(null)} - Received response:18
01:24:25,558 [(null)-80] INFO Other.ListenerTest {(null)} - Received request:6
01:24:25,959 [(null)-77] INFO Other.ListenerTest {(null)} - Sent response for:10
01:24:25,998 [(null)-27] INFO Other.ListenerTest {(null)} - Received response:10
01:24:25,998 [(null)-81] INFO Other.ListenerTest {(null)} - Received request:4
01:24:26,059 [(null)-80] INFO Other.ListenerTest {(null)} - Sent response for:6
01:24:26,059 [(null)-21] INFO Other.ListenerTest {(null)} - Received response:6
01:24:26,060 [(null)-82] INFO Other.ListenerTest {(null)} - Received request:2
我在 MS .Net 上运行相同的代码(但没有 log4net),看看我的期望:
01:38:39:754 Sending 9 request.
01:38:39:738 Sending 5 request.
01:38:39:738 Sending 3 request.
01:38:39:738 Sending 1 request.
01:38:40:660 Received request:18
01:38:40:660 Received request:1
01:38:40:660 Received request:0
01:38:40:660 Received request:2
01:38:40:660 Received request:3
01:38:40:660 Received request:4
01:38:40:676 Received request:5
01:38:40:676 Received request:6
01:38:40:676 Received request:7
01:38:40:676 Received request:8
01:38:40:692 Received request:9
01:38:40:692 Received request:10
01:38:40:692 Received request:11
01:38:40:692 Received request:12
01:38:40:707 Received request:13
01:38:40:707 Received request:14
01:38:40:707 Received request:15
01:38:40:707 Received request:16
01:38:40:723 Received request:17
01:38:40:723 Received request:19
01:38:41:160 Sent response for:4
01:38:41:160 Received response:18
01:38:41:160 Received response:1
01:38:41:160 Sent response for:3
01:38:41:160 Sent response for:2
01:38:41:176 Received response:8
01:38:41:160 Sent response for:1
01:38:41:160 Sent response for:0
01:38:41:160 Sent response for:18
01:38:41:160 Received response:2
01:38:41:160 Received response:3
01:38:41:160 Received response:4
01:38:41:176 Sent response for:8
01:38:41:176 Sent response for:7
01:38:41:176 Sent response for:6
01:38:41:176 Sent response for:5
01:38:41:176 Received response:7
01:38:41:176 Received response:6
01:38:41:176 Received response:5
01:38:41:192 Received response:9
01:38:41:192 Sent response for:9
01:38:41:192 Received response:10
01:38:41:192 Sent response for:10
01:38:41:192 Received response:11
01:38:41:192 Sent response for:11
01:38:41:192 Received response:12
01:38:41:192 Sent response for:12
01:38:41:160 Received response:0
01:38:41:207 Received response:16
01:38:41:207 Sent response for:16
01:38:41:223 Sent response for:19
01:38:41:223 Received response:19
01:38:41:223 Sent response for:13
01:38:41:223 Sent response for:14
01:38:41:223 Sent response for:15
01:38:41:223 Sent response for:17
01:38:41:238 Received response:13
01:38:41:238 Received response:14
01:38:41:238 Received response:15
01:38:41:223 Received response:17
我认为这个问题可能与这个问题有关,但建议的解决方法不起作用。那么有人对解决方法有想法吗?