3

标题党,真正题目应该是我是如何生成出1W行C++代码的。

最近使用swoole开发一个斗地主服务端的代理层,任务不难,排除几个swoole的 segment fault(注1) 都好说。通俗点说就是将socket转变成websocket。这个很简单,关键的是也不知道哪个混蛋在最初的时候不使用浏览器的 typed array 去解析协议而是想到了将协议 struct 转变成 json 给客户端读(注2) 。这个就蛋疼了。

浩大的工程量开始了:

每一个文件都是一条协议

我的天哪

当然幸好95%都不是我写的。不过剩下的5%也不是人能承受的。

虽然我写了一个PHP的c struct 分析器使得以下这样变为了可能。

$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++ 就显得很无能了。

//在使用中的(时效)道具
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只是控制了溢出跟顺序,里面的内容它并不控制了。

这让我非常的愤慨,既然这样我只能拿出大杀器了。

装逼

具体的装逼思路是这样的:找一个 C++ 语法分析器,解析出AST,遍历一下生成C++的代码(因为协议文件是C++的,为了能利用只好是C++的了),然后再包装成PHP扩展,最后给PHP调用。

我操,这么崎岖的装逼路线已经超越了我的能力范畴了。

不过幸好在装逼路上我找到了 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加密....


qpwoeiru96
562 声望38 粉丝

吴子棋