标题党,真正题目应该是我是如何生成出1W行C++代码的。
最近使用swoole开发一个斗地主服务端的代理层,任务不难,排除几个swoole的 segment fault(注1) 都好说。通俗点说就是将socket转变成websocket。这个很简单,关键的是也不知道哪个混蛋在最初的时候不使用浏览器的 typed array 去解析协议而是想到了将协议 struct 转变成 json 给客户端读(注2) 。这个就蛋疼了。
浩大的工程量开始了:
当然幸好95%都不是我写的。不过剩下的5%也不是人能承受的。
虽然我写了一个PHP的c struct 分析器使得以下这样变为了可能。
$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>(bistream& bis,RespUsingPropList& rhs) { rhs.reset(); bis >> rhs.askid; bis >> rhs.num; for(int i=0;i > rhs.proptype[i]; bis >> rhs.timeEnd[i]; } return bis; } };
对的,他使用的不是 memcpy,使用的是运算符的重载。struct只是控制了溢出跟顺序,里面的内容它并不控制了。
这让我非常的愤慨,既然这样我只能拿出大杀器了。
具体的装逼思路是这样的:找一个 C++ 语法分析器,解析出AST,遍历一下生成C++的代码(因为协议文件是C++的,为了能利用只好是C++的了),然后再包装成PHP扩展,最后给PHP调用。
我操,这么崎岖的装逼路线已经超越了我的能力范畴了。
不过幸好在装逼路上我找到了 、 再加上一个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::Parameters ¶ms) { try { Php::Value tmp; Php::Value arr = params[0]; obj; obj.reset(); obj. = (int)arr.get(" "); obj. = (int)arr.get(" ");
最终生成的代码是这样的,非常简单是不是啊,毕竟引入了原来的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++文件。哇哈哈哈哈哈哈。
总结:合理利用工具,你将在装逼的路上越走越远。
顺便无耻的回答了下 无耻的人的问题:
注1:
确实是swoole的问题,因为将代码写法从var func = function(){ blabla... setTimeout(func, 2000);};
改成
var func = function(){ setInterval(function(){ blabla... }, 2000);};
都能提升服务稳定性。
注2:
额 当然也是有好处的 gbk转换成unicode 对于前端来说还是需要码表的 这个放在移动端就不好了...还有客服端跟服务端做了AES加密....