简述用于windowsclient的一个异步http模块的实现

1.须要实现的feature

1.1 非常easy地发起异步http请求,然后回调。

1.2 可以管理http并发数。

1.3 可以支持http超时:不依赖于curl中实现的连接超时及其他超时。

1.4 请求能够取消。





2.參与者和简要分析:

Manager:接收http请求,调用curl。

Request:封装http请求。

Response:封装http回应。





线程模型:

这里实现异步通常会开线程,假定有一个UI(主)线程,可能有这些模式:

Manager在UI线程中管理若干个工作线程,curl_easy接口。

Manager在自己新起的http线程中管理若干个工作线程,curl_easy接口。

Manager在自己新起的http线程中调用curl_multi接口。





Manager在UI线程中调用curl_multi接口不合适,由于须要占用UI线程时间去select。

开多个work线程浪费资源,线程管理难度大,线程并发度的降低不会造成性能瓶颈,

由于主要耗时在网络IO上。





所以选定模型是:开一个http线程,在上面调用curl_multi接口。





解决回调问题:

这里会遇到两个问题,一个是回调的线程问题:在http中检測到IO完毕,

假设直接在http中进行回调,会使得使用者要考虑多线程使用问题,回调中可能的崩溃,

耗时操作会影响异步http模块性能。还有一个是回调对象的生命周期问题:假设回调

到对象的成员函数,在回调时有可能该对象已经析构。





考虑到这些,使用了chromium的线程模型:chromium中的base模块提供了几个抽象

层次的线程调度接口。当中一个层次是:知道对方线程的标识符,即能够向相应的线程

派发任务。





于是设计要求调用者是在某个被管理的线程上,那么在发起请求后,在Manager的SendRequest

方法中能够检測到主调线程的MessageLoopProxy,将该代理作为请求的一部分放到请求

队列中。在请求完毕后,将response和SendRequest中的callback对象绑定到一起作为

一个任务由MessageLoopProxy派发,这样就回到调用者所在线程上了。





在此同一时候,对象的生命周期问题也解决,base中有弱指针。





回调接口被设计为void XXX::CallBack(WeakPtr<XXX> ptr, scoped_refptr<Response> ptr);

在绑定到当前对象的弱引用时得到一个签名为 void (scoped_refptr<Response>) 的Runnable Object。

这个签名的对象才干够交给Manager。

在IO完毕时,再将这个Runnable Object的參数绑定到相应的Response上,就得到签名为 void (void)的

Runnable Object,同一时候也是线程池能派发的对象。





在这里引入chromium的线程池,加上了调用者的约束,解决回调问题。





3.參与者设计

Request设计:

包括一堆參数,比方url,HTTP_VERB,HTTP头操作,HTTP的BODY操作,超时设置。

为了简化使用,能够提供一个MakeGetRequest的函数,用于生成一个不须要那么多复杂设置的Request。





Reponse设计:

包括自定义的错误码,curl的http错误码,http状态码,response body buffer,

response header,原来的url,甚至一个void* user的不透明指针。





Manager设计:

作为单件存在,拥有初始化和反初始化接口。这样,Manager得和所在模块的生命周期

绑定在一起,在合适的地方初始化和反初始化。





在初始化时内部创建http工作线程,整个模块处于就绪状态。





请求队列是少不了的,在訪问时注意相互排斥。

为每一个请求分配一个handle,交给调用者。

维护一个当前工作队列,即:加到CURLM中的全部请求。





能够预见http就是不断循环地处理一些事件。





怎样及时响应加入的请求。

怎样及时退出。

怎样处理超时。

怎样避免轮询。





假设每次循环都须要主动检測请求队列,可能比較低效。由于一方面肯定要从

curl的select中退出,然后去检測请求队列,而检測时可能发现没有请求。另外一方面

訪问队列得加锁。





一般而言,这个问题的解决方式是用个Event。

假设这里用Event,那么退出,删除操作是不是也得有个Event。另外Event的自己主动切换状态

或手动切换状态会不会切出问题。Event的信息量是1,处理多个请求足够吗。

事实上Event的信息量处理多个请求是够的,仅仅要加入请求就触发一次事件能保证work的。





然而在这里不使用Event解决问题,使用windows消息,为加入,删除,结束分别定义消息。

用一个状态变量记录上次处理请求队列时是否有未处理的请求,否则须要加锁地读请求队列状态。

当状态变量为true时,请求队列一定非空。当状态变量为false时,请求队列可能是空,也能够有

请求,这说明在上一次读请求队列后请求队列中又加了请求,会通过消息来唤起当前线程。





先来看IO循环,在这些条件下应该退出IO循环:

1.still running的handle数为0,不须要进入IO循环。

2.still running的handle数目发生变化时应该退出IO循环,这样,外部就有可能处理IO完毕。

3.still running数没有达到最大,且有未处理请求时。

4.有消息时应该退出IO循环,这个不用说。





这里select是在socket上等待,而其他4个退出条件须要轮询,所以在实现上有点违背curl_multi

的设计初衷。假设每次都额外select一个socket,在发线程消息时,往这个socket发点数据,

就能够将对线程消息的检測放到select中来。前三个退出条件和当前的still running的handle数

相关,能够在select前检測一次。select退出前still running的handle数不会发生变化,所以

能够放心select同一时候保持敏感。只是这里没有再创建socket了,还是用轮询吧。





在IO循环外是http线程的主循环,主循环可能干这些事:

A.处理IO完毕。

B.处理消息。

C.在没有任务时放弃自己的线程运行,通过GetMessage进入休眠。

D.请求队列非空,且当前运行的handle没有达到最大时,须要处理请求队列。

E.设置TIMER(须要建立一个消息窗体),用于检測超时。消息时间是最先可能造时的请求超时的时间。





这些事怎样安排?

最開始两个是事件派发类:B和C是同一类逻辑,一量地B或C须要处理,即使进入了IO循环也会

立即退出。

接着是D,检測一下能否进入GetMessage的调用状态,假设能,则一直等待有消息,又一次进入BC的逻辑。

然后是A,表示没有外部的事要处理,专心做IO吧。

而E,在不论什么可能影响超时时间的后面都加一个。

for (;;)
{
if (m_UnHandleRequest > 0 && m_RunningRequest < MAX_RUNNING_REQUEST)
{
HandleQueueingRequest();
ModifyTimer();
}
while (::PeekMessage())
{
ProcessMessage();
if (m_QuitFlag)
{
ClearResource();
break;
}
}
if (m_QuitFlag)
{
break;
}
ModifyTimer(); if (!HaveRequestAndIO())
{
GetMessage();
ProcessMessage();
if (m_QuitFlag)
{
ClearResource();
break;
}
}
if (m_QuitFlag)
{
break;
}
ModifyTimer(); IOLoop();
IOComplete();
ModifyTimer();
} void ProcessMessage()
{
case 退出:m_QuitFlag = 1; break;
case 取消Request:HandleCanceledRequest(); break;
case 加入Request:HandleQueueingRequest();break;
} void HandleCanceledRequest()
{
在请求队列中,直接remove掉。
在running中,相应的remove操作。
// bad case,在处理请求时,完毕的task已经进入调用者线程的task队列
} void HandleQueueingRequest()
{
将请求队列中的请求放到Running队列中。
调用curl相应接口開始处理相应的请求。
更新m_UnHandleRequest。
} void ModifyTimer()
{
计算近期超时,假设存在则更新timer。
} void IOLoop()
{
int m_OldRunningRequest = m_RunningRequest;
while (m_RunningRequest)
{
if (m_UnHandleRequest > 0 && m_RunningRequest < MAX_RUNNING_REQUEST)
{
break;
}
if (PeekMessage())
{
break;
}
if (m_OldRunningRequest != m_RunningRequest)
{
break;
} curl_multi_timeout();
改动超时时间,假设超时时间超过100毫秒。 假设超时时间是0,须要curl_multi_perform一次。
curl_multi_fdset();
select();
}
}

基于curl的异步http实现的更多相关文章

  1. .NET - 基于事件的异步模型

    注:这是大概四年前写的文章了.而且我离开.net领域也有四年多了.本来不想再发表,但是这实际上是Active Object模式在.net中的一种重要实现方法,因此我把它掏出来发布一下.如果该模型有新的 ...

  2. 基于事件的异步模式(EAP)

    什么是EAP异步编程模式 EAP基于事件的异步模式是.net 2.0提出来的,实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类都支持异步 ...

  3. 实践基于Task的异步模式

    Await 返回该系列目录<基于Task的异步模式--全面介绍> 在API级别,实现没有阻塞的等待的方法是提供callback(回调函数).对于Tasks来说,这是通过像ContinueW ...

  4. 实现基于Task的异步模式

    返回该系列目录<基于Task的异步模式--全面介绍> 生成方法 编译器生成 在.NET Framework 4.5中,C#编译器实现了TAP.任何标有async关键字的方法都是异步方法,编 ...

  5. 基于Task的异步模式的定义

    返回该系列目录<基于Task的异步模式--全面介绍> 命名,参数和返回类型 在TAP(Task-based Asynchronous Pattern)中的异步操作的启动和完成是通过一个单独 ...

  6. 基于Task的异步模式--全面介绍

    今天是国庆长假第一天,也是今天十月的开始.每到这个时候都是看海的季节-一个看"人海"的季节.反正我是不想在这样一个尴尬期出去放松自己,于是不如在家写写博客,长点本领呢.今天就来给大 ...

  7. 模拟登录神器之PHP基于cURL实现自动模拟登录类

    一.构思 从Firefox浏览器拷贝cURL命令(初始页.提交.提交后) 自动分析curl形成模拟登录代码 默认参数:ssl/302/gzip 二.实现 接口 (一)根据curl信息执行并解析结果 p ...

  8. Event-based Asynchronous Pattern Overview基于事件的异步模式概览

    https://msdn.microsoft.com/zh-cn/library/wewwczdw(v=vs.110).aspx Applications that perform many task ...

  9. 在Silverlight中的DispatcherTimer的Tick中使用基于事件的异步请求

    需求:在silverlight用户界面上使用计时器定时刷新数据. 在 Silverlight 中的 DispatcherTimer 的 Tick 事件 中使用异步请求数据时,会出现多次请求的问题,以下 ...

随机推荐

  1. Python decorator装饰器

    问题: 定义了一个新函数 想在运行时动态增加功能 又不想改动函数本身的代码 通过高阶段函数返回一个新函数 def f1(x): return x*2 def new_fn(f): #装饰器函数 def ...

  2. chmod g+s 、chmod o+t 、chmod u+s:Linux高级权限管理

    关于linux下权限操作chmod的一些说明!比rxw高级内容! 转载自http://blog.chinaunix.net/uid-26642180-id-3378119.html Set uid, ...

  3. 【LOJ】#2035. 「SDOI2016」征途

    题解 有人管它叫带权二分,有人管它叫dp凸优化,有人管它叫wqs二分-- 延伸出来还有zgl分治,xjp¥!%#!@#¥!# 当我没说 我们拆个式子,很容易发现所求的就是 \(m\sum_{i = 1 ...

  4. ASP.NET MVC中在Action获取提交的表单数据方法

    有Index视图如下: 视图代码如下: <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Mas ...

  5. ECshop 迁移到 PHP7版本时遇到的兼容性问题,ecshopphp7

    ECshop 迁移到 PHP7版本时遇到的兼容性问题,ecshopphp7 在 PHP7 上安装 ECShop V2.7.3时,报错! Deprecated: Methods with the sam ...

  6. FPGA In/Out Delay Timing Constaint

    先简单说说这段时间遇到的问题.FPGA采集前端scaler的视频数据.像素时钟(随路时钟),视频数据,行场同步,DE.这些信号进入FPGA后.通过CSC(颜色空间转换).输出后的图像有噪点.通过查看时 ...

  7. matplotlib 中文显示 的问题

    第一种方法 from pylab import mpl import numpy as np mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体 ...

  8. Java 中类的初始化过程

    先来一张 JVM 中的内存模型 . 在Java 虚拟机原理这本书中介绍了类会被初始化的 5 种情况 . 1 遇到 new getstatic putstatic 和 invokestatic 这 4 ...

  9. POJ 3254 & POJ 1185(状压DP入门)

    Corn Fields Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 16773   Accepted: 8860 Desc ...

  10. mysql 通过cmd 在命令行创建数据库

    一.连接MYSQL 格式: mysql -h主机地址 -u用户名 -p用户密码 1. 连接到本机上的MYSQL. 首先打开DOS窗口,然后进入目录mysql\bin,再键入命令mysql -u roo ...