标题党,真正题目应该是我是如何生成出1W行C++代码的。
最近使用swoole开发一个斗地主服务端的代理层,任务不难,排除几个swoole的 segment fault(注1) 都好说。通俗点说就是将socket转变成websocket。这个很简单,关键的是也不知道哪个混蛋在最初的时候不使用浏览器的 typed array 去解析协议而是想到了将协议 struct 转变成 json 给客户端读(注2) 。这个就蛋疼了。
浩大的工程量开始了:
当然幸好95%都不是我写的。不过剩下的5%也不是人能承受的。
虽然我写了一个PHP的c struct 分析器使得以下这样变为了可能。
1
2
3
4
5
6
7
8
9$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++ 就显得很无能了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34//在使用中的(时效)道具 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调用。
我操,这么崎岖的装逼路线已经超越了我的能力范畴了。
不过幸好在装逼路上我找到了 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居然真的生成了。然后咱们就用起来呗。
剩下的都是好写的。
1
2
3
4
5
6
7
8
9
10
11
12static 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封装了好多东西。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68static 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); } }
然后开心的执行一下:
1
2make clean && make && sudo mv ddz_protocol.so /usr/lib/php5/20131226/
不对再返回回去修修改改,将他放到正式环境。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55$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的问题,因为将代码写法从
1
2
3
4var func = function(){ blabla... setTimeout(func, 2000); };
改成
1
2
3
4
5var func = function(){ setInterval(function(){ blabla... }, 2000); };
都能提升服务稳定性。
注2:
额 当然也是有好处的 gbk转换成unicode 对于前端来说还是需要码表的 这个放在移动端就不好了...
还有客服端跟服务端做了AES加密....
最后
以上就是积极白羊最近收集整理的关于 我是如何写出1W行C++代码的的全部内容,更多相关内容请搜索靠谱客的其他文章。
发表评论 取消回复