2014年11月15日 星期六

僅使用vector container及Poco C++ Library達成多執行緒查詢

續上一篇http://zf-php-oo.blogspot.tw/2014/11/jsoncpp-poco-libraryapicombinejsonswigp.html ,其實也可以不用JsonCpp達到PHP一直call addUrl()方法的需求,C++裡的vector container也是可行。此外,本篇加入了如何代上cookie及user agent header資訊給Poco httprequest

multiThreadQuery.h:

#ifndef MULTITHREADQUERY_H
#define MULTITHREADQUERY_H
#endif

#include <vector>
#include <string>

using namespace std;

class multiThreadQuery{
public:
    explicit multiThreadQuery();

    ~multiThreadQuery();
    

    // 為配合SWIG不支援return reference,故於此return by pointer
    multiThreadQuery* clearAllUrls();

    multiThreadQuery* attachUrl(std::string url, std::string alias = "", std::string cookie = "", std::string userAgent = "");

    multiThreadQuery* fetchResponse(bool yesNo);

    std::string exe();
protected:
    int _threadNum;
    vector _urlContainer, _aliasContainer, _cookieContainer, _userAgentContainer, _responseContainer;
    bool _fetchResponse;

    void _curl(int i);

    vector _explode(std::string str, char delimiter);
};



multiThreadQuery.cpp:

#include "multiThreadQuery.h"
#include "/usr/include/Poco/Net/HTTPClientSession.h"
#include "/usr/include/Poco/Net/HTTPRequest.h"
#include "/usr/include/Poco/Net/HTTPResponse.h"
#include "/usr/include/Poco/StreamCopier.h"
#include "/usr/include/Poco/Path.h"
#include "/usr/include/Poco/URI.h"
#include "/usr/include/Poco/Exception.h"

#include <iostream>
#include <thread>
#include <cstdlib>
#include <sstream>
#include <utility>
#include <chrono>

using namespace std;
using namespace Poco;
using namespace Poco::Net;

multiThreadQuery::multiThreadQuery(void): _threadNum(0), _fetchResponse(false){
}

multiThreadQuery::~multiThreadQuery(void){
}

multiThreadQuery* multiThreadQuery::clearAllUrls(){
    _threadNum = 0;
    vector emptyContainer;
    _urlContainer = emptyContainer;
    _aliasContainer = emptyContainer;
    _cookieContainer = emptyContainer;
    _userAgentContainer = emptyContainer;
    _responseContainer = emptyContainer;

    return this;
}

multiThreadQuery* multiThreadQuery::attachUrl(std::string url, std::string alias, std::string cookie, std::string userAgent){
    _urlContainer.push_back(url);

    if(alias != ""){
        _aliasContainer.push_back(alias);
    }else{
        // time_point的time_since_epoch()才會包含小數點
        chrono::system_clock::time_point tp = chrono::system_clock::now();
        chrono::microseconds us = chrono::duration_cast(tp.time_since_epoch());
        time_t tt = us.count();

        stringstream strm;
        strm << tt;
        _aliasContainer.push_back(strm.str());
    }

    _cookieContainer.push_back(cookie);
    _userAgentContainer.push_back(userAgent);
    _responseContainer.push_back("");

    ++_threadNum;

    return this;
}

multiThreadQuery* multiThreadQuery::fetchResponse(bool yesNo){
    _fetchResponse = yesNo;

    return this;
}

std::string multiThreadQuery::exe(){
    int i;
    std::string json = "";
    // 動態設定array大小
    thread* threadArrPtr = new thread[_threadNum];

    for(i = 0; i < _threadNum; ++i){
        *(threadArrPtr + i) = thread(&multiThreadQuery::_curl, this, i);
    }

    if(_fetchResponse) json = "{";

    for(i = 0; i < _threadNum; ++i){
        // 不得放在產生thread的迴圈內,多執行緒會失效,變成跑一般迴圈
        (*(threadArrPtr + i)).join();
       
        if(_fetchResponse){
            json += _responseContainer[i];
            if(i != _threadNum - 1) json += ",";
        }
    }

    if(_fetchResponse) json += "}";

    delete [] threadArrPtr;
    threadArrPtr = nullptr;
   
    return json;
}

void multiThreadQuery::_curl(int i){
    try{
        URI uri(_urlContainer[i]);

        HTTPClientSession session(uri.getHost(), uri.getPort());

        // prepare path
        string path(uri.getPathAndQuery());
        if(path.empty()) path = "/";
       
        // send request
        HTTPRequest req(HTTPRequest::HTTP_GET, path, HTTPMessage::HTTP_1_1);

        if(_cookieContainer[i] != ""){
            NameValueCollection nvc;
            vector cookie = _explode(_cookieContainer[i], ';');

            for(vector::iterator it = cookie.begin(); it != cookie.end(); ++it){
                std::string cookieName = (*it).substr(0, (*it).find("="));
                std::string cookieValue = (*it).substr((*it).find("=") + 1);
                nvc.add(cookieName, cookieValue);
            }

            req.setCookies(nvc);
        }

        if(_userAgentContainer[i] != "") req.set("User-Agent", _userAgentContainer[i]);

        session.sendRequest(req);
       
        if(_fetchResponse){
            HTTPResponse res;
           
            // response 轉成 stream 再轉成字串
            istream &is = session.receiveResponse(res);
            std::string response;
            StreamCopier::copyToString(is, response);
           
            _responseContainer[i] = "\"" + _aliasContainer[i] + "\":" + response;
        }
    }catch(Poco::Exception &ex){
        _responseContainer[i] = "\"" + _aliasContainer[i] + "\":{\"s\":-5,\"desc\":\"Curl '" + _urlContainer[i] + "' failed.\"}";
    }
}

vector multiThreadQuery::_explode(std::string str, char delimiter){
    std::vector result;
    std::istringstream iss(str);

    for(std::string token; getline(iss, token, delimiter);){
        result.push_back(move(token));
    }

    return result;
}

int main(){
    const auto start = chrono::system_clock::now();

    multiThreadQuery mtq;
    std::string response;

    response = mtq.clearAllUrls()->fetchResponse(true)
                                    ->attachUrl("http://127.0.0.1/test.php", "test", "snm=54659c9a7decb:SMH2IG9QAVAEBFNYMXK3I3NNH6;snm_token=d7d4a58c3d3d69cb32bf74fa84e4f416;", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0")
                                    ->attachUrl("http://127.0.0.1/helloWorld.php", "hw", "snm=54659c9a7decb:SMH2IG9QAVAEBFNYMXK3I3NNH6;snm_token=d7d4a58c3d3d69cb32bf74fa84e4f416;", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0")
                                    ->exe();

    cout << response << endl;
   
    const auto end = chrono::system_clock::now();
    auto ts = end - start;
    cout << "spent: " << chrono::duration_cast(ts).count() << "us" << endl;

    return 0;
}


至於如何包成PHP的Extension檔,再自行參考前幾篇裡的說明囉~

沒有留言:

張貼留言