蛙蛙推荐:改进同步等待的网络服务端应用
蛙蛙推荐:改进同步等待的网络服务端应用
摘要:服务端收到客户端的请求,如果该请求的处理依赖另一个服务,而且客户端要求同步返回结果,一般得把请求的线程等待一个信号,等请求处理完毕再发送一个信号,给客户端返回结果,但如果这样的同步等待请求并发量很大的话,会很快耗费完线程池线程。
思路:ThreadPool的静态方法RegisterWaitForSingleObject是专门解决这个问题的,它可以等待一个等待句柄,在这个等待句柄收到信号后执行一个回调方法,从而解决了利用一个单独的线程去等待信号的问题,减少了空闲的线程。为了演示这个场景,我们用HttpListener类来创建一个网络服务端,收到客户端的请求后要异步调用一个方法去处理请求,在这个请求处理完或者超时之前,不会给客户端返回Response的,客户端的请求一直在这儿等着 。ThreadPool.RegisterWaitForSingleObject注册了异步方法调用返回的等待句柄,等异步方法执行完后会给这个等待句柄发送信号,从而执行回调,如果在指定的时间内等待句柄没有收到信号,我们就给客户端返回504超时的应答,否则就把处理结果返回给客户端。
当然ThreadPool.RegisterWaitForSingleObject也可以注册一个全局的等待句柄,而不是异步方法调用返回的等待句柄,这样可以更灵活一些,可以给后端服务发送请求,然后等待全局句柄一段儿时间,等收到后端服务的相应的Response后再给这个全局等待句柄发送信号,从而把处理结果返回给客户端。
另外就是由于WaitOrTimerCallback委托不接受按引用传递的object参数,所以我们用一个全局的字典(_dictHandlerResults)去保存每个请求处理的结果信息,在收到请求后获取一个序列号,同时告诉给异步的请求处理方法(asyncHandlerRequest)和线程池注册的等待句柄回调方法(RequestCompleteCallback)。请求处理方法在处理完请求后以传入的序列号为key把处理结果放入全局的请求处理结果字典里,线程池等待句柄回调方法也根据这个序列号去全局字典里取请求处理结果。考虑到排队的请求可能不会是无穷大的,所以全局字典有上限,暂定为10万,然后序列号的取值是从1w到10w,这样全局字典可以不用加任何锁就可以用,而且几乎不会发生通过某个序列号取到的请求处理结果和这个请求不匹配,除非请求的排队大于10万。
服务端的实现代码如下:
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
namespace Server
{
internal class Program
{
private static void Main(string[] args)
{
try
{
MyHttpServer myHttpServer = new MyHttpServer();
myHttpServer.Start();
Console.ReadKey();
myHttpServer.Stop();
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadKey();
}
}
}
internal class MyHttpServer
{
private const string PREFIXES = "http://+:8080/service/";
private const int MAX_PENDING_REQUEST = 100000;
private static int _currentSeq = 0;
private static readonly Random _random = new Random();
private static readonly Dictionary<int, string> _dictHandlerResults = new Dictionary<int, string>(MAX_PENDING_REQUEST);
private readonly HttpListener _listener;
public MyHttpServer()
{
if (!HttpListener.IsSupported)
throw new NotSupportedException("系统不支持HttpListener");
_listener = new HttpListener();
for (int i = 0; i < MAX_PENDING_REQUEST; i++)
{
_dictHandlerResults.Add(i,string.Empty);
}
}
public void Start()
{
_listener.Prefixes.Add(PREFIXES);
_listener.Start();
Console.WriteLine("Listening");
_listener.BeginGetContext(ListenerCallback, _listener);
}
public void Stop()
{
_listener.Stop();
}
private static int getSeq()
{
if (++_currentSeq == MAX_PENDING_REQUEST)
_currentSeq = 0;
return _currentSeq;
}
private static void showThreadCount()
{
int workerThreads, ioThreads, maxWorkerThreads, maxIoThreads;
ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxIoThreads);
ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
Console.WriteLine("workerThreads:{0},ioThreads:{1}", maxWorkerThreads - workerThreads, maxIoThreads - ioThreads);
}
private static void ListenerCallback(IAsyncResult result)
{
try
{
HttpListener listener = (HttpListener)result.AsyncState;
listener.BeginGetContext(ListenerCallback, listener);
showThreadCount();
HttpListenerContext context = listener.EndGetContext(result);
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
int tempKey = getSeq();
Console.WriteLine("current seq:{0}",tempKey);
//这里模仿异步的IO密集型的操作,如果是计算密集型操作就不用异步调用了
//因为这里本身就是线程池线程在处理,可以做一些异步的Remoting调用,Socket调用等。
//或者给后端服务发送请求后,等应答回来后找到匹配的WaitHandler调用其set方法。
asyncHandlerRequestDelegate d = asyncHandlerRequest;
IAsyncResult ar = d.BeginInvoke(request, tempKey, asyncHandlerRequestCallback, null);
ThreadPool.
RegisterWaitForSingleObject(
ar.AsyncWaitHandle,
RequestCompleteCallback,
new object[] { response, tempKey },
5000, true);
}
catch (Exception ex){Console.WriteLine(ex);}
}
private delegate void asyncHandlerRequestDelegate(HttpListenerRequest request, int key);
private static void asyncHandlerRequest(HttpListenerRequest request, int key)
{
int rnd = _random.Next(1, 8);
Thread.Sleep(rnd * 1000); // 这里模仿复杂的处理,真实系统中打死你你也别在线程池线程里sleep哦
_dictHandlerResults[key] = string.Format("this request handler by {0} miniut.", rnd);
}
private static void asyncHandlerRequestCallback(IAsyncResult ar)
{
asyncHandlerRequestDelegate d = (asyncHandlerRequestDelegate)((AsyncResult)ar).AsyncDelegate;
d.EndInvoke(ar);
}
private static void RequestCompleteCallback(object state, bool isTimeout)
{
object[] states = (object[])state;
HttpListenerResponse response = (HttpListenerResponse)states[0];
int key = (int)states[1];
Stream output = null;
try
{
output = response.OutputStream;
string requestHandlerResult;
if (isTimeout)
requestHandlerResult = "504 time out";
else
_dictHandlerResults.TryGetValue(key, out requestHandlerResult);
byte[] buffer = Encoding.UTF8.GetBytes(requestHandlerResult);
response.ContentLength64 = buffer.Length;
output.Write(buffer, 0, buffer.Length);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
try { if (output != null) output.Close(); }catch { }
}
}
}
}
客户端模拟一些HTTP请求,代码如下
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net;
using System.Threading;
namespace Client
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 10000; i++)
{
WebRequest Request = WebRequest.Create("http://localhost:8080/service/abc.aspx");
Request.BeginGetResponse(GetResponseCallback, Request);
Thread.Sleep(1000);
}
Console.ReadKey();
}
static void GetResponseCallback(IAsyncResult ar)
{
try
{
WebRequest request = (WebRequest)ar.AsyncState;
WebResponse response = request.EndGetResponse(ar);
StreamReader streamReader = new StreamReader(response.GetResponseStream(),
Encoding.UTF8);
Console.WriteLine(streamReader.ReadToEnd());
}
catch (Exception ex){ Console.WriteLine(ex);}
}
}
}
小结:不知道这样大量的使用等待句柄会不会有性能问题,还有就是不知道.net的httplistener对象的实现性能如何,有无bug。