標題黨,真正題目應該是我是如何生成出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
不過幸虧在裝逼路上我找到了 Antlr、 PHP-CPP 再加上一個Antlr 3的PHP runtime,我操完美啊。
固然這條路仍是很是崎嶇的,畢竟我在最開始想的太美好了。好比至今沒找到能生成C++ PHP Parser的Antlr 語法描述文件。找到都是Java C++的。嘗試的改了一下發現。 No Zuo No Die啊。
後來發現不行啊,卡在AST這條路上過久了(雖然可使用其餘工具生成AST.xml而後PHP分析),便果斷退而求其次,來來Parser沒有,Lexer總有吧,找到一個C的Antlr語法描述文件。點擊生成Generate Lexer Code竟然真的生成了。而後我們就用起來唄。
剩下的都是好寫的。
static Php::Value pack<?php print $struct['name'];?>(Php::Parameters ¶ms) { 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 ¶ms) { 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加密....