这篇文章记录 Lite2D 里 HttpClient 的实现思路。网络请求底层封装的是 curl,由于网络部分运行在独立线程,所以需要和主线程之间做线程同步。

Director 的主线程同步

为了让其他线程能够安全地把回调派发回主线程执行,Director 维护了一个互斥锁保护下的函数队列。其他线程通过 addFuncToMainThread 把 lambda 放入队列,主线程每一帧调用 processOtherThreadFunc 统一取出并执行,执行完就清空队列。

void Director::addFuncToMainThread(const std::function<void()>& func)
                {
                	_mutex_mainThread.lock();
                	_queue_other_thread_func.push_back(func);
                	_mutex_mainThread.unlock();
                
                }
                
                
                void Director::processOtherThreadFunc()
                {
                	// 	for (int i = 0;i<_queue_other_thread_func.size(); ++i)
                	// 	{
                	// 		_queue_other_thread_func[i]();
                	// 	}
                
                
                	_mutex_mainThread.lock();
                
                	for (int i = 0; i < _queue_other_thread_func.size(); ++i)
                	{
                		_queue_other_thread_func[i]();
                
                	}
                	_queue_other_thread_func.clear();
                
                	_mutex_mainThread.unlock();
                }

Lite2D 的 HttpClient 实现

HttpClient 是一个单例,内部起一个独立工作线程跑 workFunc。外部通过 send 把 HttpRequest 投递进请求队列,工作线程被条件变量唤醒后从队列取出请求,调用 doRequest_curl 实际执行 curl。

curl 的数据回调 write_data 会把响应写入 HttpRespone,然后通过 Director::addFuncToMainThread 把用户的 HttpCallBack 派发回主线程执行,避免在网络线程里直接触发游戏逻辑。

doRequest_curl 里还根据 curl 返回的 HTTP 状态码(200 / 0 / 其他)分别设置 HttpStatus 为 OK / TIMEOUT / NETERROR,其中超时分支会再次手动调用一次 write_data,以便把超时事件也通过同样的回调链路回调出去。

#include"HttpClient.h"
                #include "HttpRequest.h"
                #include "HttpRespone.h"
                #include "../base/Director.h"
                
                #include "../3party/curl/curl.h"
                
                
                
                static int write_data(void *buffer, size_t size, size_t buffer_size, void *_respone)
                {
                
                	HttpRespone *respone = (HttpRespone*)_respone;
                
                
                	HttpRequest*request = respone->getHttpRequest();
                
                	respone->writeData(buffer, buffer_size);
                
                	request->release();
                
                
                
                
                
                	Director::getInstance()->addFuncToMainThread([=]()
                	{
                		const HttpCallBack & x = request->getHttpCallback();
                
                		x(request, respone);
                
                		request->release();
                		respone->release();
                	});
                
                
                
                
                	return 0;
                }
                
                
                HttpClient* HttpClient::getInstance()
                {
                	static HttpClient*ins = nullptr;
                	if (ins == nullptr)
                	{
                		ins = new HttpClient;
                	}
                	return ins;
                
                }
                
                
                
                
                
                void HttpClient::doRequest_curl(HttpRequest*request)
                {
                
                	HttpRespone *respone = HttpRespone::create();
                
                
                	CURL*easy_handle = curl_easy_init();
                	curl_easy_setopt(easy_handle, CURLOPT_URL, request->getUrl().c_str());
                	curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, write_data);
                	curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, respone);
                	curl_easy_setopt(easy_handle, CURLOPT_TIMEOUT, this->_time_read);
                	curl_easy_setopt(easy_handle, CURLOPT_CONNECTTIMEOUT, this->_time_connect);
                
                
                
                	// do post or get
                	if (request->getHttpType() == HttpType::POST)
                	{//post
                
                		curl_easy_setopt(easy_handle, CURLOPT_HTTPPOST, 1);
                		curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, request->getPostData().c_str());
                	}
                	else if (request->getHttpType() == HttpType::GET)
                	{//get
                
                		curl_easy_setopt(easy_handle, CURLOPT_HTTPGET, 1);
                
                	}
                	respone->setHttpRequest(request);
                	request->setCURLhandle(easy_handle);
                
                	// perform
                	auto succ = curl_easy_perform(easy_handle);
                
                	// do http status code 404 200 etc.
                	int code;
                	curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &code);
                
                	std::cout << "  code:" << code << std::endl;
                	switch (code)
                	{
                	case 200:
                	{
                				respone->setHttpStatus(HttpStatus::OK);
                	}break;
                
                	case  0:
                	{//timeout such read and connect
                			   respone->setHttpStatus(HttpStatus::TIMEOUT);
                			   write_data(0, 0, 0, respone);
                
                	}break;
                	default:
                	{//404 etc.
                			   respone->setHttpStatus(HttpStatus::NETERROR);
                	}
                		break;
                	}
                	respone->setOriginHttpCode(code);
                
                }
                
                
                
                
                
                
                void HttpClient::workFunc()
                {
                	while (true)
                	{
                		HttpRequest* request;
                
                		_mutex.lock();
                
                		if (_queue_request.size() <= 0)
                		{
                			_condition.wait(_mutex);
                		}
                		request = _queue_request.front();
                		_queue_request.pop();
                		_mutex.unlock();
                
                		//	log("net thread  %d", _queue_request.size());
                
                		this->doRequest_curl(request);
                
                
                	}
                
                }
                
                
                
                
                HttpClient::HttpClient()
                {
                	this->_time_connect = 5;
                	this->_time_read = 10;
                	curl_global_init(CURL_GLOBAL_WIN32);
                
                	auto t = std::thread([=]
                	{
                		this->workFunc();
                	});
                
                
                	t.detach();
                }
                
                
                
                void HttpClient::send(HttpRequest*request)
                {
                	request->retain();
                
                	_mutex.lock();
                	_queue_request.push(request);
                	_mutex.unlock();
                
                	_condition.notify_one();
                
                
                }
                
                
                HttpClient::~HttpClient()
                {
                	_condition.notify_all();
                }