实习随笔

本文深入探讨游戏服务器开发的各个环节,包括代码版本管理、调试技巧、性能优化及常见问题解决策略。涵盖git操作流程、gdb调试命令、编译加速方法等实用技能,适合游戏开发者参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2-25

  1. 内网-外网不是互通的,只能从外网发送文件到内网
  2. 利用CRT连上服务器进行开发。25是开发服务器,21是运行服务器。
  3. 在内网上 Sz 和 rz 命令可以传送本机文件到服务器。

2-26

  1. git更新代码。因为本地和远端存在冲突(每一个 commit 是一个节点,连起来就是一个链,而本地进行rebase会改变链结构,把提交历史整理成一条线)。所以步骤是先fetch 同步到本地版本库,在本地rebase两个同名分支,再把处理好的分支提交到远端。这样做的目的是让远端的提交链形成一条线,便于观看。
  2. 用Revert 代替 reset, 是撤销某次提交,不影响其他提交点。Reset 破坏了提交的log, --hard 尽量在自己的分支上使用。
  3. Checkout 可以切换分支,也可以撤销工作区文件的修改。
  4. git stash
  5. 内网155 目录是 \10.68.64.10 , 外网是 \10.12.0.151
  6. 21 服务器 zx 在 sandbox/…zxbin 里。
    调试的步骤: gdb attach 进程号

2-27

linux C++ 源码跟踪

  1. 利用 grep -Iwnr "key" dir 查找所有包含关键字内容的文件

回归奖励流程梳理

return_award类的构造函数初始化了两个award类型的结构体,每个结构体包含的内容是奖励获得时间等信息。在player_imp 文件中,是创建了return_award对象,并且对其内部较为复杂的部分函数进行封装。
award type 有三个类型,无,7天 和30 天回归,由一个整形变量 cur_award_type 记录。并且 getCurAwardType 函数直接返回了这个状态变量。
checkDeliver 函数传入上次登录时间,判断是否应该发放回归
奖励。具体方法是通过 updateAwardState 函数改变cur_award_type变量的状态。
getAward 函数是具体的发放奖励函数。判断奖励类型,返回一个布尔类型的结果。结果是1,表示两种奖励发放成功。若为0,表示其中一种奖励发放失败了。例如在Get7DayReturnAward 函数中,可能出现3种错误发放的情况。

遗留问题:
不知道这个机制从哪里开始调用的
未找到starttime 等关键变量的作用

  1. debug 命令
    shift + ~

  2. archive 类?串行化

  3. vs 用于寻找变量定义位置, grep用于查看函数/变量存在的位置

2-28

award debug 命令制作

1988 命令的研究:
每个命令是一个switch case , 接受一个cmd_type,case到相应的命令号执行其内容。
定义一个gplayer_imp 类型的指针,指向_imp对象。这个类存储了人物的属性,例如金钱,物品等。
通过对象指针获取到人物的basic属性(等级),乘以100000得到一个整数。调用GainMoney 函数(现有金钱和整数相加,并 检查是否溢出)修改player_money。之后调用dispacher 调度类的 get_player_money 方法真正实现金钱增加的功能。
那么制作命令的思路就是,获取到人物对象句柄,调用特定的接口函数,就能实现debug命令制作。
这些接口函数和可用的人物属性都在player_imp.h 里面存放,在里面看注释找。

award 变量的存储过程

Save 函数接收一个base_wrapper对象,base_wrapper 是一个抽象类,需要继承它的类重写 << 运算符重载函数。
Load 函数则把 base_wrapper对象的内容 流出来,直接存到各个变量里(?).

rsync -r 172.16.10.5:/etc /tmp # 将远程主机的/etc目录拷贝到本地/tmp下,以保证本地/tmp目录和远程/etc保持同步

3-1

  1. git 目录一般用于控制代码,而不是二进制文件
  2. MD5sum gs 唯一性
  3. “rm -rf /xxxx 与 rm -rf /xxxx/ ”是不一样的东西,在某些平台下面 rm -rf /xxx/是删除软链接目录里的文件,而不是删除软链接,。。正确的删除方法就是不带最后的“/”。
  4. 密钥登陆原理。在本地生成一个钥匙对,然后把公钥发给服务器,由管理员把我的公钥放到一个文件里(authirzed_keys)。我则把私钥存放在本地(identity),需要登陆的时候拿我的私钥去连接服务器。
  5. openssh代理转发。远程主机A 连接主机B,想要免密登陆,就得把A的公钥 rsa.pub 放到主机 B的authrized_keys文件中。
  6. git push -u origin master 第一次, 之后直接 push

调试一个功能的流程

  1. 在25服务器改好代码
  2. 编译过程 在gs下 make clean make
  3. 25的代码 zxdist 改一个脚本 发送到 21 服务器
  4. 在 21 服务器zxbin执行 restart.sh,在gs下 restart_gs.sh 5
  5. 进入游戏 输入调试命令

遇到线提不起来的问题

一种可能数据不同步。 ./gs 查看错误信息或者查看core 信息,两者都有可能。 查看 gs/config/xx 的版本和t1版本的文件一致否。方法是 svn up ,svn info xx文件。因为svn是管理数据的。在zxdata 拉去新数据。
二种可能当前程序落后于版本。我在开发时别人已经更新了上线版本,需要更新代码到最新版,再重新编译重启。

另外startup.sh 里有手动启gs的方法。

3-4

探究制作一个属性组

跟踪6666命令,模仿Damage。
2015
insert 已经将数据放入set里了,为什么还需要runner ,写入wrapper。
1989
变量值是怎么获取的?
buf ,结构体 偏移 或者 指针强转。
string 和 string.h
map 插入
10806
防溢出:乘法利用溢出不可逆性,加法利用溢出变符号。

3-5

make 可以解析 .c 到 .o 的依赖关系,但是不知道 .h 对其他文件的影响。所以,在一个项目中,如果修改了 .h 文件,那么必须make clean 重新编译整个项目,如果只是修改了 .c 文件,只需要重新 make 就可以了 。

gdb 进程名 core文件, bt 查看函数栈,定位崩溃位置。

char* 转到结构体指针。需要用一个临时变量做中介。

struct mma{ int  id; }
mma* tmp = (mma*)buf;
tmp->id;

客户端再给服务器发送数据时,为了节省空间,往往会修改内存对齐。所以在服务端解析字节流到结构体的时候,要根据客户端的内存对齐方式,添加

#pragma pack(1)
#pragma pacd()
  • 很多时候解析的数据有问题,就是因为这个原因。

看源码,没头绪,就是搜!各种搜!

3-6

正式c2s命令需要在protocol 文件记录命令格式,然后再把buf 转为相应的结构指针进行操作。
version == 存取长度?
可扩展性,预留字段

Save 调用

Save 的调用源头是 user_save_data() 函数,在需要存储角色信息的时候被使用。
return_award 存储实体是 raw_wrapper 类的一个实例。raw 类主要重载了 << >> 运算符,利用 push_byte() 函数将数据存储在octets 类实例的对象中,push_back()传入起始地址和数据大小,reserve 一块新的地址空间,将数据memcpy到_data空间。之后从实例中拿出数据起始地址,赋值给vecdata data,这是高级封装的db。
userlogin : pImp->SaveReturnAwardData()
player.cpp: Save()
load 的调用源头是角色登陆时,和所有初始化动作一起加载

znet 目录结构

每一个子目录是一个整体,表示两个模块通信的接口。
例如:gdbclient 是 gs 和db 模块接口,并且gs 作为client (请求发起方)。

3-7

actobject, obj_interface 都是基础物体实现,player的操作不应该放在这么基础的地方。
重写代码,将人物属性写在player_imp ,函数实现写在 player.cpp.通过 gplayer_imp 指针传递人物句柄。

更新资料片

  1. 更新服务器的代码(git fetch, rebase, ./config, rebuild, make) 和数据。在zxdata ,数据svn co 下来,创建软连接指向他。
  2. 更新客户端,修改server文件
    ln -s test.log test2.log #创建f1的一个符号连接文件test2.log

3-8

服务器-client1-client2 ,同步问题。三种方法,客户端先行,服务器主导,折中。
Version 是保证一个复杂类存储时的可扩展性。小类,map,直接用 size() 标识存取大小就可以了。
不一定在 xx 类的函数实现必须在 xx.cpp ,在别的地方只要声明了类域,也可以访问类的私有变量。
提升编译速度, make -j8, 和机器的核数相匹配。
86997 测试物品

3-11

Save属性流程

在player_imp 定义属性,player.cpp 实现存储加载的逻辑,在userlogin 存储和加载。
rpcalls.xml 负责生成数据框架,只能使用预留字段。
db_if 定义有数据库结构, db_if.cpp 实现数据赋值操作。

  • grep 一个文件,找到和他有联系的makefile。 通过makefile 了解一个模块的牵制关系。

gdb调试

gdb attach 进程号
handle SIGPIPE nostop noprint
gdb b xx.cpp : xx
此时由于文件在25服务器,所以会显示文件不存在。因此在21上操作gdb,在25上看代码

octets 可扩展,不好解析。高位低字节。
s 单步遇到new 麻烦了。去查查吧

3 -12 3 - 13

使用物品的流程

单步跟踪物品使用,从OnUse 函数中 调用 filter 使用物品。其他工作都是获取物品位置,id,数量的工作。
player.cpp 20315 是物品使用入口。

item 文件的 OnUse函数,使用小黄丹调用了 item_potion.cpp 的 OnUse函数, 而使用测试物品调用了 item_reset.cpp 文件的 OnUse 函数。因为测试物品和小黄丹都继承了父类 item_body ,使用对象的引用可以实现多态调用。
测试物品的私有变量 _type 是策划从lua 脚本中设定好的,我们只需要获取到这个变量值,去switch case 相应的逻辑就好了。

exptypes 是物品结构定义的文件,一个新的物品需要和策划商量着定义好有哪些字段。这是一类物品结构的定义,根据其中某个变量值区分具体的某一个物品。
配置表会用到

其实数据是经由策划填到 element.data 文件中的,服务器启动时加载这个文件到内存,从中获取物品的数据信息,所以物品信息在代码中是找不到的。

3-14

赶工开题论文。

3-15

测试文档

3-18 - 3-25

佩章洗炼不掉级。ctags -R --c+±kinds=+px --fields=+iaS --extra=+q

功能完成以后

git diff 先检查检查
git fetch , rebase
到master上 git rebase -i 整合节点
push 到资料片master
jenkins 上开始编译,并查看编译结果

GS 总览

一座BIG 代码山,
自己摸索探根源。
抽丝剥茧 GDB,
架构细节全了然。

  1. World :每个大地图即为一个world,w_tag 标识了唯一性,调试中常会用到。Wrold除此之外还封装了地图初始化时要加载的配置,玩家map列表,基础消息传播方式,移动格子视野判断。
    Slice:地图格子对象。可看作一个双向链表结构。一个运动对象在两点之间的移动可以看作Rm当前Slice, Insert 目标Slice。
    Grid:封装了Slice,步长的类。作为更大的区域管理?

  2. world_manager:每个world有一个worldmanager,通过worldmanager可以让人物对象获取world的信息。比如pImp
    ->GetWorldManager->GetWroldTag. worldmanager里有生成NPC的入口,设置基础信息(世界tag,存盘点,视野,),加载地图,加载区域信息。
    global_world_manager:创建global消息句柄,从配置文件中加载Grid,npc分布,通路。

  3. gobject: 基础对象信息,id,尺寸,世界tag。
    gactive_obj:基础活物对象,性别,血量,状态
    gnpc:tid,主人。
    从上到下依次继承。
    gplayer:各种玩家状态 + 逻辑操作设置状态
    objmanager_base:

    1. 用模板技术,为传入的不同对象申请一块内存空间,按对象大小逐个头尾相连串成链表。
    2. 申请一个对象的流程就是将链表的header取出,并激活。按头取尾插的规则操作链表。用Scoped互斥锁保证线程安全。(RAII包装的范围锁,底层是自旋锁)

    objmanager 将相同类型的对象利用模板技术、迭代器,实现统一的接口调用,实现代码复用。心跳管理?

  4. gplayer_imp 类是管理玩家角色的类,背包,队伍等
    gplayer_dispather 定义了某个角色功能和客户端通信的方式
    controller msghandler
    gplayer 更为基础,不常修改❓
    安全锁:S2C协议

    服务器处理客户端命令:线程池循环接收消息队列里的数据,通过dispatcher,让msghandler 接收到客户端消息,并进入服务函数。(可能是session 类,虚函数)
    服务器发送命令给客户端:利用dispatcher(_runner), 在gplayer_dispatcher类里写S2C协议,给客户端发送约定好的数据结构(protocol.h 定义数据字段, protocol_imp 定义了数据传输先后)。

  5. vector< item > 管理背包
    item_content 是封装的缓冲区,用于存放物品可改变的属性(essence),第一步修改ess,第二步赋值构造ess并把content复制到data。而item_xxx 是继承自 itembody,是使用物品的逻辑所在。

  6. GDB::vecdata 是登陆时加载和存储要用的数据库结构

  7. 一个消息的漂泊?
    产生-----加入消息队列-----线程池不停的循环,取出队头的消息---------Send------Dispatch-----call_msg_handler,人物有player专属的handler----根据人物状态,再次 Dispatch。接受拒绝------ 真正根据消息类型 case handle消息 ,有可能case 不到就返回gobject 基类case。
    各种消息广播,通过一个仿函数对象收集target 指定区域内的obj

  8. Session 呢,是写好的读条/一个持续过程 框架。也是利用消息 | 定时机制处理的。AddSession,加入sessionList(vector)------------StartSession,取出队头session,执行其start,设置其start_tick--------- end() 或者onserve()会在时间结束时调用。

  9. 角色移动?
    建立移动session,设置目的地---------------------检查checkplayermove 主要是速度的检测,碰撞检测phaseContro,AABBTrace ❓ --------------广播自己的移动消息player_dispatcher, AutoBrodcast, 获取玩家所在地图,给地图上的每个玩家发消息-----------移动结束时发送停止协议

  10. filter
    例如小黄丹这种药品是属于某一种类的药品,这种药品都有一种共性是上一个buff,每2s回一次血量,但具体每一种丹药回复的血量可以设置。因此, 我们复用这个逻辑,写一个item_step_healing_poton 类,在策划配好大黄丹、小黄丹的数值后,写入element.data. 在初始化gs时,实例化所有物品放到map<id, item_body>,在用的时候去取背包里的这个物品对象(已经赋好值),接着通过OnUse多态调用(因为上一层还有药品大类)进入使用逻辑。
    filter 框架先执行Attach 的逻辑,之后利用心跳机制?,隔段时间执行On逻辑,完成buff。filter_man 用map, list管理filter。OperationAux 是内部辅助类,配合FUNC_DEFINE 宏,定义各种逻辑相同、名称有规律改变的函数。
    心跳机制在gmatrix 里被统一调用,根源是线程池执行了World_Tick_Task.

  11. 一个线程不断执行task_queue 的任务,若队列为空则沉睡1s,并且是线程安全的。一个gs x 是多线程的。TaskQueue封装了带优先级的双端队列,每个task有Run的虚函数。

  12. gs::octets 比share库的要简单,只是提供一块可插入删除的字节流。packet_wrapper 只是封装了octets。Marshal 类要往网络发送字节流,因此经过字节序转变和压缩。
    protocol.h 定义协议号,给客户端发送的就是大写协议号,一般用同名小写定义协议结构,protocol_imp文件里实现协议字段打包。

  13. NPC生成,看稿纸。

  14. 内存池看图?

Share 库总览

  1. Octets cap是容量,len是有效长度。这个类在初始化创建时会new一块size大小的空间,用Rep构造函数初始化,返回的data指针则是一个Rep大小之后的地址。在这里Rep内部类起到一个管理分配空间的作用。利用引用计数进行管理,利用创建对象、复制构造、赋值操作符都会增加计数,release只做–计数操作,若为0真正回收空间。

  2. Marshal compact压缩比较粗暴,分三类char,short,int大小,直接取低位有效字节,往字节流里加入字节,并加入分隔符0xe0。在往网络发送数据之前会利用swap汇编指令转换字节序。 根据三个pos可以实现传输回滚。

  3. 和gs的有什么关系??❓

  4. Mutex 是利用pthread_mutex_t 实现的自旋锁。用Scoped 做了RAII包装 。❓

  5. PollIO 支持多种IO复用接口,epoll、select、poll、kevent。封装了统一的Poll、Trigger、LoadEvent 。

  6. Protocol 类是通信协议的定义,需要完成服务器间通信,会话管理。NetSession 是对一个连接的管理,具有缓冲区+fd。 Session 加入了State。Manager 管理Session。
    协议发送流程:WRAPPER结构封装了协议号和内容,Send,此时利用NetSession进行管理-----------Encode(使用Marshal类序列化成字节流),其实就是利用重载的流运算符倒一下。-----------将准备发送数据放至deque buffer------设置PollOut事件,一般是用的POLL,触发后直接执行PollOut,OnSend()将deque buffer 转至NetSession buffer,之后发送到内核buff和网卡。
    接受流程:内核通知PollIn – 数据读到session buffer – decode —Dispatch

  7. Rpc 操纵远程主机的通信协议,封装了复杂的通信细节。利用Rpc工具生成两端存根函数,保证两边参数列表完全一致,这样在调用方调用函数,接收方就能在主循环里Polling,从套接字读取到数据,解析数据执行对应的函数。
    Rpc 类在protocol 之上,提供带回应的请求服务❓

接一个需求

规划,看是否有实现可能。
从工具链角度解决难题,而不是加if else。
扯皮。

增加cmd网页文档

export zxadmin/trunk 拉下文件
修改cmd文件
在21 var/www/html/zxadmin 下svn up.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值