蛙蛙推荐:改进同步等待的网络服务端应用

-

蛙蛙推荐:改进同步等待的网络服务端应用

摘要:服务端收到客户端的请求,如果该请求的处理依赖另一个服务,而且客户端要求同步返回结果,一般得把请求的线程等待一个信号,等请求处理完毕再发送一个信号,给客户端返回结果,但如果这样的同步等待请求并发量很大的话,会很快耗费完线程池线程。

思路:ThreadPool的静态方法RegisterWaitForSingleObject是专门解决这个问题的,它可以等待一个等待句柄,在这个等待句柄收到信号后执行一个回调方法,从而解决了利用一个单独的线程去等待信号的问题,减少了空闲的线程。为了演示这个场景,我们用HttpListener类来创建一个网络服务端,收到客户端的请求后要异步调用一个方法去处理请求,在这个请求处理完或者超时之前,不会给客户端返回Response的,客户端的请求一直在这儿等着 。ThreadPool.RegisterWaitForSingleObject注册了异步方法调用返回的等待句柄,等异步方法执行完后会给这个等待句柄发送信号,从而执行回调,如果在指定的时间内等待句柄没有收到信号,我们就给客户端返回504超时的应答,否则就把处理结果返回给客户端。
当然ThreadPool.RegisterWaitForSingleObject也可以注册一个全局的等待句柄,而不是异步方法调用返回的等待句柄,这样可以更灵活一些,可以给后端服务发送请求,然后等待全局句柄一段儿时间,等收到后端服务的相应的Response后再给这个全局等待句柄发送信号,从而把处理结果返回给客户端。
另外就是由于WaitOrTimerCallback委托不接受按引用传递的object参数,所以我们用一个全局的字典(_dictHandlerResults)去保存每个请求处理的结果信息,在收到请求后获取一个序列号,同时告诉给异步的请求处理方法(asyncHandlerRequest)和线程池注册的等待句柄回调方法(RequestCompleteCallback)。请求处理方法在处理完请求后以传入的序列号为key把处理结果放入全局的请求处理结果字典里,线程池等待句柄回调方法也根据这个序列号去全局字典里取请求处理结果。考虑到排队的请求可能不会是无穷大的,所以全局字典有上限,暂定为10万,然后序列号的取值是从1w到10w,这样全局字典可以不用加任何锁就可以用,而且几乎不会发生通过某个序列号取到的请求处理结果和这个请求不匹配,除非请求的排队大于10万。

服务端的实现代码如下:

using  System;
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;
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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值