`
zsxxsz
  • 浏览: 450545 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

使用 acl 库 rpc 功能类实现 阻塞任务过程与MFC 界面过程分离

阅读更多

一、概述

       MFC 程序员在编写 Windows 界面程序时经常需要处理一些阻塞任务过程,为了避免阻塞窗口的消息过程,一般会将阻塞过程将由一个子线程处理,该子线程在处理过程中通过向界面线程发送 Windows 窗口消息将处理结果传递给窗口线程。在 acl 库中的 rpc 功能类实现了更为方便的处理方式,通过 rpc 功能类,用户可以在主线程中进行非阻塞过程(如:界面消息过程或网络非阻塞通讯过程),而将阻塞任务交由子线程处理(如:网络阻塞通讯或数据库操作等),子线程可以将任务处理的中间状态和最终状态通过 rpc 功能类传递给主线程。

       acl 的 rpc 类不仅能实现网络通讯方面的阻塞与非阻塞的粘合,同时还实现了阻塞过程与 MFC 界面过程的粘合,本文将以一个具体的 HTTP 下载过程为例来描述这一过程(示例在 acl 库中的 acl/lib_acl_cpp/samples/gui_rpc 目录下)。关于 acl 库中 rpc 相关类的使用,用户可以参考 《acl_cpp 的 rpc 相关类整合阻塞及非阻塞过程》(在该文中的例子描述了非阻塞主线程与阻塞子线程的交互过程,其示例代码适用于 win32 及 linux 平台)。

 

二、实例

       1、在界面主线程中初始化时创建 rpc 服务对象:acl::rpc_service

 

	// 全局静态变量
	static acl::aio_handle* handle_;
	static acl::rpc_service* service_;

	......

	// 创建非阻塞框架句柄,并采用 WIN32 消息模式:acl::ENGINE_WINMSG
	handle_ = new acl::aio_handle(acl::ENGINE_WINMSG);

	// 创建 rpc 服务对象
	int max_threads = 10;  // 服务最大子线程数量
	service_ = new acl::rpc_service(max_threads, true);
	// 打开消息服务
	if (service_->open(handle_) == false)
		logger_fatal("open service error: %s", acl::last_serror());

       在上面代码中,有几点需要注意:1)创建的 rpc 服务对象是全局性的;2)在创建非阻塞句柄时必须指定为 win32 界面消息事件类型:acl::ENGINE_WINMSG;3)在创建 rpc_service 时的第二个参数 win32_gui 必须为 true。

  

    2、创建 rpc 中 acl::rpc_request 类的子类,以实现阻塞非阻塞粘合过程,本例中该子类为:http_download,在 http_download 类中必须实现父类 acl::rpc_request 中定义的两个纯虚接口:rpc_run,rpc_onover。

       其中,http_download 的头文件如下:

 

/**
 * http 请求过程类,该类对象在子线程中发起远程 HTTP 请求过程,将处理结果
 * 返回给主线程
 */
class http_download : public acl::rpc_request
{
public:
	/**
	 * 构造函数
	 * @param addr {const char*} HTTP 服务器地址,格式:domain:port
	 * @param url {const char*} http url 地址
	 * @param callback {rpc_callback*} http 请求结果通过此类对象
	 *  通知主线程过程
	 */
	http_download(const char* addr, const char* url,
		rpc_callback* callback);
protected:
	~http_download() {}

	// 基类虚函数:子线程处理函数
	virtual void rpc_run();

	// 基类虚函数:主线程处理过程,收到子线程任务完成的消息
	virtual void rpc_onover();

	// 基类虚函数:主线程处理过程,收到子线程的通知消息
	virtual void rpc_wakeup(void* ctx);

        ......

       在 http_download 类的构造参数中有一个接口类:rpc_callback,这是一个纯虚类,主要是为了方便将 http 的结果数据返回给主线程,该类的声明如下:

  

// 纯虚类,子类须实现该类中的纯虚接口
class rpc_callback
{
public:
	rpc_callback() {}
	virtual ~rpc_callback() {}

	// 设置 HTTP 请求头数据虚函数
	virtual void SetRequestHdr(const char* hdr) = 0;
	// 设置 HTTP 响应头数据虚函数
	virtual void SetResponseHdr(const char* hdr) = 0;
	// 下载过程中的回调函数虚函数
	virtual void OnDownloading(long long int content_length,
		long long int total_read) = 0;
	// 下载完成时的回调函数虚函数
	virtual void OnDownloadOver(long long int total_read,
		double spent) = 0;
};

      http_download 类的函数实现如下:

#include "stdafx.h"
#include <assert.h>
#include "http_download.h"

// 由子线程动态创建的 DOWN_CTX 对象的数据类型
typedef enum
{
	CTX_T_REQ_HDR,		// 为 HTTP 请求头数据
	CTX_T_RES_HDR,		// 为 HTTP 响应头数据
	CTX_T_CONTENT_LENGTH,	// 为 HTTP 响应体的长度
	CTX_T_PARTIAL_LENGTH,	// 为 HTTP 下载数据体的长度
	CTX_T_END
} ctx_t;

// 子线程动态创建的数据对象,主线程接收此数据
struct DOWN_CTX 
{
	ctx_t type;
	long long int length;
};

// 用来精确计算时间截间隔的函数,精确到毫秒级别
static double stamp_sub(const struct timeval *from,
	const struct timeval *sub_by)
{
	struct timeval res;

	memcpy(&res, from, sizeof(struct timeval));

	res.tv_usec -= sub_by->tv_usec;
	if (res.tv_usec < 0)
	{
		--res.tv_sec;
		res.tv_usec += 1000000;
	}

	res.tv_sec -= sub_by->tv_sec;
	return (res.tv_sec * 1000.0 + res.tv_usec/1000.0);
}

//////////////////////////////////////////////////////////////////////////

// 子线程处理函数
void http_download::rpc_run()
{
	acl::http_request req(addr_);  // HTTP 请求对象
	// 设置 HTTP 请求头信息
	req.request_header().set_url(url_.c_str())
		.set_content_type("text/html")
		.set_host(addr_.c_str())
		.set_method(acl::HTTP_METHOD_GET);

	req.request_header().build_request(req_hdr_);
	DOWN_CTX* ctx = new DOWN_CTX;
	ctx->type = CTX_T_REQ_HDR;
	rpc_signal(ctx);  // 通知主线程 HTTP 请求头数据

	struct timeval begin, end;;
	gettimeofday(&begin, NULL);

	// 发送 HTTP 请求数据
	if (req.request(NULL, 0) == false)
	{
		logger_error("send request error");
		error_ = false;
		gettimeofday(&end, NULL);
		total_spent_ = stamp_sub(&end, &begin);
		return;
	}

	// 获得 HTTP 请求的连接对象
	acl::http_client* conn = req.get_client();
	assert(conn);

	(void) conn->get_respond_head(&res_hdr_);
	ctx = new DOWN_CTX;
	ctx->type = CTX_T_RES_HDR;
	rpc_signal(ctx);   // 通知主线程 HTTP 响应头数据

	ctx = new DOWN_CTX;
	ctx->type = CTX_T_CONTENT_LENGTH;
	
	ctx->length = conn->body_length();  // 获得 HTTP 响应数据的数据体长度
	content_length_ = ctx->length;
	rpc_signal(ctx);  // 通知主线程 HTTP 响应体数据长度

	acl::string buf(8192);
	int   real_size;
	while (true)
	{
		// 读 HTTP 响应数据体
		int ret = req.read_body(buf, true, &real_size);
		if (ret <= 0)
		{
			ctx = new DOWN_CTX;
			ctx->type = CTX_T_END;
			ctx->length = ret;
			rpc_signal(ctx);  // 通知主线程下载完毕
			break;
		}
		ctx = new DOWN_CTX;
		ctx->type = CTX_T_PARTIAL_LENGTH;
		ctx->length = real_size;
		// 通知主线程当前已经下载的大小
		rpc_signal(ctx);
	}

	// 计算下载过程总时长
	gettimeofday(&end, NULL);
	total_spent_ = stamp_sub(&end, &begin);

	// 至此,子线程运行完毕,主线程的 rpc_onover 过程将被调用
}

//////////////////////////////////////////////////////////////////////////

http_download::http_download(const char* addr, const char* url,
	rpc_callback* callback)
	: addr_(addr)
	, url_(url)
	, callback_(callback)
	, error_(false)
	, total_read_(0)
	, content_length_(0)
	, total_spent_(0)
{

}

//////////////////////////////////////////////////////////////////////////

// 主线程处理过程,收到子线程任务完成的消息
void http_download::rpc_onover()
{
	logger("http download(%s) over, 共 %I64d 字节,耗时 %.3f 毫秒",
		url_.c_str(), total_read_, total_spent_);
	callback_->OnDownloadOver(total_read_, total_spent_);
	delete this;  // 销毁本对象
}

// 主线程处理过程,收到子线程的通知消息
void http_download::rpc_wakeup(void* ctx)
{
	DOWN_CTX* down_ctx = (DOWN_CTX*) ctx;

	// 根据子线程中传来的不同的下载阶段进行处理

	switch (down_ctx->type)
	{
	case CTX_T_REQ_HDR:
		callback_->SetRequestHdr(req_hdr_.c_str());
		break;
	case CTX_T_RES_HDR:
		callback_->SetResponseHdr(res_hdr_.c_str());
		break;
	case CTX_T_CONTENT_LENGTH:
		break;
	case CTX_T_PARTIAL_LENGTH:
		total_read_ += down_ctx->length;
		callback_->OnDownloading(content_length_, total_read_);
		break;
	case CTX_T_END:
		logger("%s: read over", addr_.c_str());
		break;
	default:
		logger_error("%s: ERROR", addr_.c_str());
		break;
	}

	// 删除在子线程中动态分配的对象
	delete down_ctx;
}

//////////////////////////////////////////////////////////////////////////

  

      3、在 MFC 界面类中创建 rpc_callback 的子类,接收子线程的 HTTP 处理结果:本例直接将对话框类继承了 rpc_callback 接口类,其中部分内容如下:

 

// Cgui_rpcDlg 对话框
class Cgui_rpcDlg : public CDialog
	, public rpc_callback
{
// 构造
public:
	Cgui_rpcDlg(CWnd* pParent = NULL);	// 标准构造函数
	~Cgui_rpcDlg();

        ......

public:
	// 基类 rpc_callback 虚函数

	// 设置 HTTP 请求头数据虚函数
	virtual void SetRequestHdr(const char* hdr);
	// 设置 HTTP 响应头数据虚函数
	virtual void SetResponseHdr(const char* hdr);
	// 下载过程中的回调函数虚函数
	virtual void OnDownloading(long long int content_length,
		long long int total_read);
	// 下载完成时的回调函数虚函数
	virtual void OnDownloadOver(long long int total_read,
		double spent);
        ......
};

  

     4、用 VC2003 编译该例子,运行可执行程序可以得到如下的界面:

  

       运行这个例子,在 URL 中输入地址(如:http://www.sina.com.cn),点“开始运行”按钮,在下载 URL 数据的过程中移动界面窗口,可以看到界面窗口的消息过程并未被阻塞(因为 HTTP 阻塞下载过程是在子线程中进行的),同时界面的状态栏还能实时显示当前 URL 下载的进度状态(子线程通过 rpc_request 的消息传递方式将下载状态通知界面主线程)。

 

四、小结

       在界面编程中,将阻塞过程与界面过程分离( 即将阻塞过程交由子线程处理)是一种编程思想,不仅可以用在 PC 机的界面编程中,同时对于手机 APP 开发也有用处,这样做的好处是:一方面可以利用多核,更重要的是使得界面编程更为简单(要比所有模块全部采用非阻塞编程要容易得多)。

五、参考

《acl_cpp 的 rpc 相关类整合阻塞及非阻塞过程》

acl 库下载地址:http://sourceforge.net/projects/acl/

acl 库 SVN 地址:svn://svn.code.sf.net/p/acl/code/

github 地址:https://github.com/acl-dev/acl

个人微博:http://weibo.com/zsxxsz

QQ 群:242722074

 

2
1
分享到:
评论

相关推荐

    acl C++跨平台库.rar

    C 语言写的一些网络应用协议库)、lib_acl_cpp(用 C++ 语言编写,封装了 lib_acl/lib_protocol 两个库,同时增加更多实用的功能库)、 lib_fiber(用 C 语言编写的支持高性能、高并发的网络协程库)、lib_rpc(用...

    acl库,编译好的库文件

    直接使用编译好的库可以大大简化集成过程,快速将ACL功能引入到项目中。 “json解析”标签表明ACL库可能包含了处理JSON(JavaScript Object Notation)数据的能力。JSON是一种轻量级的数据交换格式,广泛应用于Web...

    cisco最完美的ACL配置详解及配置全过程

    cisco最完美的ACL配置详解及配置全过程cisco最完美的ACL配置详解及配置全过程cisco最完美的ACL配置详解及配置全过程cisco最完美的ACL配置详解及配置全过程cisco最完美的ACL配置详解及配置全过程cisco最完美的ACL配置...

    acl库 (advanced c library)

    ACL库,全称为Advanced C Library,是一个为C程序员设计的跨平台库,旨在提供一系列高效且易用的数据处理算法,以及强大的服务器框架。这个库特别适合于开发需要高性能、高并发性和良好扩展性的网络应用程序。在本文...

    acl 的使用方法

    使用 ACL 可以实现报文过滤、阻塞攻击报文、为不同类报文流提供差分服务等功能。ACL 的使用方法可以分为以下几个步骤: 1. 创建 ACL:创建 ACL 需要指定 ACL 的名称和编号。 2. 定义 ACL 规则:定义 ACL 规则需要...

    BCM实现ACL功能

    实现ACL功能。 ACL,Access Control List,访问控制列表,是路由器和交换机接口的指令列表,用来控制端口进出的数据包。这张表中包含了匹配关系、条件和查询语句,表只是一个框架结构,其目的是为了对某种访问进行...

    网络课程设计--ACL编程实现

    4. **网络套接字编程**:理解TCP/IP协议栈,以及如何使用Socket类进行数据包的接收和发送,是实现ACL的基础。 接下来是VB(Visual Basic)版本的实现。VB是Microsoft开发的一种事件驱动的编程语言,常用于快速开发...

    基于acl库封装c\c++ 的redis-client客户端源码

    基于acl库封装的redis client vs2010工程; 运行时解压到: redis-acl\lib_acl_cpp\samples\redis路径下,把lib_acl_vc2010d.lib、lib_acl_cpp_vc2010d.lib放到 \redis-acl\lib_acl_cpp\samples\redis\redisclient...

    基于BCM53115实现ACL功能

    "基于BCM53115实现ACL功能" 基于BCM53115实现ACL功能是指利用BCM53115芯片来实现访问控制列表(Access Control List,ACL)功能。ACL是一种流分类技术,用于控制网络设备对数据流的处理。ACL的本质是人为定义的一些...

    基于ACL搭建VGG16实现图像分类应用-内含源码和说明书.zip

    基于ACL搭建VGG16实现图像分类应用 student 文件夹则是学生需要完成的任务,可能包括编写代码、实现特定功能或者调整模型参数。学生需要根据教师的指导,利用ACL来控制VGG16模型的学习过程,例如设置学习率衰减策略...

    mfc自动删除图片按天数保存

    3. **定时任务**:为了实现定期检查并删除过期图片,可以使用MFC的CTimer类。CTimer允许设置一个定时器,当达到预设时间间隔时触发事件。在回调函数中执行文件检查和删除操作。 4. **权限和安全**:在删除文件之前...

    acl-master.zip_ACL库_c++ acl_git acl-master

    acl_cpp 是基于 acl 库的 C++ 库,包括 MIIME 解析、Handlersocket 客户端库、数据库连接池(支持mysql/sqlite)、WEB 编程、数据库编程、阻塞/非阻塞数据流等内容。

    基于Spring Security的ACL实现与扩展ppt

    **Spring Security ACL 实现与扩展** **ACL 简介** 访问控制列表(Access Control List,简称 ACL)是一种用于限制不同用户或用户组对资源访问的机制。在计算机安全领域,ACL 允许系统管理员定义特定用户的访问权限...

    Python库 | acl_iitbbs-0.1-py3-none-any.whl

    如果这个库提供了API接口,开发者可以通过调用这些接口与其他系统集成,实现数据交换或功能扩展。 总之,`acl_iitbbs`是一个针对Python 3环境的库,用于与IITBBS论坛交互。`.whl`文件的提供使得安装更加便捷。要...

    acl,一个用于Linux、Mac、FreeBSD、Solaris(x86)、Windows、Android、iOS的高级C/C++库.zip

    例如,你可以使用ACL库来创建一个新的ACL,添加或删除特定用户或组的权限,或者检查现有文件的ACL设置。 对于Windows系统,ACL同样扮演着关键的角色,但其工作方式略有不同。Windows的安全模型基于安全标识符(SID...

    使用扩展ACL限制公司网络访问.rar

    3、应用场景:使用扩展ACL限制公司网络访问 4、特点:Word文档提供了实验的详细过程,包括每一步骤的操作命令和截图,并给出了实验的topo文件(包括配置信息) 5、适用人群:网络系统的建设与运维 6、使用说明:重点...

    使用基本ACL限制公司网络访问.rar

    3、应用场景:使用基本ACL限制公司网络访问 4、特点:Word文档提供了实验的详细过程,包括每一步骤的操作命令和截图,并给出了实验的topo文件(包括配置信息) 5、适用人群:网络系统的建设与运维 6、使用说明:重点...

    实验:使用基本ACL限制公司网络访问.docx

    本实验"使用基本ACL限制公司网络访问"旨在让学生深入理解并掌握如何通过访问控制列表(ACL)来实现这一目标。实验主要涉及了eNSP(Enterprise Network Simulation Platform,企业网络模拟平台)、ACL、VLAN以及三层...

    acl.3.0.18.gz_ACL

    2. 上层app接口:这个部分指的是与ACL库交互的接口,使得开发者可以方便地在应用程序中使用ACL功能。这些接口可能包括创建、修改、查询和删除ACL条目的函数,以及处理用户和组权限的工具。 3. Windows和Linux支持:...

    基于Linux2.6内核ACL功能体验

    - 接着,挂载该文件系统时,使用`mount`命令并添加`acl`选项,以启用ACL功能。 2. **ACL的基本操作**: - 创建一个文件后,可以使用`setfacl`命令添加ACL。例如,让用户A有读取权限,用户B所在的组有读写权限,但...

Global site tag (gtag.js) - Google Analytics