我是如何寫出1W行C++代碼的

標題黨,真正題目應該是我是如何生成出1W行C++代碼的。php

最近使用swoole開發一個鬥地主服務端的代理層,任務不難,排除幾個swoole的 segment fault(注1) 都好說。通俗點說就是將socket轉變成websocket。這個很簡單,關鍵的是也不知道哪一個混蛋在最初的時候不使用瀏覽器的 typed array 去解析協議而是想到了將協議 struct 轉變成 json 給客戶端讀(注2) 。這個就蛋疼了。前端

浩大的工程量開始了:ios

每個文件都是一條協議

個人天哪

固然幸虧95%都不是我寫的。不過剩下的5%也不是人能承受的。web

雖然我寫了一個PHP的c struct 分析器使得如下這樣變爲了可能。json

$struct = <<<EOT
    int askid;
    BYTE flag;
    char bversion[16];
    char pversion[16];
    char uversion[16];
    //string errmsg[32+1];
EOT;

$tmp = PHPStruct::load($struct)->encode(false)->unpack($body);

可是這只是很簡單的分析,對於變態的 C++ 就顯得很無能了。segmentfault

//在使用中的(時效)道具
struct RespUsingPropList
{
    enum { XY_ID = CMDT_RESPUSINGPROPLIST };
    
    int askid;
    int num;
    int proptype[MAX_PROP_NUM];//時效類type
    int timeEnd[MAX_PROP_NUM];
    
    void reset() { memset(this, 0, sizeof(*this)); }
    RespUsingPropList() { reset(); }
    friend bostream& operator<<(bostream& bos,const RespUsingPropList& rhs)
    {
        bos << rhs.askid;
        bos << rhs.num;
        for(int i=0;i<rhs.num;++i)
        {
            bos << rhs.proptype[i];
            bos << rhs.timeEnd[i];
        }
        return bos;
    }
    friend bistream& operator>>(bistream& bis,RespUsingPropList& rhs)
    {
        rhs.reset();
        bis >> rhs.askid;
        bis >> rhs.num;
        for(int i=0;i<rhs.num;++i)
        {
            bis >> rhs.proptype[i];
            bis >> rhs.timeEnd[i];
        }
        return bis;
    }
    
};

對的,他使用的不是 memcpy,使用的是運算符的重載。struct只是控制了溢出跟順序,裏面的內容它並不控制了。瀏覽器

這讓我很是的憤慨,既然這樣我只能拿出大殺器了。websocket

裝逼

具體的裝逼思路是這樣的:找一個 C++ 語法分析器,解析出AST,遍歷一下生成C++的代碼(由於協議文件是C++的,爲了能利用只好是C++的了),而後再包裝成PHP擴展,最後給PHP調用。swoole

我操,這麼崎嶇的裝逼路線已經超越了個人能力範疇了。session

不過幸虧在裝逼路上我找到了 AntlrPHP-CPP 再加上一個Antlr 3的PHP runtime,我操完美啊。

固然這條路仍是很是崎嶇的,畢竟我在最開始想的太美好了。好比至今沒找到能生成C++ PHP Parser的Antlr 語法描述文件。找到都是Java C++的。嘗試的改了一下發現。 No Zuo No Die啊。

struct定義的語法規則

後來發現不行啊,卡在AST這條路上過久了(雖然可使用其餘工具生成AST.xml而後PHP分析),便果斷退而求其次,來來Parser沒有,Lexer總有吧,找到一個C的Antlr語法描述文件。點擊生成Generate Lexer Code竟然真的生成了。而後我們就用起來唄。

使用CLexer

剩下的都是好寫的。

static Php::Value pack<?php print $struct['name'];?>(Php::Parameters &params)
    {
        try {
            Php::Value tmp;
            Php::Value arr = params[0];
            <?php print ($namespace ? $namespace . '::' : '');?><?php print $struct['name'];?> obj;
            obj.reset();

    <?php foreach($struct['list'] as $val): ?>

    <?php if(in_array($val['type'], ['int', 'char']) && !isset($val['number'])) : ?>
            obj.<?php print $val['name']; ?> = (int)arr.get("<?php print $val['name']; ?>");
    <?php elseif(in_array($val['type'], ['int']) && isset($val['number'])) : ?>
            obj.<?php print $val['name']; ?> = (int)arr.get("<?php print $val['name']; ?>");

最終生成的代碼是這樣的,很是簡單是否是啊,畢竟引入了原來的Struct文件跟PHP-CPP封裝了好多東西。

static Php::Value packPlayerConnect(Php::Parameters &params)
    {
        try {
            Php::Value tmp;
            Php::Value arr = params[0];
            Protocol::V10::ToolMobile::PlayerConnect obj;
            obj.reset();

    
                obj.askid = (int)arr.get("askid");
        
                tmp = arr.get("userid");
            if(!tmp.isString()) {
                throw Php::Exception("userid is not a string");
            } else {
                memcpy(obj.userid, (const char *)tmp, tmp.length() >= (Protocol::V10::ToolMobile::MAX_USERID+1) ? (Protocol::V10::ToolMobile::MAX_USERID+1) : tmp.length());
            }
        
                obj.numid = (int)arr.get("numid");
        
                            tmp = arr.get("sessionid");
            if(!tmp.isString()) {
                throw Php::Exception("sessionid is not a string");
            } else {
                memcpy(obj.sessionid, (const char *)tmp, tmp.length() >= (16) ? (16) : tmp.length());
            }
        
                obj.logintype = (int)arr.get("logintype");
        
                obj.gameid = (int)arr.get("gameid");
        
                tmp = arr.get("passwd");
            if(!tmp.isString()) {
                throw Php::Exception("passwd is not a string");
            } else {
                memcpy(obj.passwd, (const char *)tmp, tmp.length() >= (Protocol::V10::ToolMobile::MAX_PWD+1) ? (Protocol::V10::ToolMobile::MAX_PWD+1) : tmp.length());
            }
        
                tmp = arr.get("devid");
            if(!tmp.isString()) {
                throw Php::Exception("devid is not a string");
            } else {
                memcpy(obj.devid, (const char *)tmp, tmp.length() >= (Protocol::V10::ToolMobile::MAX_DEVID+1) ? (Protocol::V10::ToolMobile::MAX_DEVID+1) : tmp.length());
            }
        
                tmp = arr.get("nickname");
            if(!tmp.isString()) {
                throw Php::Exception("nickname is not a string");
            } else {
                memcpy(obj.nickname, (const char *)tmp, tmp.length() >= (Protocol::V10::ToolMobile::MAX_NICKNAME+1) ? (Protocol::V10::ToolMobile::MAX_NICKNAME+1) : tmp.length());
            }
        
                obj.clienttype = (int)arr.get("clienttype");
        
                obj.osver = (int)arr.get("osver");
        
                obj.ip = (int)arr.get("ip");
        
                obj.channelid = (int)arr.get("channelid");
        
                obj.version = (int)arr.get("version");
        
                obj.devtype = (unsigned char)(int)arr.get("devtype");
        
                obj.areaid = (int)arr.get("areaid");
        
                tmp = arr.get("token");
            if(!tmp.isString()) {
                throw Php::Exception("token is not a string");
            } else {
                memcpy(obj.token, (const char *)tmp, tmp.length() >= (Protocol::V10::ToolMobile::MAX_TOKEN+1) ? (Protocol::V10::ToolMobile::MAX_TOKEN+1) : tmp.length());
            }
        
                obj.loginflag = (int)arr.get("loginflag");
        
            char buffer[Protocol::PROTOCOL_MAXSIZE];
            bostream bos;
            bos.attach(buffer, sizeof(obj));
            bos << obj;
        
            Php::Value str(buffer, (int)bos.length());
            return str;
        } catch(biosexception e) {
            char error[32];
            sprintf(error, "exception: %d", e.m_cause);
            throw Php::Exception(error);
        }
    }

而後開心的執行一下:

make clean && make  && sudo mv ddz_protocol.so /usr/lib/php5/20131226/

不對再返回回去修修改改,將他放到正式環境。

$data = \DDZProtocol::packPlayerConnect([
            'askid' => 0,
            'userid' => $userid,
            'numid' => 0,
            'sessionid' => '',
            'logintype' => $logintype,
            'gameid' => $this->gameId,
            'passwd' => $passwd,
            'devid' => '',
            'nickname' => '',
            'clienttype' => 2,
            'osver' => 10000,
            'ip' => $ip,
            'channelid' => 10001,
            'version' => 10104,
            'devtype' => 0,
            'areaid' => 0,
            'token' => $token
        ]);
        var_dump(base64_encode($data));

//        $data  = pack('i', 0);// 1. askid
//        $data .= $this->packStr($userid);// 2. userid
//        $data .= pack('i', 0);// 3. numid
//        $data .= $this->packStr('');// 4. sessionid
//        $data .= pack('i', $logintype);// 5. logintype
//        $data .= pack('i', $this->gameId);// 6. gameid
//        $data .= $this->packStr($passwd);// 7. passwd
//        $data .= $this->packStr('');// 8. devid
//        $data .= $this->packStr('');// 9. nickname
//        $data .= pack('i', 2);// 10. clienttype
//        $data .= pack('i', 10000);// 11. osver 操做系統版本號
//        $data .= pack('i', (int)$ip);// 12. ip
//        $data .= pack('i', 10001);// 13. channelid
//        $data .= pack('i', 10104);// 14. version
//        $data .= pack('C', 0);// 15. devtype
//        $data .= pack('i', 0); // 16. areaid
//        $data .= $this->packStr($token);// 17. token

玩一下鬥地主,竟然成功了,頓時以爲世界很是的美好。若是我將所有代碼生成我操,那將是我第一個1W行代碼的C++文件。哇哈哈哈哈哈哈。

總結:合理利用工具,你將在裝逼的路上越走越遠。

順便無恥的回答了下 無恥的人的問題: 使用ANTLR對C++代碼進行語法分析並生成抽象語法樹

注1:
確實是swoole的問題,由於將代碼寫法從

var func = function(){
    blabla...
    setTimeout(func, 2000);
};

改爲

var func = function(){
    setInterval(function(){
        blabla...
    }, 2000);
};

都能提高服務穩定性。

注2:額 固然也是有好處的 gbk轉換成unicode 對於前端來講仍是須要碼錶的 這個放在移動端就很差了...還有客服端跟服務端作了AES加密....

相關文章
相關標籤/搜索