16【cocos2d-x 源码分析】:HttpClient 的详细分析

    xiaoxiao2022-07-14  152

    对应源码位置:cocos2d-x-3.3\cocos\network\Http*

    HttpRequest的实现

    typedef std::function<void(HttpClient* client, HttpResponse* response)> ccHttpRequestCallback; //(cocos2d::Ref::*SEL_HttpResponse) 代表cocos2d::Ref类的这种成员函数 typedef void (cocos2d::Ref::*SEL_HttpResponse)(HttpClient* client, HttpResponse* response); //一个类型转换而已 #define httpresponse_selector(_SELECTOR) (cocos2d::network::SEL_HttpResponse)(&_SELECTOR) class CC_DLL HttpRequest : public Ref { public: //请求方式 enum class Type { GET, POST, PUT, DELETE, UNKNOWN, }; /** Constructor Because HttpRequest object will be used between UI thead and network thread, requestObj->autorelease() is forbidden to avoid crashes in AutoreleasePool new/retain/release still works, which means you need to release it manually Please refer to HttpRequestTest.cpp to find its usage */ //注意上面一=的话 autorelease不再能用了 由于多线程的问题 HttpRequest() { _requestType = Type::UNKNOWN; _url.clear(); _requestData.clear(); _tag.clear(); _pTarget = nullptr; _pSelector = nullptr; _pCallback = nullptr; _pUserData = nullptr; }; /** Destructor */ virtual ~HttpRequest() { if (_pTarget) { _pTarget->release(); } }; //专门告诉你 不能用了 Ref* autorelease(void) { CCASSERT(false, "HttpResponse is used between network thread and ui thread \ therefore, autorelease is forbidden here"); return NULL; } inline void setRequestType(Type type) { _requestType = type; }; inline Type getRequestType() { return _requestType; }; inline void setUrl(const char* url) { _url = url; }; inline const char* getUrl() { return _url.c_str(); }; inline void setRequestData(const char* buffer, size_t len) { _requestData.assign(buffer, buffer + len); }; inline char* getRequestData() { if(_requestData.size() != 0) return &(_requestData.front()); return nullptr; } inline ssize_t getRequestDataSize() { return _requestData.size(); } inline void setTag(const char* tag) { _tag = tag; }; inline const char* getTag() { return _tag.c_str(); }; inline void setUserData(void* pUserData) { _pUserData = pUserData; }; inline void* getUserData() { return _pUserData; }; //设置回调 inline void setResponseCallback(const ccHttpRequestCallback& callback) { _pCallback = callback; } inline Ref* getTarget() { return _pTarget; } /* This sub class is just for migration SEL_CallFuncND to SEL_HttpResponse, someday this way will be removed */ class _prxy { public: _prxy( SEL_HttpResponse cb ) :_cb(cb) {} ~_prxy(){}; operator SEL_HttpResponse() const { return _cb; } CC_DEPRECATED_ATTRIBUTE operator SEL_CallFuncND() const { return (SEL_CallFuncND) _cb; } protected: SEL_HttpResponse _cb; }; inline const ccHttpRequestCallback& getCallback() { return _pCallback; } //首部信息 inline void setHeaders(std::vector<std::string> pHeaders) { _headers=pHeaders; } inline std::vector<std::string> getHeaders() { return _headers; } protected: Type _requestType; /// kHttpRequestGet, kHttpRequestPost or other enums std::string _url; /// target url that this request is sent to std::vector<char> _requestData; /// used for POST std::string _tag; /// user defined tag, to identify different requests in response callback //用这两个 才能实现回调 Ref* _pTarget; /// callback target of pSelector function SEL_HttpResponse _pSelector; /// callback function, e.g. MyLayer::onHttpResponse(HttpClient *sender, HttpResponse * response) //用仿函数 更好实现 ccHttpRequestCallback _pCallback; /// C++11 style callbacks void* _pUserData; /// You can add your customed data here //自定义的头 std::vector<std::string> _headers; /// custom http headers };

    HttpResponse的实现

    class CC_DLL HttpResponse : public cocos2d::Ref { public: HttpResponse(HttpRequest* request) { _pHttpRequest = request; if (_pHttpRequest) { _pHttpRequest->retain(); } _succeed = false; _responseData.clear(); _errorBuffer.clear(); } /** Destructor, it will be called in HttpClient internal, users don't need to desturct HttpResponse object manully */ virtual ~HttpResponse() { if (_pHttpRequest) { _pHttpRequest->release(); } } //不能用 cocos2d::Ref* autorelease(void) { CCASSERT(false, "HttpResponse is used between network thread and ui thread \ therefore, autorelease is forbidden here"); return NULL; } inline HttpRequest* getHttpRequest() { return _pHttpRequest; } //看是否成功 inline bool isSucceed() { return _succeed; }; //获取返回的数据 inline std::vector<char>* getResponseData() { return &_responseData; } //返回的报文头 inline std::vector<char>* getResponseHeader() { return &_responseHeader; } //返回码 200 404 inline long getResponseCode() { return _responseCode; } //错误原因 inline const char* getErrorBuffer() { return _errorBuffer.c_str(); } inline void setSucceed(bool value) { _succeed = value; }; inline void setResponseData(std::vector<char>* data) { _responseData = *data; } inline void setResponseHeader(std::vector<char>* data) { _responseHeader = *data; } inline void setResponseCode(long value) { _responseCode = value; } inline void setErrorBuffer(const char* value) { _errorBuffer.clear(); _errorBuffer.assign(value); }; protected: bool initWithRequest(HttpRequest* request); // 属性 HttpRequest* _pHttpRequest; /// the corresponding HttpRequest pointer who leads to this response bool _succeed; /// to indecate if the http reqeust is successful simply std::vector<char> _responseData; /// the returned raw data. You can also dump it as a string std::vector<char> _responseHeader; /// the returned raw header data. You can also dump it as a string long _responseCode; /// the status code returned from libcurl, e.g. 200, 404 std::string _errorBuffer; /// if _responseCode != 200, please read _errorBuffer to find the reason };

    HttpClient的实现

    class CC_DLL HttpClient { public: //单例模式 static HttpClient *getInstance(); //销毁 static void destroyInstance(); //允许cookie void enableCookies(const char* cookieFile); //传入SSL verification 否则就代表不支持 void setSSLVerification(const std::string& caFile); //发送请求 void send(HttpRequest* request); //立即发送 void sendImmediate(HttpRequest* request); //超时时间 inline void setTimeoutForConnect(int value) {_timeoutForConnect = value;}; inline int getTimeoutForConnect() {return _timeoutForConnect;} //下载超时时间 inline void setTimeoutForRead(int value) {_timeoutForRead = value;}; inline int getTimeoutForRead() {return _timeoutForRead;}; private: HttpClient(); virtual ~HttpClient(); bool init(void); //初始化 信号量 互斥量 bool lazyInitThreadSemphore(); //网络线程的执行函数 void networkThread(); void networkThreadAlone(HttpRequest* request); //回调 void dispatchResponseCallbacks(); private: int _timeoutForConnect; int _timeoutForRead; };

    详细实现

    //一些配置 //两个队列的 互斥量 static std::mutex s_requestQueueMutex; static std::mutex s_responseQueueMutex; //条件变量 static std::condition_variable_any s_SleepCondition; //两个请求队列 static Vector<HttpRequest*>* s_requestQueue = nullptr; static Vector<HttpResponse*>* s_responseQueue = nullptr; //单例的 HTTPClient static HttpClient *s_pHttpClient = nullptr; // pointer to singleton //错误缓冲 static char s_errorBuffer[CURL_ERROR_SIZE] = {0}; //write_callback typedef size_t (*write_callback)(void *ptr, size_t size, size_t nmemb, void *stream); //cookie文件名 static std::string s_cookieFilename = ""; //SSL证书 位置 static std::string s_sslCaFilename = ""; //一个准备的请求对象 哨兵请求 用来当HttpClient 析构时 通知 工作线程退出 static HttpRequest *s_requestSentinel = new HttpRequest; // libcurl 用来收集response data 的回调 static size_t writeData(void *ptr, size_t size, size_t nmemb, void *stream) { std::vector<char> *recvBuffer = (std::vector<char>*)stream; size_t sizes = size * nmemb; //写入到stream中 recvBuffer->insert(recvBuffer->end(), (char*)ptr, (char*)ptr+sizes); return sizes; } // libcurl 用来收集header data 的回调 static size_t writeHeaderData(void *ptr, size_t size, size_t nmemb, void *stream) { std::vector<char> *recvBuffer = (std::vector<char>*)stream; size_t sizes = size * nmemb; //同理 recvBuffer->insert(recvBuffer->end(), (char*)ptr, (char*)ptr+sizes); return sizes; }

    两个重要的工作线程

    // Worker thread void HttpClient::networkThread() { auto scheduler = Director::getInstance()->getScheduler(); while (true) { HttpRequest *request; // step 1: send http request if the requestQueue isn't empty { //获取请求队列的锁 std::lock_guard<std::mutex> lock(s_requestQueueMutex); //如果为空 就要sleep while (s_requestQueue->empty()) { s_SleepCondition.wait(s_requestQueueMutex); } //否则 取出第一个需求 request = s_requestQueue->at(0); s_requestQueue->erase(0); } //是哨兵 就结束 if (request == s_requestSentinel) { break; } // step 2: libcurl sync access // Create a HttpResponse object, the default setting is http access failed //开始 包装response对象 HttpResponse *response = new (std::nothrow) HttpResponse(request); //开始处理 processResponse(response, s_errorBuffer); //处理完成了 放到response队列里面去 // add response packet into queue s_responseQueueMutex.lock(); s_responseQueue->pushBack(response); s_responseQueueMutex.unlock(); //放到主线程去执行 回调 //每次处理一个 通知一次回调 注意不是每帧调用 或定时调用 只是这一次 if (nullptr != s_pHttpClient) { scheduler->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); } } //说明 全部处理完了 清理一下 s_requestQueueMutex.lock(); s_requestQueue->clear(); s_requestQueueMutex.unlock(); //不为空 就删除队列 if (s_requestQueue != nullptr) { delete s_requestQueue; s_requestQueue = nullptr; delete s_responseQueue; s_responseQueue = nullptr; } } //单独处理 一个请求 void HttpClient::networkThreadAlone(HttpRequest* request) { // Create a HttpResponse object, the default setting is http access failed HttpResponse *response = new (std::nothrow) HttpResponse(request); char errorBuffer[CURL_ERROR_SIZE] = { 0 }; //处理完了 之后 processResponse(response, errorBuffer); //注册回调 auto scheduler = Director::getInstance()->getScheduler(); //单独注册 scheduler->performFunctionInCocosThread([response, request]{ const ccHttpRequestCallback& callback = request->getCallback(); Ref* pTarget = request->getTarget(); SEL_HttpResponse pSelector = request->getSelector(); //这几种回调 看有哪一个 if (callback != nullptr) { callback(s_pHttpClient, response); } else if (pTarget && pSelector) { (pTarget->*pSelector)(s_pHttpClient, response); } //手动释放 response->release(); // do not release in other thread request->release(); }); }

    可以看到对待每一个request,都对其执行 processResponse

    //传入 Request封装成的Response 并传入errorBuffer // Process Response static void processResponse(HttpResponse* response, char* errorBuffer) { auto request = response->getHttpRequest(); long responseCode = -1; int retValue = 0; // Process the request -> get response packet //根据类型进行处理 分别处理 switch (request->getRequestType()) { case HttpRequest::Type::GET: // HTTP GET retValue = processGetTask(request, writeData, response->getResponseData(), &responseCode, writeHeaderData, response->getResponseHeader(), errorBuffer); break; case HttpRequest::Type::POST: // HTTP POST retValue = processPostTask(request, writeData, response->getResponseData(), &responseCode, writeHeaderData, response->getResponseHeader(), errorBuffer); break; case HttpRequest::Type::PUT: retValue = processPutTask(request, writeData, response->getResponseData(), &responseCode, writeHeaderData, response->getResponseHeader(), errorBuffer); break; case HttpRequest::Type::DELETE: retValue = processDeleteTask(request, writeData, response->getResponseData(), &responseCode, writeHeaderData, response->getResponseHeader(), errorBuffer); break; default: CCASSERT(true, "CCHttpClient: unkown request type, only GET and POSt are supported"); break; } // write data to HttpResponse response->setResponseCode(responseCode); if (retValue != 0) { response->setSucceed(false); response->setErrorBuffer(errorBuffer); } else { response->setSucceed(true); } } //Process Get Request static int processGetTask(HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) { //整个 网络功能 都是对curl库的封装 CURLRaii curl; bool ok = curl.init(request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_FOLLOWLOCATION, true) && curl.perform(responseCode); return ok ? 0 : 1; } //Process POST Request static int processPostTask(HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) { CURLRaii curl; bool ok = curl.init(request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_POST, 1) && curl.setOption(CURLOPT_POSTFIELDS, request->getRequestData()) && curl.setOption(CURLOPT_POSTFIELDSIZE, request->getRequestDataSize()) && curl.perform(responseCode); return ok ? 0 : 1; } //Process PUT Request static int processPutTask(HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) { CURLRaii curl; bool ok = curl.init(request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_CUSTOMREQUEST, "PUT") && curl.setOption(CURLOPT_POSTFIELDS, request->getRequestData()) && curl.setOption(CURLOPT_POSTFIELDSIZE, request->getRequestDataSize()) && curl.perform(responseCode); return ok ? 0 : 1; } //Process DELETE Request static int processDeleteTask(HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) { CURLRaii curl; bool ok = curl.init(request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_CUSTOMREQUEST, "DELETE") && curl.setOption(CURLOPT_FOLLOWLOCATION, true) && curl.perform(responseCode); return ok ? 0 : 1; }

    CURLRaii的实现

    //主要是实现 一些参数的 简单传递 //以及使用RAII思想 将初始化与清理放在 构造析构之中 class CURLRaii { /// Instance of CURL CURL *_curl; /// Keeps custom header data curl_slist *_headers; public: CURLRaii() : _curl(curl_easy_init()) , _headers(nullptr) { } ~CURLRaii() { if (_curl) curl_easy_cleanup(_curl); /* free the linked list for header data */ if (_headers) curl_slist_free_all(_headers); } template <class T> bool setOption(CURLoption option, T data) { return CURLE_OK == curl_easy_setopt(_curl, option, data); } /** * @brief Inits CURL instance for common usage * @param request Null not allowed * @param callback Response write callback * @param stream Response write stream */ bool init(HttpRequest *request, write_callback callback, void *stream, write_callback headerCallback, void *headerStream, char *errorBuffer) { if (!_curl) return false; if (!configureCURL(_curl, errorBuffer)) return false; /* get custom header data (if set) */ std::vector<std::string> headers=request->getHeaders(); if(!headers.empty()) { /* append custom headers one by one */ for (std::vector<std::string>::iterator it = headers.begin(); it != headers.end(); ++it) _headers = curl_slist_append(_headers,it->c_str()); /* set custom headers for curl */ if (!setOption(CURLOPT_HTTPHEADER, _headers)) return false; } if (!s_cookieFilename.empty()) { if (!setOption(CURLOPT_COOKIEFILE, s_cookieFilename.c_str())) { return false; } if (!setOption(CURLOPT_COOKIEJAR, s_cookieFilename.c_str())) { return false; } } return setOption(CURLOPT_URL, request->getUrl()) && setOption(CURLOPT_WRITEFUNCTION, callback) && setOption(CURLOPT_WRITEDATA, stream) && setOption(CURLOPT_HEADERFUNCTION, headerCallback) && setOption(CURLOPT_HEADERDATA, headerStream); } /// @param responseCode Null not allowed bool perform(long *responseCode) { if (CURLE_OK != curl_easy_perform(_curl)) return false; CURLcode code = curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, responseCode); if (code != CURLE_OK || !(*responseCode >= 200 && *responseCode < 300)) { CCLOGERROR("Curl curl_easy_getinfo failed: %s", curl_easy_strerror(code)); return false; } // Get some mor data. return true; } };

    Send相关的实现

    //分配 两个队列 bool HttpClient::lazyInitThreadSemphore() { if (s_requestQueue != nullptr) { return true; } else { s_requestQueue = new (std::nothrow) Vector<HttpRequest*>(); s_responseQueue = new (std::nothrow) Vector<HttpResponse*>(); auto t = std::thread(CC_CALLBACK_0(HttpClient::networkThread, this)); t.detach(); } return true; } //send 为加入到发送队列 void HttpClient::send(HttpRequest* request) { if (false == lazyInitThreadSemphore()) { return; } if (!request) { return; } request->retain(); if (nullptr != s_requestQueue) { s_requestQueueMutex.lock(); s_requestQueue->pushBack(request); s_requestQueueMutex.unlock(); // 通知唤醒 s_SleepCondition.notify_one(); } } //立即发送 即为 重开一个线程 专门处理这个请求 void HttpClient::sendImmediate(HttpRequest* request) { if(!request) { return; } request->retain(); auto t = std::thread(&HttpClient::networkThreadAlone, this, request); t.detach(); }

    Callback的实现

    //每产生一个 response 都会调用到这里一次 void HttpClient::dispatchResponseCallbacks() { // log("CCHttpClient::dispatchResponseCallbacks is running"); //occurs when cocos thread fires but the network thread has already quited if (nullptr == s_responseQueue) { return; } HttpResponse* response = nullptr; s_responseQueueMutex.lock(); if (!s_responseQueue->empty()) { response = s_responseQueue->at(0); s_responseQueue->erase(0); } s_responseQueueMutex.unlock(); if (response) { HttpRequest *request = response->getHttpRequest(); const ccHttpRequestCallback& callback = request->getCallback(); Ref* pTarget = request->getTarget(); SEL_HttpResponse pSelector = request->getSelector(); //取出来 进行回调 if (callback != nullptr) { callback(this, response); } else if (pTarget && pSelector) { (pTarget->*pSelector)(this, response); } response->release(); // do not release in other thread request->release(); } }

    最后

    下一篇,分析 多分辨率支持 的实现。

    最新回复(0)