我是靠谱客的博主 积极白羊,最近开发中收集的这篇文章主要介绍 我是如何写出1W行C++代码的,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

标题党,真正题目应该是我是如何生成出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调用。

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

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

最后

以上就是积极白羊为你收集整理的 我是如何写出1W行C++代码的的全部内容,希望文章能够帮你解决 我是如何写出1W行C++代码的所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部