acl_cpp 中流式編程的設計

1、爲何須要流式編程node

      首先解釋一下什麼是流式編程,所謂流式編程是指編程接口能夠接收部分輸入數據,邊接收邊處理,沒必要等待完整數據。這比如 TCP 數據流,由於網絡傳輸的因素,每次讀操做並不能保證會得到完整的數據塊,每次僅讀取一部分數據,屢次讀取才可能會讀到一塊完整的數據,基於 TCP 的應用服務也比較多,象 HTTP/SMTP/POP 等服務都是基於 TCP 傳輸協議進行數據傳輸的,由於 TCP 流的流式特色,因此這些應用都定義了數據完整性的規則。正由於有了這些數據完整性規則,才使得編程變得簡單,所以不少實現都是根據這些完整性原則在讀取了完整數據後才進行處理。編程

      流式編程由於容許每次輸入的數據僅是一部分,甚至可能只有一個字節,因此就須要維護一個數據流的處理狀態(內部可能會有不少標誌位和緩衝區),通常會採用有限狀態機的方法進行編程(象知名的 telnet 服務就是採用了有限狀態機的編程思想)。json

      既然能夠把基於 TCP 流的應用協議根據具體應用的數據完整性規則轉換爲非流式編程,那爲何還須要採用複雜的流式編程呢?緣由就是爲了適應數據流的多樣性以及應用的複雜性。採用流式編程方式,既能夠應用於非阻塞 IO,又能夠應用於阻塞 IO,而若是採用非流式編程,則通常僅能採用阻塞 IO 編程了;同時,採用流式編程,還能夠很是容易使數據流以管道的方式從一個流式編程接口輸給下一個流式編程接口,實現流水線式的數據處理過程。xcode

 

2、acl_cpp 中流式編程接口的設計服務器

      acl_cpp 中爲了處理流式數據,設計了兩種類:流式數據處理器(簡稱流式處理器)和流式處理器管理器(流式管理器)。流式處理器類中,定義了一個虛接口,規定了子類爲了實現某一具體應用的流式處理功能而必須遵循的接口;流式管理器,負責管理流式處理器,將這些流式處理器組成一個數據流管道,接收用戶的數據輸入,將數據流從一個管道(即流式處理器)傳向另外一個管道。網絡

      一、流式處理器類函數

      pipe_stream:流式處理器基類,是一個純虛類。繼承該類的子類必須實現三個虛方法中的兩個純虛接口:push_pop,pop_end,子類可根據須要實現另外一個虛方法:clear。編碼

      1.一、push_pop 純虛接口定義:.net

/**
		 * 數據輸入輸出接口
		 * @param in {const char*} 輸入數據的地址
		 * @param len {size_t} 輸入數據長度
		 * @param out {string*} 存儲輸出結果緩衝區,不能爲空
		 * @param max {size_t} 但願接收到輸出結果的長度限制,若是爲0則
		 *  表示沒有限制,輸出結果都存儲在 out 緩衝區中
		 * @return {int} 輸出數據的長度,若是 < 0 則表示出錯
		 */
		virtual int push_pop(const char* in, size_t len,
			string* out, size_t max = 0) = 0;

       該接口接收外部數據流,同時將中間處理結果輸出。設計

      1.二、pop_end 純虛接口定義:

/**
		 * 最後處理的輸出數據接口
		 * @param out {string*} 存儲輸出結果緩衝區,不能爲空
		 * @param max {size_t} 但願接收到輸出結果的長度限制,若是爲0則
		 *  表示沒有限制,輸出結果都存儲在 out 緩衝區中
		 * @return {int} 輸出數據的長度,若是 < 0 則表示出錯
		 */
		virtual int pop_end(string* out, size_t max = 0) = 0;

       當數據流結束時(即已經讀到了完整的數據時),子類必須實現該接口,將自身緩衝區裏的數據處理後輸出給調用者。

      二、流式管理器類

      pipe_manager:流式處理器類的管理器類。該類主要定義並實現了四個方法:push_back,push_front,update,update_end。

      2.1 push_back/push_front 方法:

/**
		 * 以尾部添加的方式註冊新的管道流處理器
		 * @param stream {pipe_stream*} 管道流處理器對象
		 * @return {bool} 若是該管道流處理器對象已經存在則返回 false
		 */
		bool push_back(pipe_stream* stream);

		/**
		 * 以頭部添加的方式註冊新的管道流處理器
		 * @param stream {pipe_stream*} 管道流處理器對象
		 * @return {bool} 若是該管道流處理器對象已經存在則返回 false
		 */
		bool push_front(pipe_stream* stream);

       這兩個方法分別以尾部或頭部添加的方式,將流式處理器加入到流式管道中,造成數據流處理的管道。須要注意的是:添加的流式處理器對象必須在 pipe_manager 的類實例做用域內依然有效,pipe_manager 流式管理器實例並不負責流式處理器對象的銷燬,若是這些流式處理器是動態建立的,用戶應該負責對象銷燬。

      2.二、update/update_end 方法:

/**
		 * 應用向管道流管理器添加新數據,由該管理器依次傳遞給全部已註冊管道流
		 * 處理器,同時從已註冊管道流處理器接收處理結果,依次傳遞給下一個
		 * @param src {const char*} 待處理的數據地址
		 * @param len {size_t} src 數據長度
		 * @param out {pipe_stream*} 若是非空,則該管道處理器將是最後一個只接收
		 *  輸入而不進行輸出的管道處理器
		 * @return {bool} 是否有錯誤發生
		 */
		bool update(const char* src, size_t len, pipe_stream* out = NULL);

		/**
		 * 最後必須調用一次該函數,以使有些管道的緩衝區裏的數據能夠一次性地
		 * 刷新至最後的管道中
		 * @param out {pipe_stream*} 若是非空,則該管道處理器將是最後一個只接收
		 *  輸入而不進行輸出的管道處理器
		 * @return {bool} 是否有錯誤發生
		 */
		bool update_end(pipe_stream* out = NULL);

       這兩個方法提供了數據輸入及數據輸出的方法,容許用戶每次僅輸入部分數據,流式管理器內部會自動將數據流在各個流式處理器之間進行傳遞;當用戶肯定數據完整時,應該調用 update_end 將管道流中可能存在的最後結果數據取出。

 

3、示例

      以 HTTP 應用爲例,客戶端在接收服務器響應數據時,假設數據是採用 utf-8 字符集的 xml 數據格式,同時對數據進行了壓縮處理。則客戶端接收到數據後,若是將接收到數據轉換爲GBK字符集後再提取數據字段,處理順序爲:解壓縮->字符集轉換->xml解析,而後才提取出須要的數據字段。若是這三個處理過程都提供了流式接口,則要方便得多,咱們只需將數據輸入一個流式處理器,而後提取中間處理結果,再將中間處理結果輸入到另外一個流式處理器便可。

      下面列出了實現上述功能的示例代碼:

#include "lib_acl.hpp"

bool http_get(acl::istream& in)
{
	// 初始化解壓庫
	acl::zlib_stream zlib;
	if (zlib.pipe_unzip_begin() = false)
	{
		printf("初始化解壓庫失敗\r\n");
		return false;
	}

	// 初始化字符集轉碼庫
	acl::charset_conv utf8ToGbk;
	if (utf8ToGbk.update_begin("utf-8", "gbk") == false)
	{
		printf("初始化字符集轉碼庫失敗\r\n");
		return false;
	}

	acl::xml xml;  // xml 數據解析器

	acl::pipe_manager manager;  // 流式管理器

	// 以尾部添加方式分別添加:解壓流式處理器、字符集轉碼處理器以及 xml 解析處理器,
	// 從而使處理管理流的處理方向爲:解壓處理->字符集轉碼處理->xml解析處理
	manager.push_back(&zlib);
	manager.push_back(&utf8ToGbk);
	manager.push_back(&xml);

	// 循環讀取數據流,進行處理
	char  buf[4096];
	int   ret;
	while (true)
	{
		ret = in.read(buf, sizeof(buf) - 1, false);  // 讀取部分數據
		if (ret == -1)
			break;
		buf[ret] = 0;
		if (manager.update(buf, ret) == false)   // 輸入數據至流式管理器
		{
			printf("流式處理器內部出錯\r\n");
			return false;
		}
	}
	if (manager.update_end() == false)   // 處理最後一部分數據
	{
		printf("流式處理器內部出錯\r\n");
		return false;
	}

	// 假設完整數據爲:
	// <users><user name="zsx1" age="1" /><user name="zsx2" age="2" /></users>
	// 想要提取名字爲 zsx2 的 age 字段,則可以下處理:

	// 提取符合 users/user 方式的 xml 結點對象集合
	const std::vector<acl::xml_node*>& users = xml.getElementsByTags("users/user");
	if (users.empty())
	{
		printf("zsx2 未發現\r\n");
		return false;
	}

	// 遍歷查詢結果集,找顳骨 zsx2 的 xml 結點
	std::vector<acl::xml_node*>::const_iterator cit = users.begin();
	for (; cit != users.end(); ++cit)
	{
		// 提取 name 屬性值
		const char* user = (*cit)->attr_value("name");
		if (strcasecmp(user, "zsx2") != 0)
			continue;

		// 提取 age 屬性值
		const char* age = (*cit)->attr_value("age");
		if (age == NULL)
			continue;
		printf("zsx2's age: %s\r\n", age);
		return true;
	}

	printf("zsx2's age not found\r\n");
	return false;
}

       以上是一個簡單的流式編程的示例,全部符合流式編程規則的處理器類不只能夠組合起來,由 pipe_manager 管理器統一管理,並且也能夠單獨使用。

 

4、acl_cpp 庫中支持流式編程的流式處理器類

      在 acl_cpp 庫中可以支持流式處理功能的類有:

      4.一、能夠和 pipe_manager 流式管理器配合的流式處理器

      pipe_string:字符串處理雙向管理流;

      xml:xml 數據格式流式解析處理器;

      json:json 數據格式流式解析處理器;

      mime_code/mime_base64/mime_uucode/mime_xxcode/mime_quoted_printable:郵件數據格式編碼/解碼的流式處理器;

      charset_conv:字符集轉碼處理器;

      zlib_stream:流式壓縮/解壓處理器;

      ostream:IO 輸出流處理器。

      4.二、暫時不能與 pipe_manager 流式管理器配合的流式處理器

      mime:郵件 mime 數據的流式解析處理器;

      rfc2047:郵件 mime rfc2047 編碼的流式解析處理器。      

 

我的微博:http://weibo.com/zsxxsz

本文連接地址:http://zsxxsz.iteye.com/blog/1566188

acl_cpp 下載

acl_cpp 的編譯與使用

QQ 羣:242722074

相關文章
相關標籤/搜索