├── README.md ├── article ├── AI实现思考.md ├── C++游戏后台异步架构与Lua协程结合探究.md ├── LuaTinker VS tolua++.md ├── gdb调试coredump.md ├── hold住你的后台程序.md ├── mysql代理server实现总结.md ├── protobuf协议设计.md ├── protobuf懒读取.md ├── tcp系统调用总结.md ├── tolua++实现分析.md ├── ucontext簇函数学习.md ├── 《linux_programming_interface》读书笔记.md ├── 《深入探索C++对象模型》读书笔记.md ├── 为互联网应用程序而生的State Threads[常见后台架构浅析].md ├── 内存对象池一对多关系处理.md ├── 单进程异步模型与多进程同步模型对比.md ├── 后台常用系统调用性能总结.md ├── 手游服务器多版本管理.md ├── 服务器后台进程的启动和退出.md ├── 服务器程序发布的一种方法.md ├── 游戏物品掉落分析.md ├── 玩家多终端登录踢下线小结.md └── 移动网络下的动作格斗类PVP游戏网络同步方案.md └── img ├── PlayFrame_heaptimer.png ├── PlayFrame游戏架构.png ├── PlayeFrame_lua_coroutine_async.png ├── TankGameLua.jpg ├── TankGameLua.png ├── ai_behaviac.png ├── ai_impl.png ├── cocos2d_x_axes.jpg ├── cocos2d_x_ccaction.jpg ├── cocos2d_x_ccactioninstant.jpg ├── cocos2d_x_ccactioninterval.jpg ├── cocos2d_x_ccdirector.jpg ├── cocos2d_x_ccfinitetimeaction.jpg ├── cocos2d_x_cclayer.jpg ├── cocos2d_x_ccnode.jpg ├── cocos2d_x_ccnode_event.jpg ├── cocos2d_x_ccscene.jpg ├── cocos2d_x_ccsprite.jpg ├── cocos2d_x_ccsprite_attr.jpg ├── cocos2d_x_inner_layer.jpg ├── game_item_drops_wow.jpg ├── hold住你的后台程序_disk_write_read.jpg ├── hold住你的后台程序_memory_write.jpg ├── hold住你的后台程序_open_close.jpg ├── hold住你的后台程序_time.jpg ├── hold住你的后台程序_time_vdso.jpg ├── hold住你的后台程序_磁盘读写背后.jpg ├── hold住你的后台程序_网络相关系统调用.jpg ├── lpi_10_1.png ├── lpi_10_2.png ├── lpi_12_1.png ├── lpi_13_1.png ├── lpi_13_2.png ├── lpi_13_3.png ├── lpi_14_1.png ├── lpi_14_2.png ├── lpi_14_3.png ├── lpi_14_4.png ├── lpi_14_5.png ├── lpi_14_6.png ├── lpi_19_1.png ├── lpi_19_2.png ├── lpi_23_1.png ├── lpi_24_1.png ├── lpi_25_1.png ├── lpi_25_2.png ├── lpi_34_1.png ├── lpi_34_2.png ├── lpi_41_1.png ├── lpi_41_2.png ├── lpi_41_3.png ├── lpi_4_1.png ├── lpi_4_2.png ├── lpi_4_3.png ├── lpi_51_1.png ├── lpi_52_1.png ├── lpi_53_1.png ├── lpi_54_1.png ├── lpi_55_1.png ├── lpi_56_1.png ├── lpi_59_1.png ├── lpi_5_1.png ├── lpi_5_2.png ├── lpi_63_1.png ├── lpi_6_1.png ├── lpi_6_2.png ├── lpi_6_3.png ├── lua_async_task_lua_co_model.png ├── lua_async_task_sync_model.png ├── lua_async_task_task_model.png ├── lua_tinker_cpp_impl.jpg ├── markdown_basics.png ├── mem_hash_arch.png ├── mobile_game_multi_version_mgr.jpg ├── mysql_proxy_server_2thread.jpg ├── mysql_proxy_server_mutithread.jpg ├── mysql_proxy_server_mysqlmgr.jpg ├── network_delay.png ├── network_pkg_loss.png ├── network_pvp.png ├── one_obj_has_many_other_obj.jpg ├── perf_graph.png ├── protobuf懒读取_proxy.jpg ├── protobuf懒读取_测试结果.jpg ├── tcp_system_call_send.jpg ├── tolua_class_relation.png ├── tolua_register_table.png ├── toluapp_impl.jpg ├── ucontext簇函数学习_实际使用.png ├── 为互联网应用程序而生的State_Threads_fig.gif ├── 单一继承的一般继承.png ├── 复制构造函数.png ├── 多重继承.png ├── 构造函数.png ├── 玩家多终端登录踢下线小结.png ├── 重复继承.png └── 钻石型多重继承.png /README.md: -------------------------------------------------------------------------------- 1 | Fergus Blog 2 | ==== 3 | 4 | My Blog & Thanks Github! 5 | 6 | - [移动网络下的动作格斗类PVP游戏网络同步方案](https://github.com/zfengzhen/Blog/blob/master/article/移动网络下的动作格斗类PVP游戏网络同步方案.md) 7 | - [AI实现思考](https://github.com/zfengzhen/Blog/blob/master/article/AI实现思考.md) 8 | - [ucontext簇函数学习](https://github.com/zfengzhen/Blog/blob/master/article/ucontext簇函数学习.md) 9 | - [Protobuf懒读取](https://github.com/zfengzhen/Blog/blob/master/article/protobuf%E6%87%92%E8%AF%BB%E5%8F%96.md) 10 | - [玩家多终端登录踢下线小结](https://github.com/zfengzhen/Blog/blob/master/article/玩家多终端登录踢下线小结.md) 11 | - [后台常用系统调用性能总结](https://github.com/zfengzhen/Blog/blob/master/article/后台常用系统调用性能总结.md) 12 | - [Protobuf协议设计](https://github.com/zfengzhen/Blog/blob/master/article/protobuf%E5%8D%8F%E8%AE%AE%E8%AE%BE%E8%AE%A1.md) 13 | - [《Linux Programming Interface》读书笔记](https://github.com/zfengzhen/Blog/blob/master/article/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md) 14 | - [tolua++实现分析](https://github.com/zfengzhen/Blog/blob/master/article/tolua%2B%2B%E5%AE%9E%E7%8E%B0%E5%88%86%E6%9E%90.md) 15 | - [tcp系统调用总结](https://github.com/zfengzhen/Blog/blob/master/article/tcp%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E6%80%BB%E7%BB%93.md) 16 | - [单进程异步模型与多进程同步模型对比](https://github.com/zfengzhen/Blog/blob/master/article/%E5%8D%95%E8%BF%9B%E7%A8%8B%E5%BC%82%E6%AD%A5%E6%A8%A1%E5%9E%8B%E4%B8%8E%E5%A4%9A%E8%BF%9B%E7%A8%8B%E5%90%8C%E6%AD%A5%E6%A8%A1%E5%9E%8B%E5%AF%B9%E6%AF%94.md) 17 | - [手游服务器多版本管理](https://github.com/zfengzhen/Blog/blob/master/article/%E6%89%8B%E6%B8%B8%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%A4%9A%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86.md) 18 | - [服务器程序发布的一种方法](https://github.com/zfengzhen/Blog/blob/master/article/%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%A8%8B%E5%BA%8F%E5%8F%91%E5%B8%83%E7%9A%84%E4%B8%80%E7%A7%8D%E6%96%B9%E6%B3%95.md) 19 | - [gdb调试coredump](https://github.com/zfengzhen/Blog/blob/master/article/gdb%E8%B0%83%E8%AF%95coredump.md) 20 | - [服务器后台进程的启动和退出](https://github.com/zfengzhen/Blog/blob/master/article/%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%90%8E%E5%8F%B0%E8%BF%9B%E7%A8%8B%E7%9A%84%E5%90%AF%E5%8A%A8%E5%92%8C%E9%80%80%E5%87%BA.md) 21 | - [游戏物品掉落分析](https://github.com/zfengzhen/Blog/blob/master/article/%E6%B8%B8%E6%88%8F%E7%89%A9%E5%93%81%E6%8E%89%E8%90%BD%E5%88%86%E6%9E%90.md) 22 | - [内存对象池一对多关系处理](https://github.com/zfengzhen/Blog/blob/master/article/%E5%86%85%E5%AD%98%E5%AF%B9%E8%B1%A1%E6%B1%A0%E4%B8%80%E5%AF%B9%E5%A4%9A%E5%85%B3%E7%B3%BB%E5%A4%84%E7%90%86.md) 23 | - [mysql代理server实现总结](https://github.com/zfengzhen/Blog/blob/master/article/mysql%E4%BB%A3%E7%90%86server%E5%AE%9E%E7%8E%B0%E6%80%BB%E7%BB%93.md) 24 | - [LuaTinker VS tolua++](https://github.com/zfengzhen/Blog/blob/master/article/LuaTinker%20VS%20tolua%2B%2B.md) 25 | - [C++游戏后台异步架构与Lua协程结合探究](https://github.com/zfengzhen/Blog/blob/master/article/C%2B%2B%E6%B8%B8%E6%88%8F%E5%90%8E%E5%8F%B0%E5%BC%82%E6%AD%A5%E6%9E%B6%E6%9E%84%E4%B8%8ELua%E5%8D%8F%E7%A8%8B%E7%BB%93%E5%90%88%E6%8E%A2%E7%A9%B6.md) 26 | - [《深入探索C++对象模型》读书笔记](https://github.com/zfengzhen/Blog/blob/master/article/%E3%80%8A%E6%B7%B1%E5%85%A5%E6%8E%A2%E7%B4%A2C%2B%2B%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md) 27 | - [为互联网应用程序而生的State Threads[常见后台架构浅析]](https://github.com/zfengzhen/Blog/blob/master/article/%E4%B8%BA%E4%BA%92%E8%81%94%E7%BD%91%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E8%80%8C%E7%94%9F%E7%9A%84State%20Threads%5B%E5%B8%B8%E8%A7%81%E5%90%8E%E5%8F%B0%E6%9E%B6%E6%9E%84%E6%B5%85%E6%9E%90%5D.md) 28 | - [hold住你的后台程序](https://github.com/zfengzhen/Blog/blob/master/article/hold%E4%BD%8F%E4%BD%A0%E7%9A%84%E5%90%8E%E5%8F%B0%E7%A8%8B%E5%BA%8F.md) 29 | -------------------------------------------------------------------------------- /article/AI实现思考.md: -------------------------------------------------------------------------------- 1 | # AI实现思考 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | 项目中采用行为树[behaviac](https://github.com/TencentOpen/behaviac)+状态机的形态来做AI. 5 | ![image](https://github.com/zfengzhen/Blog/blob/master/img/ai_behaviac.png) 6 | 行为树主要做AI层面上的决策, 每次决策都会运行到行为树底层的Action节点. 7 | Action节点上的三种逻辑: 8 | - 执行逻辑函数, 比如Move() 9 | - 赋值操作, 比如target = xxxxx 10 | - 切换行为树(决策) 11 | 12 | 执行逻辑函数包括了状态机的切换, 状态机中不包含状态切换的代码, 状态切换通过行为树(决策)来进行, 状态机只包含状态的相关参数, 比如移动状态只包括移动相关的信息, 比如移动速度, 移动方向, 设置到外部的移动模块, 传递相应的参数, 移动模版通过tick或者定时器进行驱动. 13 | 14 | behaviac提供了AI编辑器, 也就是说程序只要把AI底层的原子逻辑封装好, 提供给behaviac, 通过behaviac编辑器进行拖动各种AI就可以导出相应的AI, 程序运行时加载相应的AI, 并挂载到相应的实体上, 就可以运行. 15 | 16 | 我们把上面的AI框架进行抽象: 17 | AI = **决策** + **动作** 18 | ![image](https://github.com/zfengzhen/Blog/blob/master/img/ai_impl.png) 19 | 决策就是逻辑判定, 动作就是AI逻辑的具体执行 20 | 上面我们决策这块采用的是behaviac行为树来实现, 我们也可以用其他方式来实现 21 | 根据决策是否需要返回running状态(也就是说决策逻辑是否需要挂起, 下一次tick进来的时候, 能直接在上次挂起的逻辑上跑)来看看有其他哪些实现方式 22 | 23 | ##### 需要返回running状态的情况需要**协程**的方式来实现 24 | - 1 lua以及lua协程 25 | - 2 c++通过swapcontext进行封装 26 | 27 | 1 lua以及lua协程, 需要向lua中注册相应的C++函数, 该方式比较好的就是能够支持热更新, 每个实体绑定一个lua协程 28 | 2 c++封装的协程比较好的就是不需要注册函数的过程, 不过协程栈大小的判定需要经验值, 我们尽量不在协程栈中分配变量, 通过实体指针的方式处理, 协程中只做逻辑 29 | 30 | #### 不需要running状态的情况 31 | 直接用c++的if else switch 进行决策逻辑处理, 简单直接, 不用running状态的情况就会每次决策都得从头到尾的跑一遍, 这样需要在决策逻辑中建立多级的if else的判定, 尽量少跑判定逻辑 32 | 33 | --- -------------------------------------------------------------------------------- /article/C++游戏后台异步架构与Lua协程结合探究.md: -------------------------------------------------------------------------------- 1 | # C++游戏后台异步架构与Lua协程结合探究 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | `性能、复杂的取舍。` 5 | `程序中一般分为底层事件处理以及业务逻辑处理两大块。` 6 | `对于业务逻辑这块,经常是变动较大,工作量较多,我们是否可以尝试在不太影响性能的情况下,降低其复杂度?` 7 | ###游戏后台中常见的一种架构### 8 | - 异步单进程架构 9 | - 性能较好,可读性差,线性逻辑代码被拆分为许多不同的部分存在不同的代码里,需要根据不同的回调点去串连起来 10 | - 常见的单进程异步代码处理 11 | - 请求 -> 保存session -> 异步逻辑(其他server服务) 12 | - 异步逻辑回包 -> 恢复session -> 本地逻辑 -> 保存session -> 异步逻辑(其他server服务) 13 | - session保存的两种方式: 14 | - 1、session较小,直接保存在网络包上,回包时获取 15 | - 2、session较大,创建session和id的map,将session保存在共享内存上,网络包上带上session id,回包时根据id获取session 16 | 17 | ###Task异步模型### 18 | ![Task异步模型](https://github.com/zfengzhen/Blog/blob/master/img/lua_async_task_task_model.png) 19 | 20 | - Task 21 | - 多个Step通过链表连接 22 | - 通过注册定时器完成超时处理,释放资源 23 | - 负责存储该业务的session数据,并提供接口给Step读取和修改 24 | - 需要提供以下接口 25 | - `on_task_timeout` 超时处理接口 26 | - `on_task_failed` 任务失败时处理接口 27 | - `on_task_successed` 任务成功完成时处理接口 28 | - `do_next_step` 当前Step完成时,跳转到下一个Step,提供给Step在Step成功时调用 29 | - `get_running_step` 获取当前所在的Step 30 | - Step 31 | - 封装异步操作,一般为SS处理以及数据库操作 32 | - 需要提供两个接口: 33 | - `perform`异步任务执行,执行完后回到逻辑主循环 34 | - `notify` 异步任务执行完后回调 35 | - `do_complete` 用于Step完成时判断,根据Step的成功失败调用Task的不同操作 36 | - TaskMgr 37 | - 通过map或者红黑树管理task_id和Task之间的映射,根据回包中的task_id查找到Task,获取到当前所在的Step,调用`Step::notify` 38 | - **优点**: 39 | - 性能较好 40 | - **缺点**: 41 | - 逻辑分布在各个Step的perform和notify中,难理解 42 | - 每个不同任务都得继承Task,实现上述接口;不同的步骤都得继承Step,实现上述接口;代码量较多 43 | 44 | ###同步模型### 45 | ![同步模型](https://github.com/zfengzhen/Blog/blob/master/img/lua_async_task_sync_model.png) 46 | 47 | - 模型 48 | - 采用顺序方式执行逻辑 49 | - 由于执行IO操作(包括磁盘、网络IO),需要大量时间去等待操作完成,需采用多进程,多线程方式去提高吞吐量 50 | 采用同步方式顺序执行逻辑。 51 | - 由于进程线程的开销,以及线程需要锁的开销,吞吐量比异步模式低不少 52 | - **优点**: 53 | - 编写容易,代码易读 54 | - **缺点**: 55 | - 吞吐量较低 56 | 57 | ###C++与lua异步协程模型### 58 | ![C++与lua异步协程模型](https://github.com/zfengzhen/Blog/blob/master/img/lua_async_task_lua_co_model.png) 59 | 60 | - AsyncTaskMgr 61 | - `init` 初始化时,读入所有的lua脚本,新建一个master_state 62 | - `register_func` 注册lua要用到的所有C函数 63 | - `create_task` 创建新的任务,通过lua_newthread,根据不同的任务类型,将对应的任务函数放入新建的协程中,并将该协程保存到TASK_TABLE table中 64 | - `resume` resume相应的任务 65 | - `close_task` 关闭相应的任务,通过`TASK_TABLE[task_id] = nil`将资源释放交个lua GC管理 66 | - 采用lua协程进行异步逻辑处理 67 | - 1、C++处理事件触发以及定时器触发 68 | - 2、根据触发事件找到相应的协程,继续执行 69 | - 3、在Lua协程中遇到异步逻辑处理时,通过coroutine。yield放弃CPU,恢复到主逻辑 70 | - **优点**: 71 | - 1、增加异步代码开发效率,所有逻辑按同步方式写入Lua脚本中 72 | - 2、对于策划易变的逻辑,Lua脚本也会较容易修改,并且能够实现热更新 73 | - 代码: 74 | - https://github.com/zfengzhen/lua_async_task -------------------------------------------------------------------------------- /article/LuaTinker VS tolua++.md: -------------------------------------------------------------------------------- 1 | # LuaTinker VS tolua++ 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | lua原生提供注册C函数的方式对C中的函数进行调用. 5 | C++面向对象的项目都需要一套lua OOD的封装. 6 | 如果直接使用注册C函数的方式的话, 对于面向对象的所有方法都得重写成C的形式, 工作量太大,还好目前有许多第三方库对lua的C++面向对象的调用进行封装, 主要有**LuaPlus**, **LuaBind**, **tolua++**, **LuaTinker**. 7 | 最近想在后台使用LuaTinker调用lua进行逻辑处理, 也学习了下quick-cocos2d-x, quick-cocos2d-x使用的时tolua++, 也阅读了下源码, 查看了下两者对调用C++面向对象的实现, 进行了下比较. 8 | 9 | --- 10 | 11 | ## LuaTinker 12 | 采用模板实现的小巧精悍的封装, 不到2000行代码, 基本的功能都有, 使用起来非常方便. 13 | 使用LuaTinker时, 类定义, 继承关系只能在C++中定义好, 并不能在Lua中定义类, 以及继承某个类, 也就是说类原型是在C++中声明好的, 只能传入Lua中, Lua进行调用, 如果想在Lua中创建新的类的话, LuaTinker是支持不了的, 所以在选择LuaTinker时, 要注意自己有没有这些需求. 14 | 其中源代码中有几个坑: 15 | `1 table的某个构造函数中没有增加table的引用, 在使用table的时候会遇到问题` 16 | `2 var_base作为基类, 没有添加虚析构函数` 17 | `3 class_def的时候不能添加静态成员函数, 如果时静态成员函数的时候需奥通过def去定义` 18 | `4 call函数调用时候不能传入引用` 19 | `5 class_con 构造函数只能注册一个, 并以最后一个注册的为准 (2014.5.22添加)` 20 | 修改下, 并注意下就好了, 毕竟这么小巧精悍的代码, 以后遇到问题也很快能够找出来. 21 | 22 | ### LuaTinker C++类实现 23 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lua_tinker_cpp_impl.jpg) 24 | 25 | --- 26 | 27 | ## tolua++ 28 | quick_cocos2d-x中使用的是tolua++ 29 | tolua++是通过修改头文件, 并保存为.pkg文件(quick-cocos2d-x是.tolua文件), 通过tolua++工具转换为lua可调用的cpp文件, 把这些cpp文件编译链接到你的app中, 并通过相应的tolua++ API进行初始化, 在lua脚本中就可以调用C++面向对象的东西了. 30 | tolua++实现比LuaTinker复杂很多, 它*`可以在Lua中进行类定义, 类继承的行为`*. 31 | 32 | ### tolua++实现 33 | ![](https://github.com/zfengzhen/Blog/blob/master/img/toluapp_impl.jpg) -------------------------------------------------------------------------------- /article/gdb调试coredump.md: -------------------------------------------------------------------------------- 1 | # gdb调试coredump 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ## coredump开启: 5 | 6 | ```shell 7 | ulimit -c unlimited 8 | ``` 9 | 10 | vim /etc/sysctl.conf 添加如下行: 11 | 12 | ```shell 13 | kernel.core_uses_pid = 1 14 | kernel.core_pattern = /tmp/core-%e-%s-%u-%g-%p-%t 15 | fs.suid_dumpable = 2 16 | ``` 17 | 18 | 可使用参数: 19 | %% – 符号% 20 | %p – 进程号 21 | %u – 进程用户id 22 | %g – 进程用户组id 23 | %s – 生成core文件时收到的信号 24 | %t – 生成core文件的 时间 (seconds since 0:00h, 1 Jan 1970) 25 | %h – 主机名 26 | %e – 程序文件名 27 | 28 | ``` 29 | sysctl -p 30 | ``` 31 | 不重启使得/etc/sysctl.conf生效 32 | 33 | ## gdb指令: 34 | bt [backtrace] 打印当前函数调用栈信息 35 | f[rame] n 切换当前栈, n为从0开始的整数 36 | info frame 显示当前栈信息 37 | info args 显示当前函数的参数名及其值 38 | info locals 显示当前函数所有局部变量及其值 39 | info catch 显示当前函数中的异常处理信息 40 | 41 | i[nfo] r[egister] ebp 打印ebp 42 | 43 | ```shell 44 | ebp 0x232f3e 0x834f2e1 45 | ``` 46 | 47 | x /4x [ebp] 打印ebp指向内容 48 | 49 | ```shell 50 | 0x232f3e: 0x121a2c 0x1de13c 0x000 0x000 51 | ``` 52 | 53 | info symbol 打印符号 54 | 55 | ```shell 56 | __strtoll_internal + 23 in section.text 57 | ``` 58 | 59 | ebp的调用信息通过链表组织, 第一个节点为指针, 通过将调用信息连接起来, 紧随气候的4个字节时该层的调用返回地址. 60 | 61 | SIGSEGV段错误(信号11), 访问非法内存, 地址越界, 指针为空 62 | SIGABRT检测异常(信号6), 调用abort()函数导致, 释放内存再次释放, 内存分配失败等 63 | SIGBUS总线错误(信号7), 总线地址不存在, 硬件故障 -------------------------------------------------------------------------------- /article/hold住你的后台程序.md: -------------------------------------------------------------------------------- 1 | # hold住你的后台程序 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ## 简介 5 | * hold住你的后台程序,心中自然无码 6 | * 1、了解常用系统调用和库函数量级 7 | * 2、清楚你编写的每个函数的大致性能 8 | * 3、掌握一门工具分析程序的瓶颈 9 | 10 | --- 11 | 12 | ## 系统调用和库函数的量级 13 | 14 | 系统调用和库函数的量级依赖于操作系统以及机器硬件配置 15 | 本文数据测试基于测试数据基于:suse64、16核CPU、64G内存、300G硬盘、千兆网卡 16 | 17 | ### 内存:write 18 | ![](https://github.com/zfengzhen/Blog/blob/master/img/hold%E4%BD%8F%E4%BD%A0%E7%9A%84%E5%90%8E%E5%8F%B0%E7%A8%8B%E5%BA%8F_memory_write.jpg) 19 | 20 | ### 硬盘:write、read 21 | ![](https://github.com/zfengzhen/Blog/blob/master/img/hold%E4%BD%8F%E4%BD%A0%E7%9A%84%E5%90%8E%E5%8F%B0%E7%A8%8B%E5%BA%8F_disk_write_read.jpg) 22 | 23 | ### 硬盘读写的背后 24 | ![](https://github.com/zfengzhen/Blog/blob/master/img/hold%E4%BD%8F%E4%BD%A0%E7%9A%84%E5%90%8E%E5%8F%B0%E7%A8%8B%E5%BA%8F_%E7%A3%81%E7%9B%98%E8%AF%BB%E5%86%99%E8%83%8C%E5%90%8E.jpg) 25 | 26 | ### 文件打开关闭:open close 27 | ![](https://github.com/zfengzhen/Blog/blob/master/img/hold%E4%BD%8F%E4%BD%A0%E7%9A%84%E5%90%8E%E5%8F%B0%E7%A8%8B%E5%BA%8F_open_close.jpg) 28 | 29 | ### 时间戳函数:time gettimeofday 30 | ![](https://github.com/zfengzhen/Blog/blob/master/img/hold%E4%BD%8F%E4%BD%A0%E7%9A%84%E5%90%8E%E5%8F%B0%E7%A8%8B%E5%BA%8F_time.jpg) 31 | 32 | ### VDSO 33 | * Virtual Dynamic Shared Object 34 | * 虚拟动态共享对象 35 | * 将内核函数映射到用户空间,虚拟了一个so共享模块`linux-vdso.so.1`,这个模块实际上不存在,调用起来和直接调用用户空间的C函数一样,减少了系统调用开销。VDSO经常用来用于给gettimeofday提供快速访问。 36 | 37 | ### time gettimeofday vdso加速 38 | ![](https://github.com/zfengzhen/Blog/blob/master/img/hold%E4%BD%8F%E4%BD%A0%E7%9A%84%E5%90%8E%E5%8F%B0%E7%A8%8B%E5%BA%8F_time_vdso.jpg) 39 | 40 | ### VDSO启动参数 41 | * /proc/sys/kernel/vsyscall64 42 | * 0 提供最精确的微秒时间间隔解决方案,但是也是开销最大,因为它使用了一个常规的系统调用 43 | * 1 稍微没那么精确,但也是微秒级别的,较低的开销 44 | * 2 最不精确,毫秒级别的时间间隔,但是最低的开销 45 | 46 | ### 网络相关系统调用 47 | ![](https://github.com/zfengzhen/Blog/blob/master/img/hold%E4%BD%8F%E4%BD%A0%E7%9A%84%E5%90%8E%E5%8F%B0%E7%A8%8B%E5%BA%8F_%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8.jpg) 48 | 49 | 总结:碰到自己常用的系统调用和库函数可以自己测一测,掌握这些常用系统调用和库函数的大概量级 50 | 51 | --- 52 | 53 | ##函数性能评估 54 | 清楚你编写的每个函数的大致性能 55 | 56 | ### gprof 57 | * gprof使用 58 | * 用编译器对程序进行编译,加上-pg参数 59 | * 运行编译后的程序,生成gmon.out文件 60 | * 用gprof工具分析gmon.out 61 | * 优点: 62 | * 简单易用 63 | * 缺点: 64 | * 不太适合后台程序 65 | * 只能分析程序运行过程中消耗的用户时间,无法得到程序内核空间的运行时间 66 | 67 | ### 利用共享内存以及C++构造析构函数进行函数性能分析 68 | * 获取函数单次运行时长 69 | * 在函数的开头实例化一个特殊的类 70 | * 在该类构造函数和析构函数中调用gettimeofday 71 | * 相减得到耗时 72 | * 存储该耗时到共享内存,并求多次平均值 73 | * avg_cost += (cost – avg_cost)/call_times; 74 | * 编写一个工具读取共享内存数据,并进行分析 75 | * 优点:能够得到用户级别函数的性能,压测时能够得到后台程序的执行性能 76 | * 缺点:需要编写实现;单进程数据比较准确;多进程时只能通过总体开始时间以及结束时间进行计算 77 | 78 | --- 79 | 80 | ##系统性能瓶颈评估 81 | 82 | ### perf 83 | linux系统性能分析工具(内核2.6.3x及以上),应用程序可以利用`PMU,tracepoint和内核中的特殊计数器`来进行性能统计,不但可以分析应用程序性能问题,还可以分析内核性能问题,从而全面理解应用程序中性能瓶颈。 84 | 参考: 85 | [Perf -- Linux下的系统性能调优工具,第 1 部分](http://www.ibm.com/developerworks/cn/linux/l-cn-perf1/) 86 | [Perf -- Linux下的系统性能调优工具,第 2 部分](http://www.ibm.com/developerworks/cn/linux/l-cn-perf2/) 87 | 88 | * perf list 89 | * 功能:查看当前环境支持的性能事件 90 | * perf stat 91 | * 功能:收集性能数据统计信息 92 | * 常用参数: 93 | * -e : 指定性能事件 94 | * -p : 指定进程PID 95 | * -d : 更详细的性能事件 96 | * perf record 97 | * 功能:收集一段事件内的性能数据 98 | * 常用参数: 99 | * -e : 指定性能事件 100 | * -p : 指定进程PID 101 | * -a : 分析整个系统的性能 102 | * -f : 强制覆盖之前的性能文件 103 | * -g : 收集函数间的调用关系 104 | * -o : 指定性能数据文件名字 105 | * perf report 106 | * 功能:分析性能数据 107 | * 常用参数: 108 | * -i : 读入的性能分析数据 109 | * -g : 输出调用关系 110 | * -v : 显示每个符号的地址 111 | * -n : 显示每个符号对应的事件数 112 | * perf top 113 | * 功能:实时显示系统性能数据 114 | * 常用参数: 115 | * -e : 指定性能事件 116 | * -p : 指定进程PID 117 | * -d : 界面刷新周期 118 | * -K : 不显示内核符号 119 | * -U : 不显示用户符号 120 | 121 | ### gprof2dot 122 | * [gprof2dot](http://code.google.com/p/jrfonseca/wiki/Gprof2Dot)是一个python脚本把各种性能分析工具的的输出转换成**dot**格式文件,支持prof、gprof、Linux perf、oprofile、Valgrind's callgrind tool等。 123 | * 用法:`gprof2dot.py [options] [file] ...` 124 | * 常用参数: 125 | * -o FILE : 输出文件(默认stdout) 126 | * -e PERCENTAGE : 删除权值在PERCENTAGE以下的边(默认0.1) 127 | * -f FORMAT : 指定性能分析文件格式profile format: prof, callgrind, oprofile, hprof,sysprof, sleepy, aqtime, pstats, axe, **perf**, or xperf(默认prof) 128 | 129 | ### graphviz 130 | * [Graphviz](http://www.graphviz.org/)由一种被称为DOT语言的图形描述语言与一组可以生成和/或处理DOT文件的工具组成。 131 | * dot :一个用来将生成的图形转换成多种输出格式的命令行工具。其输出格式包括PostScript,PDF,SVG,PNG,含注解的文本等等。 132 | * 用法:`dot –Tpng xxx.dot –o pic.png` 133 | 134 | ### 性能分析步骤 135 | * 1、产生perf性能文件 136 | * `perf record –afg –p pid –o perf.data.pid sleep 10` 137 | * http://linux.die.net/man/1/perf-record 138 | * 2、查看perf.data文件 139 | * `perf report –i perf.data.pid` 140 | * http://linux.die.net/man/1/perf-report 141 | * 3、将perf.data文件转换成dot文件(python 2.6以上) 142 | * `perf script –i perf.data.pid | python gprof2dot.py –f perf –e 1 –o xxx.dot` 143 | * http://linux.die.net/man/1/perf-script 144 | * http://code.google.com/p/jrfonseca/wiki/Gprof2Dot 145 | * 4、将xxx.dot文件转换成png图片 146 | * `dot –Tpng xxx.dot –o pic.png` 147 | * http://www.graphviz.org/pdf/dotguide.pdf 148 | 149 | ### 性能结果 150 | ![](https://github.com/zfengzhen/Blog/blob/master/img/perf_graph.png) 151 | 152 | > 从生成的结果图去寻址程序或者系统性能瓶颈。 -------------------------------------------------------------------------------- /article/mysql代理server实现总结.md: -------------------------------------------------------------------------------- 1 | # mysql代理server实现总结 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ## 需求: 5 | 假设某个逻辑server为单进程异步模式, 在这种情况下不能直接去访问mysql数据库, 否则会阻塞逻辑server. 这个时候需要单独拉出一个msyql的代理server, 使得逻辑server通过协议方式去访问mysql代理server, 实现数据库的异步交互.(后续mysql代理server称为**datasvr**, 逻辑server称为**logicsvr**) 6 | 7 | ## 1 单进程单连接 8 | 在业务量不大的情况下, datasvr采用单进程单连接的方式去访问mysql, 实现简单以及可以快速完成. 9 | 10 | ## 2 多进程且每个进程有一个独立连接 11 | 业务量上来的情况下, datasvr采用多进程部署, 每个进程采用单进程单连接的方式, 这个时候需要有一个代理进程去负责分包路由, 代理进程和工作进程的通信可以采用UDP或者是共享内存队列(看业务情况), 分包路由可以采用简单的按工作进程数取余数分发给工作进程, 或者是通过工作进程进行抢占式工作. 12 | 13 | ## 3 多线程, 连接池, 每来一个请求创建一个线程, 并从连接池中获取一个空闲连接 14 | 初始化一个连接池, 连接池的实现采用单例模式, 当收到logicsvr的请求时, 创建一个线程执行相应的函数, 不同的数据库操作对应不同的函数, 并从连接池中获取一个空闲连接, 进行数据库操作, 当查询结束, 给logicsvr回响应包后, 线程销毁. 线程的频繁创建和销毁会损耗一部分性能. 15 | 16 | ## 3 多线程, 主线程负责网络收发包, 工作线程负责数据库操作 17 | 分配两个队列: **网络请求队列**, **数据库结果队列** 18 | 主线程收到网络请求后, 将相应的请求压入网络请求队列, 工作线程轮询(或者更好的方式select, epoll)网络请求队列, 一旦有数据就开始读取数据进行数据库查询, 并把查询结果放入数据库结果队列. 主线程轮询数据库结果队列, 如果有数据则处理数据并进行相应的回包. 19 | 这种模式实际上类似于单进程, 只是缓存了请求包, 如果logicsvr跟datasvr采用共享内存队列的工作方式, 实际上缓存在共享内存上, 可以采用单进程单连接的模式. 20 | ![](https://github.com/zfengzhen/Blog/blob/master/img/mysql_proxy_server_2thread.jpg) 21 | 22 | ## 4 多线程, 每个线程绑定一个数据库连接, 有一个同一的管理类来管理这些数据库连接, 进行分配, 回收等工作 23 | 数据库连接包含在MysqlClient类中, MysqlClient还绑定了一个存储sql语句的缓存, 一个执行工作的线程, 一对pipe的fd, 一个数据库结果集, 数据库相关的属性设置, 一个回调函数. 24 | MysqlClient绑定的工作线程的主循环: select pipefd 看是否有数据写入, 如果有则执行相应的sql语句, 执行完后调用回调函数. 25 | MysqlClient的主线程: 如果执行sql操作时, 写入sql到相应的字段, 写入回调函数到相应的字段, 并写入pipe一个字节, 用来通知工作线程. 26 | MysqlMgr用来管理多个MysqlClient, 维持一个空闲MysqlClient队列, 每次从中取出一个空闲的MysqlClient, 并执行相应的sql查询, 注册回调函数. 27 | 框架: 28 | ![](https://github.com/zfengzhen/Blog/blob/master/img/mysql_proxy_server_mysqlmgr.jpg) 29 | 执行流程: 30 | ![](https://github.com/zfengzhen/Blog/blob/master/img/mysql_proxy_server_mutithread.jpg) 31 | 32 | -------------------------------------------------------------------------------- /article/protobuf协议设计.md: -------------------------------------------------------------------------------- 1 | # Protobuf协议设计 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ## 采用extension 5 | cs_msg.proto 6 | 7 | ``` 8 | // 二进制格式 9 | // _________________________________ 10 | // |____|__________________________| 11 | // | | 12 | // 数据包 | 13 | // 整个长度 | 14 | // (uint16_t) | 15 | // Msg序列化后的二进制字符串 16 | // 17 | // 1 根据数据包大小, 解包Msg 18 | // 2 根据head中的cmd字段, 解包extension 19 | // 3 其中head中的cmd字段为extension的tag值 20 | // 比如ProtoCs::kLoginReqFieldNumber 21 | // 22 | // 优点: 23 | // 1 定义了extension的tag可以用于具体的extension的获取 24 | // 不用再定义一套对应的键值对 25 | // 2 各个模块定义分离, 用法上通过extension获取具体字段 26 | // 27 | // 使用方法: 28 | // 1 msg->HasExtension(ProtoSs::conn_data) 29 | // 2 msg->ClearExtension(ProtoCs::login_req) 30 | // 3 const ProtoCs::LoginReq& login_req = msg->GetExtension(ProtoCs::login_req) 31 | // 4 ProtoCs::LoginReq* login_req = msg->MutableExtension(ProtoCs::login_req) 32 | // 33 | // 不足: 34 | // 1 脚本语言(Python, Lua)中无法获取ProtoCs::kLoginReqFieldNumber 35 | // 2 protobuf的ExtensionRegistry采用hash_map实现, 虽然google实现的hash_map性能 36 | // 很高, 但是比不使用extension的性能稍低, 网上有同学测试不带extension的会快2.64倍 37 | 38 | package ProtoCs; 39 | 40 | message MsgHead { 41 | optional int32 cmd = 1; 42 | optional int32 ret = 2; 43 | optional uint64 seq = 3; 44 | } 45 | 46 | message Msg { 47 | optional MsgHead head = 1; 48 | extensions 50 to 10000; 49 | } 50 | ``` 51 | 52 | cs_role.proto 53 | 54 | ``` 55 | import "cs_msg.proto"; 56 | 57 | package ProtoCs; 58 | 59 | enum CsRoleProtoRet { 60 | option allow_alias = true; 61 | RET_LOGIN_OK = 0; 62 | RET_LOGIN_FAILED = -1; 63 | RET_LOGIN_GAMESVR_FULL = -2; 64 | } 65 | 66 | message LoginReq { 67 | optional bytes account = 1; 68 | optional bytes password = 2; 69 | } 70 | 71 | message LoginRes { 72 | optional bool numb = 1; 73 | } 74 | 75 | extend Msg { 76 | optional LoginReq login_req = 300; 77 | optional LoginRes login_res = 301; 78 | } 79 | ``` 80 | 81 | ## 不采用extension, 业务逻辑包不用MsgBody嵌套 82 | cs_msg.proto 83 | 84 | ``` 85 | // 二进制格式 86 | // ______________________________________________ 87 | // |____|____|__MsgHead__|______业务逻辑包______| 88 | // | \ 89 | // 数据包 \ 90 | // 整个长度 \ 91 | // (uint16_t) \ 92 | // MsgHead包长度 93 | // 94 | // 1 根据MsgHead大小, 解包MsgHead 95 | // 2 根据head中的cmd字段以及业务逻辑包大小, 解包具体业务逻辑包 96 | // 97 | // 优点: 98 | // 1 将MsgHead和业务逻辑包分离, 没有用具体的一个Msg去再包装一层, 99 | // 接入server可能要用到MsgHead的一些字段去做路由或者鉴权, 100 | // 这样就不用把整个Msg解析出来, 提高性能 101 | // 2 业务逻辑包并没有通过一个类似于MsgBody去包装, 102 | // 这样的话各个模块定义依然分离, 而且减少了一层包装, 提高性能 103 | // 3 简单的使用, 脚本语言(Python, Lua)都支持这些基本功能 104 | // 4 键值对也可以采用字段tag, 105 | // 106 | // 不足: 107 | // 1 业务请求包只能通过char*指针进行传参 108 | 109 | package ProtoCs; 110 | 111 | message MsgHead { 112 | optional int32 cmd = 1; 113 | optional int32 ret = 2; 114 | optional uint64 seq = 3; 115 | } 116 | ``` 117 | 118 | cs_role.proto 119 | 120 | ``` 121 | package ProtoCs; 122 | 123 | enum CsRoleProtoRet { 124 | option allow_alias = true; 125 | RET_LOGIN_OK = 0; 126 | RET_LOGIN_FAILED = -1; 127 | RET_LOGIN_GAMESVR_FULL = -2; 128 | } 129 | 130 | message LoginReq { 131 | optional bytes account = 1; 132 | optional bytes password = 2; 133 | } 134 | 135 | message LoginRes { 136 | optional bool numb = 1; 137 | } 138 | ``` 139 | 140 | ## 业务逻辑包不采用MsgBody统一封装时遇到的麻烦处理 2013.08.26 141 | 142 | 不采用MsgBody统一封装时遇到的麻烦, 在封装消息处理时, 参数的传递非常让人讨厌. 143 | 比如: 144 | 有一个函数负责MsgHead包头处理, 根据包头里面的cmd查找具体处理函数, 这个时候如果没有MsgBody的封装时, 只能传入字符串指针, 等处理处理函数直接序列化到该指针指向的缓冲区, 还得提供缓冲区的大小; 如果通过MsgBody进行包装的话, 在整体处理函数的时候, 建立一个MsgBody的实例, 传入到具体的处理函数, 函数返回时, 整体处理函数进行组包, 传递给发包的函数, 整体一气呵成. 对于不封装MsgBody的方式, 只是多了一层message的嵌套开销, 换来的是整体代码的阅读性更强, 以及在看协议的时候, MsgBody集中了各个具体业务逻辑, 对于有的所有业务逻辑更加清晰. 145 | 146 | 147 | ``` 148 | // 二进制格式 149 | // ______________________________________________ 150 | // |____|____|__MsgHead__|________MsgBody_______| 151 | // | \ 152 | // 数据包 \ 153 | // 整个长度 \ 154 | // (uint16_t) \ 155 | // MsgHead包长度 156 | // 157 | // 1 根据MsgHead大小, 解包MsgHead 158 | // 2 根据head中的cmd字段以及MsgBody的大小, 解包具体业务逻辑包 159 | // 160 | // 优点: 161 | // 1 将MsgHead和MsgBody分离, 没有用具体的一个Msg去再包装一层, 162 | // 接入server可能要用到MsgHead的一些字段去做路由或者鉴权, 163 | // 这样就不用把整个Msg解析出来, 提高性能 164 | // 2 简单的使用, 脚本语言(Python, Lua)都支持这些基本功能 165 | // 3 将CS, SS的MsgHead和MsgBody的分开定义, 166 | // 不过度的把SS协议字段暴露给CS, 交给CONNSVR去转换 167 | // 4 键值对也可以采用字段tag, 168 | // 169 | ``` 170 | 171 | cs_msg_head.proto 172 | 173 | ``` 174 | package ProtoCs; 175 | 176 | message MsgHead { 177 | optional int32 cmd = 1; 178 | optional int32 ret = 2; 179 | optional uint64 seq = 3; 180 | } 181 | ``` 182 | cs_msg_body.proto 183 | 184 | ``` 185 | import "cs_role.proto" 186 | 187 | package ProtoCs; 188 | 189 | message MsgBody { 190 | optional LoginReq login_req = 11; 191 | optional LoginRes login_res = 12; 192 | } 193 | ``` 194 | 195 | 196 | cs_role.proto 197 | 198 | ``` 199 | package ProtoCs; 200 | 201 | enum CsRoleProtoRet { 202 | option allow_alias = true; 203 | RET_LOGIN_OK = 0; 204 | RET_LOGIN_FAILED = -1; 205 | RET_LOGIN_GAMESVR_FULL = -2; 206 | } 207 | 208 | message LoginReq { 209 | optional bytes account = 1; 210 | optional bytes password = 2; 211 | } 212 | 213 | message LoginRes { 214 | optional bool numb = 1; 215 | } 216 | ``` 217 | 218 | -------------------------------------------------------------------------------- /article/protobuf懒读取.md: -------------------------------------------------------------------------------- 1 | # ProtoBuf懒读取 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | **我所遇到需要用到ProtoBuf懒读取的两种情况** 5 | 1 采用纯Protobuf进行通信的proxy在中转数据的时候,只需要读取包中的某几个字段,比如中转数据的方式(平均分发,随机分发,按key取余分发),发送数据的server地址(后端worker进程处理后可能需要直接给请求服务的server回包),这个时候需要懒读取需要的字段,而不去全部解析整个Protobuf,导致性能下降 6 | 2 采用Protobuf做数据存储协议描述的,游戏数据采用Protobuf描述,存储到通用的nosql的key-value结构中或者mysql的blob数据中,当不采用内存池缓存玩家全部数据的方式,而采用CacheSvr机制(CacheSvr缓存Protobuf原始二进制数据),GameSvr(无状态)在每次逻辑时都去CacheSvr读写数据,可能每次逻辑都只要其中的某几个字段的时候需要懒读取 7 | 8 | **在项目中,我们采用纯Protobuf协议,中间不含额外的其他二进制协议 9 | 第一种情况我们需要采用Protobuf懒读取处理 10 | 第二种情况我们采用GameSvr缓存玩家全部数据方式(GameSvr是用状态的),所以还不需要懒读取** 11 | 12 | ![](https://github.com/zfengzhen/Blog/blob/master/img/protobuf懒读取_proxy.jpg) 13 | 14 | 第一种情况举例: 15 | ``` 16 | message Head { 17 | optional int32 cmd = 1; 18 | optional int32 ret = 2; 19 | optional uint64 seq = 3; 20 | optional int32 route_type = 4; 21 | optional int32 route_key = 5; 22 | optional string source_ip = 6; 23 | } 24 | 25 | message Msg { 26 | optional Head head = 1; 27 | optioanl Body body = 2; 28 | } 29 | ``` 30 | 31 | 对于纯Protobuf协议,proxy只需要message Head中的相应字段,而对于Body字段不需要解析,透明转发就好。 32 | 但对于Protobuf所提供的API, msg.ParseFromString()等解析函数,都会解析整个对象,把二进制中所有数据填充到Protobuf的Msg对象中,填充的过程对于repeated采用了::google::protobuf::RepeatedPtrField<>采用new分配内存,对于没有定义的字段存入::google::protobuf::UnknownFieldSet,UnknownFieldSet采用vector以及string做存储。 33 | 34 | ```c++ 35 | class LIBPROTOBUF_EXPORT UnknownFieldSet { 36 | …… 37 | std::vector* fields_; 38 | GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(UnknownFieldSet); 39 | }; 40 | 41 | // Represents one field in an UnknownFieldSet. 42 | class LIBPROTOBUF_EXPORT UnknownField { 43 | …… 44 | public: 45 | enum Type { 46 | TYPE_VARINT, 47 | TYPE_FIXED32, 48 | TYPE_FIXED64, 49 | TYPE_LENGTH_DELIMITED, 50 | TYPE_GROUP 51 | }; 52 | uint32 number_; 53 | uint32 type_; 54 | 55 | union LengthDelimited { 56 | string* string_value_; 57 | }; 58 | 59 | union { 60 | uint64 varint_; 61 | uint32 fixed32_; 62 | uint64 fixed64_; 63 | mutable union LengthDelimited length_delimited_; 64 | UnknownFieldSet* group_; 65 | }; 66 | }; 67 | ··· 68 | 69 | 查阅一些懒解析的方法, 有采用重新定义一份Protobuf描述文件的办法进行: 70 | ··· 71 | message LazyMsg { 72 | optional Head head = 1; 73 | } 74 | ``` 75 | 76 | 这种方法把Msg序列化的二进制数据按LazyMsg进行解析,Body的数据会按UnkonwnFieldSet进行处理,会通过相应的API调用string以及vector存入UnkonwnFieldSet,此时虽然不需要具体去解析Body里面的各种嵌套的Message了,效率确实有所提高,但是还是会把数据当成一段二进制存储到UnkonwnFieldSet中,会调用sring以及vector,底层调用new进行内存分配,而且得重新定义一份Protobuf描述文件,增加出错成本。 77 | 78 | 查阅Protobuf的源文件中,发现有能够解析序列化后的二进制数据相应的类和API。 79 | protobuf/src/google/protobuf/io/coded_stream.h 80 | 81 | ```c++ 82 | class LIBPROTOBUF_EXPORT CodedInputStream { 83 | …… 84 | } 85 | ``` 86 | 87 | protobuf/src/google/protobuf/wire_format_lite.h 88 | ```c++ 89 | class LIBPROTOBUF_EXPORT WireFormatLite { 90 | public: 91 | …… 92 | static bool SkipField(io::CodedInputStream* input, uint32 tag); 93 | template 94 | static inline bool ReadMessageNoVirtual(input, MessageType* value); 95 | …… 96 | } 97 | ``` 98 | 99 | 通过这两个类可以写一个只读取某个tag值的工具类ProtobufReader,只读取你想要的字段,而且性能非常好。 100 | 我已经写了一个,参见[ProtobufReader]( https://github.com/zfengzhen/FullTest/blob/master/src/protobuf_test/protobuf_reader.h) 101 | 写了个测试用例,结果如下: 102 | ![](https://github.com/zfengzhen/Blog/blob/master/img/protobuf懒读取_测试结果.jpg) 103 | -------------------------------------------------------------------------------- /article/tcp系统调用总结.md: -------------------------------------------------------------------------------- 1 | # tcp系统调用总结 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ## send 5 | 6 | ```c 7 | ssize_t send(int sockfd, const void *buf, size_t len, int flags); 8 | ``` 9 | 10 | ### 阻塞模式下: 11 | send通过阻塞保证发送成功, 如果成功, 则返回的字节数一定等于发送字节数. 12 | 对于错误errno, 需要特别处理EINTR错误, 需要重新发送数据. 13 | 14 | ### 非阻塞模式下: 15 | send即时发送数据, 如果缓冲区如果满了, 则会返回EAGAIN或者EWOULDBLOCK错误, 如果缓冲区有空, 则尽量拷贝, 将要发送数据拷贝到缓冲区中, 返回已经拷贝的数据长度. 16 | 17 | #### 需要注意两个问题 18 | 1 碰到EAGAIN或EWOULDBLOCK时重试, 如何重试, 如果重试时间太快, 会继续出现该错误. 19 | 2 如果只发送了部分数据, 如何发送剩下的数据, 如果立马继续发送, 则会出现EAGAIN或EWOULDBLOCK错误 20 | 21 | #### 轮询模式下(或者定时器触发) 22 | 轮询模式是指主循环每隔一定时间, 循环运行一定的逻辑. 23 | 增加应用层的发送数据缓冲区, 每次发送数据, 将数据追加到数据缓冲区, 轮询或者定时器触发时, 通过send发送数据, 大小为缓冲区上的所有数据 24 | 1 如果出现EAGAIN或EWOULDBLOCK则返回, 因为下次轮询或者定时器触发时依然会发送 25 | 2 如果发送了部分数据, 则将已发送的数据移出缓冲区, 下次轮询或者定时器触发会继续发送 26 | 3 如果全部发送成功, 则将所有的数据移出缓冲区, 下次轮询或者定时器触发时, 发送缓冲区数据为空时, 直接返回主循环 27 | 28 | #### 事件触发模式下(epoll, libevent) 29 | 事件触发模式监听fd, 一旦可写的时候, 触发相应的事件进行处理 30 | 什么是可写状态? 31 | LT模式: 可写状态是指socket写缓冲区空闲值达到一定阀值时, 一般这个阀值为1, 也就是写缓冲区空闲超过1字节时就会触发写状态. 32 | ET模式: unwriteable变为writeable, 也就是得一直写, 出现了EAGAIN以及EWOULDBLOCK时, 就会变成unwriteable, 这样一旦缓冲区变为writeable, 就会触发写状态. 33 | 34 | 假设服务器处于LT模式, 需要发送数据时: 35 | 1 将socket加入事件监听, 等待可写事件 36 | 2 触发可写事件时, 发送数据, 如果数据只发送了部分, 从应用层的缓冲区删除已发送的数据, 并直接返回, 下次触发的时候, 依然有数据可写 37 | 3 如果写完, 将socket移出事件监听 38 | 这样的缺点是每次都得将socket加入事件监听, 以及移出事件监听, 有一定的代价. 39 | 优化: 40 | 如果应用层缓冲区没有数据, 则直接发送, 如果遇到EAGAIN或EWOULDBLOCK, 则加入事件监听; 如果部分发送, 则将剩余未发送的数据加入应用层缓冲区, 然后加入事件监听. 毕竟一般都是成功全部发送, 可以减少加入事件监听以及移出事件监听的消耗. 41 | 42 | 43 | ## recv 44 | 45 | ```c 46 | ssize_t recv(int sockfd, void *buf, size_t len, int flags); 47 | ``` 48 | 49 | recv传入参数是希望收到的最大数据量, 返回是实际收到数据量大小. recv工作时, 先检查socket的接收缓冲区是否正在接收数据或者是否有数据, 如果正在接接收数据或者数据未空, 则一直阻塞, 当数据接收完毕的时候, recv把socket缓冲区的数据拷贝到应用层的缓冲区中. 如果socket缓冲区数据大小比应用层的大, 则需要调用多次recv才能接收完整. 50 | 返回0, 则表示对端主动断掉连接. 51 | (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) 认为连接是正常的. 52 | ### 阻塞模式 53 | recv会阻塞直到有数据到来, 一般单进程异步的情况下不会这么做. 54 | ### 非阻塞模式 55 | 可以加入事件监听, 注意LT模式和ET模式的区别. ET模式要读到数据出现EAGAIN的情况才行. 注意自己组包, 拼包处理. 56 | 57 | ## accept 58 | 59 | ```c 60 | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 61 | ``` 62 | 63 | ### 阻塞模式 64 | 如果没有请求过来, 会阻塞进程. 65 | ### 非阻塞模式 66 | 如果没有请求过来, 将会返回EAGAIN或EWOULDBLOCK 67 | 加入事件监听, 触发可读事件, 则表示有新的连接过来. 68 | 当客户在服务器调用accept之前中止某个连接时, accept调用可以立即返回-1, errno设置为ECONNABORTED或者EPROTO错误忽略这两个错误. 69 | 70 | ## connect 71 | 72 | ```c 73 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 74 | ``` 75 | 76 | tcp三次握手: 77 | 78 | ``` 79 | client ----SYN j---> server 80 | client <---SYN k, ACK j+1--- server 81 | client ----ACK k+1---> server 82 | ``` 83 | 84 | ### 阻塞模式 85 | 1 SYN响应过满, 阻塞, 直到阻塞模式超时. 86 | 2 如果对客户端的SYN响应RST, 立即返回ECONNREFUSED错误. 87 | 3 如果发出的SYN在中间的路由器上引发了一个目的地不可达ICMP错误, 多次尝试发送失败后返回错误号为ENETUNREACH. 88 | 89 | ### 非阻塞模式 90 | 直接调用connect会返回一个EINPROGRESS错误, 但不意味着连接出错. 91 | 加入监听事件(epoll select libevent), 当连接成功时, 返回socket可写; 单连接失败时, 返回socket可读可写. 92 | 正确的作法, 当返回socket可写时, 需要调用getsockopt来判断连接是否成功. 93 | 94 | ```c 95 | int error = 0; 96 | unsigned int len = sizeof(error); 97 | if (getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, &len) == 0) { 98 | // 建立成功 99 | return 0; 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /article/tolua++实现分析.md: -------------------------------------------------------------------------------- 1 | # tolua++实现分析 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ## 前言 5 | cocos2dx-lua以及quick都大规模采用tolua++进行绑定, 对于大规模使用tolua++信心不足, 最近花了几天时间把tolua++源码读了一遍, 了解了tolua++绑定的实现, 明白了tolua++对内存的管理, 增强对这种大规模使用tolua++代码的把控制, 以及未来服务器也同步采用lua5.1和tolua++的可能. 6 | 7 | ## tolua++使用流程 8 | 1 tolua++工具通过tag或者pkg方式生成要绑定函数的目标文件, 把类成员函数翻译成lua能够注册的C函数 9 | 参见官方文档: http://www.codenix.com/~tolua/ 10 | 2 得到tolua++自动生成的绑定文件, 该代码提供了从lua层访问C/C++层的绑定 11 | 3 与项目一起编译tolua++自动生成的绑定文件, 在项目文件中调用tolua的API进行使用 12 | 13 | ##tolua++注册类 14 | 1 在项目文件中tolua++自动生成的绑定文件中的tolua_xxx_open(L)函数注册类绑定(其中xxx为pkg文件名) 15 | 2 tolua_xxx_open(L)函数调用了tolua_open(L), tolua_reg_types, tolua_cclass, tolua_beginmodule, tolua_function, tolua_variable, tolua_endmodule等函数进行类的绑定, 其中tolua_reg_types是自动绑定文件生成的函数, 其他都是tolua++库函数 16 | 3 tolua_open(L) 建立相应的全局注册表, 包括tolua_opened, tolua_peers, tolua_ubox, tolua_super, tolua_gc 17 | 4 tolua_reg_types主要调用tolua_usertype注册该pkg文件下的所有C++类型的metatabl 18 | 5 tolua_usertype调用tolua_newmetatable 注册C++类型metatable和const C++类型metatable 19 | 6 tolua_usertype调用tolua_classevents注册C++类型metatable中的__add, __call, __div, __eq, __gc, __index, __le, __lt, __mul, __newindex, __sub方法 20 | 7 tolua_cclass设置父子关系, 以及对象回收函数(为tolua++自动生成的一个调用delete的C函数) 21 | 8 tolua_cclass调用mapinheritance设置父子关系metatable, 指定父类为子类的metatable, 通过指定metatable的方式进行继承, 其中const_type—继承—>type—继承—>base_type 22 | 9 tolua_cclass调用set_ubox设置父子关系共享同一tolua_ubox, tolua_ubox暂时为空表, 且值为弱引用的弱表 23 | 10 tolua_cclass调用mapsuper设置全局注册表中tolua_super表的父子关系 24 | 11 绑定类成员函数: tolua_function 25 | 12 绑定类成员变量: tolua_variable, 成员变量的绑定通过建立.set 和.get两张表, 通过__index绑定tolua++生成的get方法, __newindex绑定tolua++绑定的set方法 26 | 13 如果有namespace的话, 通过tolua_beginmodule和tolua_endmodule进行定义 27 | 14 绑定new, 通过绑定函数创建的对象, lua不会自动调用析构, 得手动调用delete进行析构 28 | 15 绑定new_local, 通过绑定的函数创建对象, 该函数中比new的绑定函数多了一个lua_register_gc调用, 在全局注册表中的tolua_gc表中, 以C++指针为键, 类型的metatable为值, 通过class_gc_event进行自动释放 29 | 16 绑定delete, 如果有析构函数的一定要注册析构函数, 否则释放时会调用默认的释放函数, tolua_default_collect, 调用free进行释放, 会发生很多未定义的行为 30 | ![image](https://github.com/zfengzhen/Blog/blob/master/img/tolua_class_relation.png) 31 | 32 | ## tolua++中全局注册表解析 33 | 1 tolua_opened 通过该字段的存在与否标识释放已经调用过tolua_open 34 | 2 tolua_peers 用来存储C++对象在lua中的扩展, 键为弱引用的弱表. lua5.1没用到这张表, 直接通过对C++对象指针进行lua_setfenv环境变量设置和获取该C++对象的tolua_peers表 35 | 3 tolua_ubox 用来存储以C++对象指针为键, 值为lua建立的fulluserdata的键值对. 看tolua++源码中有两套ubox, 一套在全局注册表, 一套在C++类型的metatable中, 实际在看代码的过程中, 只用其中一套就OK, 先判断在C++类型的metatable中是否有ubox, 没有再建立全局注册表的ubox, 而在注册C++类型的metatable中就会在该metatable中建立ubox. 优先使用C++类型的metatable的ubox. ubox为值为弱引用的弱表. 36 | 4 tolua_super 用来标识C++父类和子类关系, 该表中以C++类型的metatable的为键, 值为一个表格. 这个表格中以父类类型名为键, 0和1为值, 用来记录对象的父子关系 37 | 5 tolua_gc 用来标识是否由lua来进行垃圾回收的表. 以C++对象指针为键, 值为传入C++类型的metatable, 传入C++类型只能为C++对象的同类或者父类, 如果传入的是父类类型, gc的时候只会调用父类的析构. 38 | ![image](https://github.com/zfengzhen/Blog/blob/master/img/tolua_register_table.png) 39 | 40 | ## tolua++在lua中的工具函数 41 | 1 tolua.cast(var, type) 42 | 修改var的元表使它成为type类型, type是一个完整的C++类型对象(包括命名空间等)字符串 43 | 2 tolua.getpeer() 44 | 获取peer表, C++对象在lua中的扩展 45 | 3 tolua.setpeer() 46 | 设置peer表, C++对象在lua中的扩展 47 | 4 tolua.inherit(table, var) 48 | tolua++会将table作为对象和var具有一样的类型, 必须自己去实现table的方法. 49 | 5 tolua.type(var) 50 | 返回一个代表对象类型的字符串 51 | 6 tolua.takeownership(var) 52 | 接管对象引用变量的所有权, 意味着当对象的所有引用都没有的话, 对象本身将会被lua删除 53 | 7 tolua.releaseownership(var) 54 | 释放对象引用变量的所有权 55 | 56 | ## tolua++的一些细节 57 | 1 在lua中对C++对象引用的实质, 比如ClassA* pa = new ClassA();tolua_pushusertype(L, pa, "ClassA");lua_setglobal(L, “classa_fromc”); classa_fromc是C++对象lightuserdata的引用嘛? 58 | tolua_pushusertype通过压入C++指针pa, 搜寻到ClassA的metatable, 从该metatable中找寻到tolua_ubox表, 以pa的C++ lightuserdata为键, 看是否有元素存在, 如果存在则表示该指针已经压入过lua中, 如果不存在, 则建立C++ lightuserdata为键, fulluserdata为值, fulluserdata存储了该C++指针, 且设置ClassA的metatable为fulluserdata的metatable, 栈上留下的是ubox[u], 也就是说classa_fromc实质是存储C++对象指针的一个lua中的fulluserdata. 59 | 60 | 2 tolua_ubox表为值为弱引用的弱表 61 | 因为lightuserdata在lua中只表示一个C++内存的地址, 所有的lightuserdata都共享一张metatable(http://lua-users.org/wiki/LightUserData), 不能直接通过C++传入的内存地址进行绑定具体的C++类型的metatable, 所以引入了tolua_ubox表. 假如tolua_ubox表不是值为弱引用的弱表, tolua_ubox[u] = newu, 不管怎么样newu在tolua_ubox的引用计数都至少为1, 所以gc不会释放, 引起不必要的内存消耗. 设置为值为弱引用的弱表, gc的时候newu不占用一个引用计数, gc后该表的键值对消失. 62 | 63 | 3 C++类如果有析构函数, 则一定要注册进tolua++ 64 | C++一定要注册析构函数, 否则释放时会调用默认的释放函数 tolua_default_collect, 调用free进行释放, 会发生很多未定义的行为 65 | 66 | 4 如果设置了tolua_peers表, 调用的时候要小心 67 | 在lua脚本中通过tolua.setpeer设置tolua_peers表时, 如果表里面有跟C++类中相同的函数名, 则在类的使用中tolua_peers表会优先使用 68 | 69 | -------------------------------------------------------------------------------- /article/ucontext簇函数学习.md: -------------------------------------------------------------------------------- 1 | # ucontext簇函数学习 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | 系统手册学习: 5 | ----------- 6 | 7 | ##名字 8 | **getcontext**, **setcontext** —— 获取或者设置用户上下文 9 | ##概要 10 | ```c 11 | #include 12 | 13 | int getcontext(ucontext_t *ucp); 14 | int setcontext(const ucontext_t *ucp); 15 | ``` 16 | ##描述 17 | 在类**System-V**环境中,定义在``````头文件中的**mcontext_t**和**ucontext_t**的两种数据类型,以及**getcontext()**,**setcontext()**,**makecontext()**和**swapcontext()**四个函数允许在一个进程不同的协程中用户级别的上下文切换。 18 | **mcontext_t**数据结构是依赖机器和不透明的。**ucontext_t**数据结构至少包含下面的字段: 19 | ```c 20 | typedef struct ucontext { 21 | struct ucontext *uc_link; 22 | sigset_t uc_sigmask; 23 | stack_t uc_stack; 24 | mcontext_t uc_mcontext; 25 | ... 26 | } ucontext_t; 27 | ``` 28 | **sigset_t**和**stack_t**定义在``````头文件中。**uc_link**指向当前的上下文结束时要恢复到的上下文(只在当前上下文是由**makecontext**创建时,个人理解:只有makecontext创建新函数上下文时需要修改),**uc_sigmask**表示这个上下文要阻塞的信号集合(参见**sigprocmask**),**uc_stack**是这个上下文使用的栈(个人理解:非**makecontext**创建的上下文不要修改),**uc_mcontext**是机器特定的保存上下文的表示,包括调用协程的机器寄存器。 29 | **getcontext()**函数初始化**ucp**所指向的结构体,填充当前有效的上下文。 30 | **setcontext()**函数恢复用户上下文为**ucp**所指向的上下文。成功调用不会返回。**ucp**所指向的上下文应该是**getcontext()**或者**makeontext()**产生的。 31 | 如果上下文是**getcontext()**产生的,切换到该上下文,程序的执行在**getcontext()**后继续执行。 32 | 如果上下文被**makecontext()**产生的,切换到该上下文,程序的执行切换到**makecontext()**调用所指定的第二个参数的函数上。当该函数返回时,我们继续传入**makecontext()**中的第一个参数的上下文中**uc_link**所指向的上下文。如果是NULL,程序结束。 33 | ##返回值 34 | 成功时,**getcontext()**返回0,**setcontext()**不返回。错误时,都返回-1并且赋值合适的errno。 35 | ##注意 36 | 这个机制最早的化身是**setjmp/longjmp**机制。但是它们没有定义处理信号的上下文,下一步就出了**sigsetjmp/siglongjmp**。当前这套机制给予了更多的控制权。但是另一方面,没有简单的方法去探明**getcontext()**的返回是第一次调用还是通过**setcontext()**调用。用户不得不发明一套他自己的书签的数据,并且当寄存器恢复时,register声明的变量不会恢复(寄存器变量)。 37 | 当信号发生时,当前的用户上下文被保存,一个新的内核为信号处理器产生的上下文被创建。不要在信号处理器中使用**longjmp**:它是未定义的行为。使用**siglongjmp()**或者**setcontext()**替代。 38 | 39 | ##名字 40 | **makecontext**,**swapcontext** —— 操控用户上下文 41 | ##概要 42 | ```c 43 | #include 44 | 45 | void makecontext(ucontext_t *ucp, void (*func)(void), int argc, ...); 46 | int swapcontext(ucontext_t *restrict oucp, const ucontext_t *restrict ucp); 47 | ``` 48 | ##描述 49 | **makecontext()**函数修改**ucp**所指向的上下文,**ucp**是被**getcontext()**所初始化的上下文。当这个上下文采用**swapcontext()**或者**setcontext()**被恢复,程序的执行会切换到**func**的调用,通过**makecontext()**调用的**argc**传递**func**的参数。 50 | 在**makecontext()**产生一个调用前,应用程序必须确保上下文的栈分配已经被修改。应用程序应该确保**argc**的值跟传入**func**的一样(参数都是int值4字节);否则会发生未定义行为。 51 | 当**makecontext()**修改过的上下文返回时,**uc_link**用来决定上下文是否要被恢复。应用程序需要在调用**makecontext()**前初始化**uc_link**。 52 | **swapcontext()**函数保存当前的上下文到**oucp**所指向的数据结构,并且设置到**ucp**所指向的上下文。 53 | ##返回值 54 | 成功完成,**swapcontext()**返回0。否则返回-1,并赋值合适的errno。 55 | ##错误 56 | **swapcontext()**函数可能会因为下面的原因失败: 57 | **ENOMEM** **ucp**参数没有足够的栈空间去完成操作。 58 | ##例子 59 | ```c 60 | #include 61 | #include 62 | 63 | 64 | static ucontext_t ctx[3]; 65 | 66 | 67 | static void 68 | f1 (void) 69 | { 70 | puts("start f1"); 71 | swapcontext(&ctx[1], &ctx[2]); 72 | puts("finish f1"); 73 | } 74 | 75 | 76 | static void 77 | f2 (void) 78 | { 79 | puts("start f2"); 80 | swapcontext(&ctx[2], &ctx[1]); 81 | puts("finish f2"); 82 | } 83 | 84 | 85 | int 86 | main (void) 87 | { 88 | char st1[8192]; 89 | char st2[8192]; 90 | 91 | 92 | getcontext(&ctx[1]); 93 | ctx[1].uc_stack.ss_sp = st1; 94 | ctx[1].uc_stack.ss_size = sizeof st1; 95 | ctx[1].uc_link = &ctx[0]; 96 | makecontext(&ctx[1], f1, 0); 97 | 98 | 99 | getcontext(&ctx[2]); 100 | ctx[2].uc_stack.ss_sp = st2; 101 | ctx[2].uc_stack.ss_size = sizeof st2; 102 | ctx[2].uc_link = &ctx[1]; 103 | makecontext(&ctx[2], f2, 0); 104 | 105 | 106 | swapcontext(&ctx[0], &ctx[2]); 107 | return 0; 108 | } 109 | ``` 110 | 111 | 代码试用总结: 112 | ----------- 113 | - 1 makecontext之前必须调用getcontext初始化context,否则会段错误core 114 | - 2 makecontext之前必须给uc_stack分配栈空间,否则也会段错误core 115 | - 3 makecontext之前如果需要上下文恢复到调用前,则必须设置uc_link以及通过swapcontext进行切换 116 | - 4 getcontext产生的context为当前整个程序的context,而makecontext切换到的context为新函数独立的context,但setcontext切换到getcontext的context时,getcontext所在的函数退出时,并不需要uc_link的管理,依赖于该函数是在哪被调用的,整个栈会向调用者层层剥离 117 | - 5 不产生新函数的上下文切换指需要用到getcontext和setcontext 118 | - 6 产生新函数的上下文切换需要用到getcontext,makecontext和swapcontext 119 | 120 | 121 | ucontext性能小试: 122 | ----------- 123 | 运行环境为我的mac下通过虚拟机开启的centos64位系统,不代表一般情况,正常在linux实体机上应该会好很多吧 124 | 125 | - 1 单纯的getcontext: 126 | function[ **getcontext(&ctx)** ] count[ **10000000** ] 127 | cost[ **1394.88** ms] avg_cost[ **0.14** us] 128 | total CPU time[ **1380.00** ms] avg[ **0.14** us] 129 | user CPU time[ **560.00** ms] avg[ **0.06** us] 130 | system CPU time[ **820.00** ms] avg[ **0.08** us] 131 | 132 | - 2 新函数的协程调用 133 | 通过getcontext和对uc_link以及uc_stack赋值,未了不增加其他额外开销,uc_stack为静态字符串数组分配,运行时不申请,makecontext中的函数foo为空函数,调用swapcontext切换协程调用测试 134 | function[ **getcontext_makecontext_swapcontext()** ] count[ **1000000** ] 135 | cost[ **544.55** ms] avg_cost[ **0.54** us] 136 | total CPU time[ **550.00** ms] avg[ **0.55** us] 137 | user CPU time[ **280.00** ms] avg[ **0.28** us] 138 | system CPU time[ **270.00** ms] avg[ **0.27** us] 139 | 140 | 每秒**百万级别**的调用性能。 141 | 142 | ucontext协程的实际使用: 143 | ----------- 144 | 将getcontext,makecontext,swapcontext封装成一个类似于lua的协同式协程,需要代码中主动yield释放出CPU。 145 | 协程的栈采用malloc进行堆分配,分配后的空间在64位系统中和栈的使用一致,地址递减使用,uc_stack.uc_size设置的大小好像并没有多少实际作用,使用中一旦超过已分配的堆大小,会继续向地址小的方向的堆去使用,这个时候就会造成堆内存的越界使用,更改之前在堆上分配的数据,造成各种不可预测的行为,coredump后也找不到实际原因。 146 | 对使用协程函数的栈大小的预估,协程函数中调用其他所有的api的中的局部变量的开销都会分配到申请给协程使用的内存上,会有一些不可预知的变量,比如调用第三方API,第三方API中有非常大的变量,实际使用过程中开始时可以采用mmap分配内存,对分配的内存设置GUARD_PAGE进行mprotect保护,对于内存溢出,准确判断位置,适当调整需要分配的栈大小。 147 | ![](https://github.com/zfengzhen/Blog/blob/master/img/ucontext簇函数学习_实际使用.png) -------------------------------------------------------------------------------- /article/《linux_programming_interface》读书笔记.md: -------------------------------------------------------------------------------- 1 | # 《Linux Programming Interface》读书笔记 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ## 目录 5 | 6 | - [4 file I/O:the universal I/O model](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#4-file-iothe-universal-io-model) 7 | - [5 file I/O: further details](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#5-file-io--further-details) 8 | - [6 processes](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#6-processes) 9 | - [10 time](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#10-time) 10 | - [12 system and process information](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#12-system-and-process-information) 11 | - [13 file I/O buffering](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#13-file-io-buffering) 12 | - [14 file systems](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#14-file-systems) 13 | - [19 monitoring file events](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#19-monitoring-file-events) 14 | - [23 process creation](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#23-process-creation) 15 | - [24 process termination](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#24-process-termination) 16 | - [25 monitoring child process](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#25-monitoring-child-process) 17 | - [34 Process Groups, Sessions, and Job Control](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#34-process-groups-sessions-and-job-control) 18 | - [41 fundamentals of shared libraries](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#41-fundamentals-of-shared-libraries) 19 | - [51 Introduction to POSIX IPC](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#51-introduction-to-posix-ipc) 20 | - [52 POSIX Message Queues](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#52-posix-message-queues) 21 | - [53 POSIX Semaphores](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#53-posix-semaphores) 22 | - [54 POSIX Shared Memory](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#54-posix-shared-memory) 23 | - [55 File Locking](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#55-file-locking) 24 | - [56 Sockets: Introduction](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#56-sockets-introduction) 25 | - [59 Sockets: Internet Domains](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#59-sockets-internet-domains) 26 | - [63 Alternative I/O Models](https://github.com/zfengzhen/Blog/blob/master/%E3%80%8Alinux_programming_interface%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#63-alternative-io-models) 27 | 28 | ## 4 file I/O:the universal I/O model 29 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_4_1.png) 30 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_4_2.png) 31 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_4_3.png) 32 | 33 | ## 5 file I/O: further details 34 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_5_1.png) 35 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_5_2.png) 36 | 37 | ## 6 processes 38 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_6_1.png) 39 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_6_2.png) 40 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_6_3.png) 41 | 42 | ## 10 time 43 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_10_1.png) 44 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_10_2.png) 45 | 46 | ## 12 system and process information 47 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_12_1.png) 48 | 49 | ## 13 file I/O buffering 50 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_13_1.png) 51 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_13_2.png) 52 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_13_3.png) 53 | 54 | ## 14 file systems 55 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_13_1.png) 56 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_14_2.png) 57 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_14_3.png) 58 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_14_4.png) 59 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_14_5.png) 60 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_14_6.png) 61 | 62 | ## 19 monitoring file events 63 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_19_1.png) 64 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_19_2.png) 65 | 66 | ## 23 process creation 67 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_23_1.png) 68 | 69 | ## 24 process termination 70 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_24_1.png) 71 | 72 | ## 25 monitoring child process 73 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_25_1.png) 74 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_25_2.png) 75 | 76 | ## 34 Process Groups, Sessions, and Job Control 77 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_34_1.png) 78 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_34_2.png) 79 | 80 | ## 41 fundamentals of shared libraries 81 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_41_1.png) 82 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_41_2.png) 83 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_41_3.png) 84 | 85 | ## 51 Introduction to POSIX IPC 86 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_51_1.png) 87 | 88 | ## 52 POSIX Message Queues 89 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_52_1.png) 90 | 91 | ## 53 POSIX Semaphores 92 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_53_1.png) 93 | 94 | ## 54 POSIX Shared Memory 95 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_54_1.png) 96 | 97 | ## 55 File Locking 98 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_55_1.png) 99 | 100 | ## 56 Sockets: Introduction 101 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_56_1.png) 102 | 103 | ## 59 Sockets: Internet Domains 104 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_59_1.png) 105 | 106 | ## 63 Alternative I/O Models 107 | ![](https://github.com/zfengzhen/Blog/blob/master/img/lpi_63_1.png) 108 | 109 | -------------------------------------------------------------------------------- /article/《深入探索C++对象模型》读书笔记.md: -------------------------------------------------------------------------------- 1 | # 《深入探索C++对象模型》读书笔记 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ## 第1章 关于对象 5 | - 1、C++在布局以及存取时间上的主要的额外负担是由**virtual**引起的,包括: 6 | - a、virtual function机制,引入**vptr**以及**vtbl**,支持一个有效率的"执行期绑定" 7 | - b、virtual base class,用以实现"多次出现在继承体系中的base class,有一个单一而被共享的实例" 8 | - c、多重继承下,派生类跟第二个以及后续基类之间的转换 9 | - 2、"指针的类型"会教导编译器如何解释某个特定地址中的内存内容以及其大小(void*指针只能够持有一个地址,而不能通过它操作所指向的object) 10 | - 3、**C++通过class的pointers和references来支持多态**,付出的代价就是额外的间接性。它们之所以支持多态是因为它们并不引发内存中任何"与类型有关的内存委托操作(type-dependent commitment)",会受到改变的,只有他们所指向的内存的"大小和内容的解释方式"而已。 11 | 12 | ## 第2章 构造函数语意学 13 | ![](https://github.com/zfengzhen/Blog/blob/master/img/%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0.png) 14 | ![](https://github.com/zfengzhen/Blog/blob/master/img/%E5%A4%8D%E5%88%B6%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0.png) 15 | 16 | ##第3章 Data语意学 17 | - 1、类对象大小受三个因素影响 18 | - a、virtual base和virtual function带来的vptr影响 19 | - b、**EBO**(Empty Base class Optimize)空基类优化处理,**EBC**(Empty Base Class)占用一个字节,其他含有数据成员的从EBC派生的派生类,只会算自己数据成员的大小,不受EBC一字节的影响 20 | - c、alignment 字节对齐 21 | - 2、Nonstatic data members在class object中的排列顺序将和其被声明顺序一样,任何中间介入的static data members都不会被放进布局之中。 22 | - 3、静态成员变量 static data members 23 | - a、存放在程序的data segment之中 24 | - b、通过指针和对象来存取member,完全一样,不管继承或者是虚拟继承得来,全局也只存在唯一一个实例 25 | - c、静态常量成员可以在类定义时直接初始化,而**普通静态常量成员只能在.o编译单元的全局范围内初始化** 26 | - 4、非静态成员变量 nonstatic data members 27 | - a、每一个nonstatic data member的偏移量在编译时即可获知,不管其有多么复杂的派生,都是一样的。通过对象存取一个nonstatic data member,其效率和存取一个C struct member是一样的。 28 | - b、从对象存取obj.x和指针存取pt->x有和差异? 29 | - 当继承链中有虚基类时,查找虚基类的成员变量时延迟到了执行期,根据**virtual class offset**查找到虚基类的部分,效率稍低(成员变量的数据存取并没有this指针的变化) 30 | - 5、成员变量具体分布 (代码引用陈皓 C++对象的内存布局 http://blog.csdn.net/haoel/article/details/3081328 http://blog.csdn.net/haoel/article/details/3081385 分析环境:linux 2.6 gcc) 31 | - ![单一继承的一般继承](https://github.com/zfengzhen/Blog/blob/master/img/%E5%8D%95%E4%B8%80%E7%BB%A7%E6%89%BF%E7%9A%84%E4%B8%80%E8%88%AC%E7%BB%A7%E6%89%BF.png) 32 | - ![多重继承](https://github.com/zfengzhen/Blog/blob/master/img/%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF.png) 33 | - ![重复继承](https://github.com/zfengzhen/Blog/blob/master/img/%E9%87%8D%E5%A4%8D%E7%BB%A7%E6%89%BF.png) 34 | - ![钻石型多重继承](https://github.com/zfengzhen/Blog/blob/master/img/%E9%92%BB%E7%9F%B3%E5%9E%8B%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF.png) 35 | 36 | ##第4章 Function语意学 37 | - 1、C++的设计准则之一:nostatic member function 至少必须和一般的nonmember function有相同的效率。 38 | - a、改写函数原型,在参数中增加this指针 39 | - b、对每一个"nonstatic data member的存取操作"改为由this指针来存取 40 | - c、将member function重写为一个外部函数,经过"mangling"处理(不许要处理的加上 extern "C") 41 | - 2、覆盖(override)、重写(overload)、隐藏(hide)的区别 42 | - **重载**是指**不同的函数使用相同的函数名,但是函数的参数个数或类型不同**。调用的时候根据函数的参数来区别不同的函数。 43 | - **覆盖**(也叫重写)是指**在派生类中重新对基类中的虚函数(注意是虚函数)重新实现**。即函数名和参数都一样,只是函数的实现体不一样。 44 | - **隐藏**是指**派生类中的函数把基类中相同名字的函数屏蔽掉了**。隐藏与另外两个概念表面上看来很像,很难区分,其实他们的关键区别就是在多态的实现上。 45 | - 3、静态成员函数 static member functions 46 | - a、不能访问非静态成员 47 | - b、不能声明为const、volatile或virtual 48 | - c、参数没有this 49 | - d、可以不用对象访问,直接 类名::静态成员函数 访问 50 | - 4、**C++多态(polymorphism)**表示"以一个public base class的指针(或者reference),寻址出一个derived class object" 51 | - 5、vtable虚函数表一定是在编译期间获知的,其函数的个数、位置和地址是固定不变的,完全由编译器掌控,执行期间不允许任何修改。 52 | - 6、vtable的内容: 53 | - a、virtual class offset(有虚基类才有) 54 | - b、topoffset 55 | - c、typeinfo 56 | - d、继承基类所声明的虚函数实例,或者是覆盖(override)基类的虚函数 57 | - e、新的虚函数(或者是纯虚函数占位) 58 | - 7、执行期虚函数调用步骤 59 | - a、通过vptr找到vtbl 60 | - b、通过thunk技术以及topoffset调整this指针(因为成员函数里面可能调用了成员变量) 61 | - c、通过virtual class offset找到虚基类共享部分的成员 62 | - d、执行vtbl中对应slot的函数 63 | - 8、多重继承中,一个派生类会有n-1个额外的vtbl(也可能有n或者n以上个vtbl,看是否有虚基类),它与第一父类共享vtbl,会修改其他父类的vtbl 64 | - 9、函数性能 65 | Inline Member > (Nonmember Friend, Static Member, Nonstatic Member) > Virtual Member > Virtual Member(多重继承) > Virtual Member(虚拟继承) 66 | - 10、Inline对编译器只是请求,并非命令。inline中的局部变量+有表达式参数-->大量临时变量-->程序规模暴涨 67 | 68 | ##第5章 构造、析构、拷贝语意学 69 | - 1、**析构函数不能定义为纯虚函数**,因为每个子类的析构函数都会被编译器扩展调用基类的析构函数以及再上层的基类的析构函数。因为只要缺乏任何一个基类的析构函数的定义,就会导致链接失败。(实际上纯虚函数是可以被定义以及静态调用-类名::函数,但是C++标准为纯虚函数就是代表没有定义) 70 | - 2、继承体系下带有数据成员的构造顺序 71 | - a、虚基类构造函数,从左到右,从顶层到底层。它和非虚基类不同:是由最底层子类调用的。 72 | - b、非虚基类构造函数,按照基类被声明的顺序从左到右调用。它与虚基类不同:是由直接之类调用的。 73 | - c、如果类中有虚表指针,则设置vptr初值,若有新增虚函数或者是覆盖基类虚函数,修改vtbl内容(实际上vtble在编译时,类定义时就已经建立好了) 74 | - d、成员变量以其声明顺序进行初始化构造 75 | - e、构造函数中,用户自定义代码最后被执行 76 | - 3、如果类没有定义析构函数,那么只有在类中含有成员对象或者是基类中含有析构函数的情况下,编译器才会自动合成一个出来 77 | - 4、析构函数的顺序跟构造函数的顺序完全相反,如果是为了多态的析构函数,设置为虚析构函数 78 | - 5、**不要在构造函数和析构函数中调用虚函数** 79 | 80 | 81 | ##第6章 执行期语意学 82 | - 1、**尽量推迟变量定义,避免不必要的构造和析构**(虽然C++编译器会尽量保证在调用变量的时候才进行构造,推迟变量定义会使得代码好阅读) 83 | - 2、全局类变量在编译期间被放置于data段中并被置为0 84 | - GOOGLE C++编程规范:禁止使用class类型的静态或全局变量,只允许使用POD型静态变量(Plain Old Data)和原生数据类型。因为它们会导致很难发现的bug和不确定的构造和析构函数调用顺序 85 | - 解决:改成在static函数中,产生局部static对象 86 | - 3、如果有表达式产生了临时对象,那么应该对完整表达式求值结束之后才摧毁这些创建的临时对象。有两个例外:1)该临时对象被refer为另外一个对象的引用;2)该对象作为另一对象的一部分被使用,而另一对象还没有被释放。 87 | 88 | ##第7章 站在对象模型的尖端 89 | - 1、对于RTTI的支持,在vtbl中增加一个**type_info**的slot 90 | - 2、dynamic_cast比static_cast要花费更多的性能(检查RTTI释放匹配、指针offset等),但是安全性更好。 91 | - 3、对引用施加dynamic_cast:1)成功;或2)抛出bad_cast异常;对指针施加:1)成功;2)返回0指针。 92 | - 4、使用**typeid()**进行判断,合法之后再进行dynamic_cast,这样就能够避免对引用操作导致的bad_cast异常: if(typeid(rt) == typeid(rt2)) …。但是如果rt和rt2本身就是合法兼容的话,就会损失了一次typeid的操作性能。 -------------------------------------------------------------------------------- /article/为互联网应用程序而生的State Threads[常见后台架构浅析].md: -------------------------------------------------------------------------------- 1 | # 为互联网应用程序而生的State Threads[常见后台架构浅析] 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | 本文翻译来自[State Threads介绍](http://state-threads.sourceforge.net/docs/st.html) 5 | 6 | ### Introduction 7 | ### 介绍 8 | 9 | State Threads is an application library which provides a foundation for writing fast and highly scalable Internet Applications on UNIX-like platforms. It combines the simplicity of the multithreaded programming paradigm, in which one thread supports each simultaneous connection, with the performance and scalability of an event-driven state machine architecture. 10 | 11 | State Threads是一个应用库:为性能得更快以及高可伸缩性的类UNIX平台的互联网应用程序提供基础库。它拥有多线程编程范式的简单(一个线程支持一个并发连接)以及事件驱动状态机架构的性能和可伸缩性。 12 | 13 | # 1. Definitions 14 | # 1. 定义 15 | 16 | ## 1.1 Internet Applications 17 | ## 1.1 互联网应用程序 18 | 19 | An Internet Application (IA) is either a server or client network application that accepts connections from clients and may or may not connect to servers. In an IA the arrival or departure of network data often controls processing (that is, IA is a data-driven application). For each connection, an IA does some finite amount of work involving data exchange with its peer, where its peer may be either a client or a server. The typical transaction steps of an IA are to accept a connection, read a request, do some finite and predictable amount of work to process the request, then write a response to the peer that sent the request. One example of an IA is a Web server; the most general example of an IA is a proxy server, because it both accepts connections from clients and connects to other servers. 20 | 21 | 一个*`互联网应用程序(IA)`*是一个客户端或服务器端网络应用,它接收来自客户端的连接,也许也需要连接到服务器。在*IA*中,数据的到达和发送通常控制着处理逻辑(也就是说*IA*是一个数据驱动型应用)。对于每一个连接,一个*IA*做着有限数量的工作,包括跟对等端(可能是一个客户端或者服务器端)交换数据。一个*IA*的典型的处理步骤:接受一个连接,读取一个请求,做一些有限且可预估数量的工作去处理这个请求,然后回包到发送请求的对等端。WEB服务就是一个*IA*的例子,更典型的*IA*例子就是代理服务器,因为它需要接受来自客户端的连接,也需要连接到其他的服务器。 22 | 23 | We assume that the performance of an IA is constrained by available CPU cycles rather than network bandwidth or disk I/O (that is, CPU is a bottleneck resource). 24 | 25 | 我们假定*IA*的性能受限于可用的CPU周期,而不是网络带宽或者是磁盘I/O(也就是说,CPU是瓶颈资源)。 26 | 27 | ## 1.2 Performance and Scalability 28 | ## 1.2 性能和柔性 29 | 30 | The performance of an IA is usually evaluated as its throughput measured in transactions per second or bytes per second (one can be converted to the other, given the average transaction size). There are several benchmarks that can be used to measure throughput of Web serving applications for specific workloads (such as SPECweb96, WebStone, WebBench). Although there is no common definition for scalability, in general it expresses the ability of an application to sustain its performance when some external condition changes. For IAs this external condition is either the number of clients (also known as "users," "simultaneous connections," or "load generators") or the underlying hardware system size (number of CPUs, memory size, and so on). Thus there are two types of scalability: load scalability and system scalability, respectively. 31 | 32 | 评估*IA*是以每秒处理任务数或每秒处理的字节数(如果给出平均的任务大小,其中一个可以转换成另一个)的吞吐量来衡量的。有许多基准测试程序能够用来衡量WEB服务应用的特定负载的吞吐量(比如SPECweb96, WebStone, WebBench)。虽然*`可伸缩性(scalability)`*没有通用的定义,*一般来说它表示应用程序在外部条件变化时维持其性能的能力*。*IA*的外部条件可能是客户端的个数(或者是说用户个数,并发连接数或者负载生成源)或者底层硬件系统的配置(CPU的个数、内存的大小等等)。因此这有两种类型的可伸缩性:*`负载可伸缩性`*和*`系统可伸缩性`*。 33 | (**译注**:scalability翻译成柔性,可伸缩性,表示规模可调整;extensibility翻译成可扩展性,表示应用是否设计得比较好、模块化是否抽象得好,当添加新功能时能够比较容易) 34 | 35 | The figure below shows how the throughput of an idealized IA changes with the increasing number of clients (solid blue line). Initially the throughput grows linearly (the slope represents the maximal throughput that one client can provide). Within this initial range, the IA is underutilized and CPUs are partially idle. Further increase in the number of clients leads to a system saturation, and the throughput gradually stops growing as all CPUs become fully utilized. After that point, the throughput stays flat because there are no more CPU cycles available. In the real world, however, each simultaneous connection consumes some computational and memory resources, even when idle, and this overhead grows with the number of clients. Therefore, the throughput of the real world IA starts dropping after some point (dashed blue line in the figure below). The rate at which the throughput drops depends, among other things, on application design. 36 | 37 | 下图展示了一个理想化的*IA*当客户端数量的增长时(蓝色实线)吞吐量的变化。初始阶段吞吐量的增长表现为线性(斜率表示一个客户端时能个提供的最大吞吐量)。在初始阶段,*IA*未充分利用且CPU有部分闲置。进一步的提高客户端的数量会导致系统饱和,吞吐量逐渐停止增长直到CPU满载。在这个点后,吞吐量保持平坦,因为没有更多的CPU周期可用。在现实的环境中,每一个并发连接(甚至当它空闲时)都要消耗一些计算和内存资源,这些开销随着客户端的数量增长而增长。因此,*IA*的现实环境的吞吐量在客户端数量增长到某个点后开始下降(图中的蓝色虚线)。吞吐量的下降速率取决于应用程序的设计。 38 | 39 | We say that an application has a good load scalability if it can sustain its throughput over a wide range of loads. Interestingly, the SPECweb99 benchmark somewhat reflects the Web server's load scalability because it measures the number of clients (load generators) given a mandatory minimal throughput per client (that is, it measures the server's capacity). This is unlike SPECweb96 and other benchmarks that use the throughput as their main metric (see the figure below). 40 | 41 | 我们设定一个应用程序有优秀的*负载可伸缩性*是指它能够在较大负载范围内维持它的*`吞吐量`*。有趣的是, SPECweb99基准测试程序某种程度反映了WEB服务的*负载可伸缩性*,因为它测量了平均每个客户端强制性最小吞吐量时(**译注**:客户端不卡时的所需要的吞吐量)的客户端的数量(也就是它测量了服务器的能力)。 SPECweb96 和其他基准测试程序主要时用吞吐量来作为它们的主要指标(见下图)。 42 | 43 | ![](https://github.com/zfengzhen/Blog/blob/master/img/%E4%B8%BA%E4%BA%92%E8%81%94%E7%BD%91%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E8%80%8C%E7%94%9F%E7%9A%84State_Threads_fig.gif) 44 | 45 | System scalability is the ability of an application to sustain its performance per hardware unit (such as a CPU) with the increasing number of these units. In other words, good system scalability means that doubling the number of processors will roughly double the application's throughput (dashed green line). We assume here that the underlying operating system also scales well. Good system scalability allows you to initially run an application on the smallest system possible, while retaining the ability to move that application to a larger system if necessary, without excessive effort or expense. That is, an application need not be rewritten or even undergo a major porting effort when changing system size. 46 | 47 | *系统可伸缩性*是指应用程序每单位硬件(比如CPU)能够增加的性能。换句话说,优秀的系统可伸缩性意味着增加一倍的处理器能够大致使得应用程序的吞吐量增加一倍(绿色虚线)。我们假定底层的操作系统也有不错的可伸缩性。优秀的系统可伸缩性允许你在一个最小的系统配置上运行应用程序的可能,同时保留在有必要时不花费过多的力气去高系统配置上运行的能力。也就是说,当改变系统配置时应用程序不需要重写以及移植工作。 48 | 49 | Although scalability and performance are more important in the case of server IAs, they should also be considered for some client applications (such as benchmark load generators). 50 | 51 | 虽然可伸缩性和性能在*IA*上更重要,客户端程序(比如基准测试负载生成源)也需要考虑。 52 | 53 | ## 1.3 Concurrency 54 | ## 1.3 并发 55 | 56 | Concurrency reflects the parallelism in a system. The two unrelated types are virtual concurrency and real concurrency. 57 | 58 | 并发反映了系统的并行性。有两种不太相关的类型:*`虚拟并发`*和*`物理并发`*。 59 | 60 | • Virtual (or apparent) concurrency is the number of simultaneous connections that a system supports. 61 | • Real concurrency is the number of hardware devices, including CPUs, network cards, and disks, that actually allow a system to perform tasks in parallel. 62 | • 虚拟并发是指系统同时支持的连接数。 63 | • 物理并发是指硬件设备的数量,包括CPU、网卡、和硬盘,允许系统真正意义上并行的执行任务。 64 | 65 | An IA must provide virtual concurrency in order to serve many users simultaneously. To achieve maximum performance and scalability in doing so, the number of programming entities than an IA creates to be scheduled by the OS kernel should be kept close to (within an order of magnitude of) the real concurrency found on the system. These programming entities scheduled by the kernel are known as kernel execution vehicles. Examples of kernel execution vehicles include Solaris lightweight processes and IRIX kernel threads. In other words, the number of kernel execution vehicles should be dictated by the system size and not by the number of simultaneous connections. 66 | 67 | 一个*IA*必须提供虚拟并发去同时服务许多用户。为了达到最大的性能和可伸缩性,*IA*创建的被操作系统调度的编程实体应该和系统的物理并发保持一致。被内核调度的编程实体叫做*`内核执行载体`*。内核执行载体的例子包括Solaris lightweight进程和IRIX内核线程。换一种说法,**内核执行载体的数量应该由系统的硬件决定而不是并发连接数**。(进程数由CPU决定而不是并发连接数) 68 | 69 | # 2. Existing Architectures 70 | # 2. 现有的架构 71 | 72 | There are a few different architectures that are commonly used by IAs. These include the Multi-Process, Multi-Threaded, and Event-Driven State Machine architectures. 73 | 74 | *IA*有一些不同的常用架构。包括多进程架构、多线程架构和事件驱动状态机架构。 75 | 76 | ## 2.1 Multi-Process Architecture 77 | ## 2.1 多进程架构 78 | 79 | In the Multi-Process (MP) architecture, an individual process is dedicated to each simultaneous connection. A process performs all of a transaction's initialization steps and services a connection completely before moving on to service a new connection. 80 | 81 | 在多进程架构中,一个独立的进程用来服务一个并发连接。一个进程执行所有任务的初始化步骤,并在服务一个新的连接前完全服务于上一个连接。 82 | 83 | User sessions in IAs are relatively independent; therefore, no synchronization between processes handling different connections is necessary. Because each process has its own private address space, this architecture is very robust. If a process serving one of the connections crashes, the other sessions will not be affected. However, to serve many concurrent connections, an equal number of processes must be employed. Because processes are kernel entities (and are in fact the heaviest ones), the number of kernel entities will be at least as large as the number of concurrent sessions. On most systems, good performance will not be achieved when more than a few hundred processes are created because of the high context-switching overhead. In other words, MP applications have poor load scalability. 84 | 85 | *IA*的用户*`会话(session)`*相对独立;进程处理不同连接时,没有*`数据同步`*的必要。因为每个进程都有它自己的地址空间,这个架构非常健壮。如果服务一个连接的进程崩溃了,其他的会话不会被影响。然而,为了服务许多并发的连接,将创建同等数量的进程。由于进程是内核实体(并且实际上是最重的一种),为了使得服务正常,内核实体的数量至少要比并发会话的数量大。在大多数系统中,由于上下文切换的开销,创建成百上千的进程时将不会有优秀的性能。换句话说,多进程应用程序负载可伸缩性较低。 86 | 87 | On the other hand, MP applications have very good system scalability, because no resources are shared among different processes and there is no synchronization overhead. 88 | 89 | 另外一方面,多进程应用程序拥有优秀的系统可伸缩性,由于不同的进程没有资源共享,没有*数据同步*的开销。 90 | 91 | The Apache Web Server 1.x ([Reference 1]) uses the MP architecture on UNIX systems. 92 | 93 | Apache服务器1.x在UNIX系统中采用多进程架构。 94 | 95 | ## 2.2 Multi-Threaded Architecture 96 | ## 2.2 多线程架构 97 | 98 | In the Multi-Threaded (MT) architecture, multiple independent threads of control are employed within a single shared address space. Like a process in the MP architecture, each thread performs all of a transaction's initialization steps and services a connection completely before moving on to service a new connection. 99 | 100 | 在多线程架构中,多个独立线程共享一个地址空间。就像在多进程架构中的进程,每个线程执行所有的任务初始化步骤并且在服务下一个连接前完全服务于上一个连接。 101 | 102 | Many modern UNIX operating systems implement a many-to-few model when mapping user-level threads to kernel entities. In this model, an arbitrarily large number of user-level threads is multiplexed onto a lesser number of kernel execution vehicles. Kernel execution vehicles are also known as virtual processors. Whenever a user-level thread makes a blocking system call, the kernel execution vehicle it is using will become blocked in the kernel. If there are no other non-blocked kernel execution vehicles and there are other runnable user-level threads, a new kernel execution vehicle will be created automatically. This prevents the application from blocking when it can continue to make useful forward progress. 103 | 104 | 大多数的现在UNIX操作系统实现了一个多对一的模型,用来映射用户态线程空间到*内核实体*。在这种模型中,任意数量的用户态线程复用到较少数量的*内核执行载体*。*内核执行载体*也被称为虚拟处理器。当一个用户态线程执行了一个阻塞的系统调用,内核执行载体将在内核中阻塞。如果没有其他非阻塞的内核执行载体,并且此时有其他已经就绪的用户态线程,一个新的*内核执行载体*将会自动创建。这样防止应用程序阻塞。 105 | 106 | Because IAs are by nature network I/O driven, all concurrent sessions block on network I/O at various points. As a result, the number of virtual processors created in the kernel grows close to the number of user-level threads (or simultaneous connections). When this occurs, the many-to-few model effectively degenerates to a one-to-one model. Again, like in the MP architecture, the number of kernel execution vehicles is dictated by the number of simultaneous connections rather than by number of CPUs. This reduces an application's load scalability. However, because kernel threads (lightweight processes) use fewer resources and are more light-weight than traditional UNIX processes, an MT application should scale better with load than an MP application. 107 | 108 | 由于*IA*性质上是网络I/O驱动,所有并发会话会阻塞在网络I/O的各个地方。因此,在内核中创建的虚拟处理器的个数接近于用户态的线程数(或者并发连接数)。这样的话,多对一模型退化为一对一模型。就像在多进程架构中,*内核执行载体*的数量取决于并发连接数,而不是CPU数量。这样减少了应用程序的负载可伸缩性。然后,由于内核线程使用较少的资源并且比传统的UNIX进程更轻,一个多线程应用程序比多进程应用程序在负载可伸缩性上更好。 109 | 110 | Unexpectedly, the small number of virtual processors sharing the same address space in the MT architecture destroys an application's system scalability because of contention among the threads on various locks. Even if an application itself is carefully optimized to avoid lock contention around its own global data (a non-trivial task), there are still standard library functions and system calls that use common resources hidden from the application. For example, on many platforms thread safety of memory allocation routines (malloc(3), free(3), and so on) is achieved by using a single global lock. Another example is a per-process file descriptor table. This common resource table is shared by all kernel execution vehicles within the same process and must be protected when one modifies it via certain system calls (such as open(2), close(2), and so on). In addition to that, maintaining the caches coherent among CPUs on multiprocessor systems hurts performance when different threads running on different CPUs modify data items on the same cache line. 111 | 112 | 不幸的是,小部分虚拟处理器在多线程架构中共享相同的地址破坏了应用的*系统可伸缩性*,因为线程的竞争导致争抢各种锁。甚至应用程序花心思去优化,避免连接在全局变量有锁竞争,但是仍然有标准库函数和系统调用访问隐藏在应用程序中的相同的资源。比如,在大多数平台,线程的内存分配函数(malloc(3),free(3)等)都是用了一个单独的全局锁。另一个例子是进程的文件描述表。这个通用的资源表在同一个进程中被所有的*内核执行载体*共享,当通过特定的系统调用(比如open(2), close(2)等)修改它时都需要被保护。除此之外,多处理器系统中维持缓存的一致性,当不同的线程跑在不同的CPU上却在相同的cache line中修改数据都会严重降低性能。 113 | 114 | In order to improve load scalability, some applications employ a different type of MT architecture: they create one or more thread(s) per task rather than one thread per connection. For example, one small group of threads may be responsible for accepting client connections, another for request processing, and yet another for serving responses. The main advantage of this architecture is that it eliminates the tight coupling between the number of threads and number of simultaneous connections. However, in this architecture, different task-specific thread groups must share common work queues that must be protected by mutual exclusion locks (a typical producer-consumer problem). This adds synchronization overhead that causes an application to perform badly on multiprocessor systems. In other words, in this architecture, the application's system scalability is sacrificed for the sake of load scalability. 115 | 116 | 为了提高*负载可伸缩性*,一些应用程序采用了不同类型的多线程架构:为每个任务创建一个或更多的线程,而不是每个连接去创建一个线程。比如,一个小的线程组用于接收客户端连接,一些线程组负责请求处理,另外一些线程组处理回包。这个架构的最主要优点是解除了线程数量和并发连接数之间的紧密耦合。然而,在这种架构中,不同的特定任务线程组必须共享共同的工作队列,他们必须被互斥锁保护(典型的生产者-消费者问题)。这样添加了*数据同步*的开销导致应用程序在多处理器系统中执行很糟糕。换句话,在这种架构中,应用程序的用系统可伸缩性换取了负载可伸缩性。 117 | 118 | Of course, the usual nightmares of threaded programming, including data corruption, deadlocks, and race conditions, also make MT architecture (in any form) non-simplistic to use. 119 | 120 | 当然,多线程编程的通常的噩梦,包括数据破坏,死锁,和竞态条件,并且使得多线程架构的使用不是那么简单。 121 | 122 | ## 2.3 Event-Driven State Machine Architecture 123 | ## 2.3 事件驱动状态机架构 124 | 125 | In the Event-Driven State Machine (EDSM) architecture, a single process is employed to concurrently process multiple connections. The basics of this architecture are described in Comer and Stevens [Reference 2]. The EDSM architecture performs one basic data-driven step associated with a particular connection at a time, thus multiplexing many concurrent connections. The process operates as a state machine that receives an event and then reacts to it. 126 | 127 | 在*`事件驱动状态机架构(EDSM)`*中,一个进程被用来并发处理多个连接。架构的基本概念在Comer和Stevens [Reference 2]中有描述。*EDSM*在同一时间内特定连接中的一个基本的数据驱动步骤,因此可以复用许多并发的连接。进程作为一个*状态机*,收到一个事件然后对其做出反应。 128 | 129 | In the idle state the EDSM calls select(2) or poll(2) to wait for network I/O events. When a particular file descriptor is ready for I/O, the EDSM completes the corresponding basic step (usually by invoking a handler function) and starts the next one. This architecture uses non-blocking system calls to perform asynchronous network I/O operations. For more details on non-blocking I/O see Stevens [Reference 3]. 130 | 131 | 在*EDSM*的空闲状态,调用select(2)或者poll(2)等待网络I/O事件。当一个特定的fd可以读写时,*EDSM*完成相应的基本步骤(通常是调用一个处理函数)并开始下一个。这个架构使用非阻塞的系统调用去执行异步的网络I/O操作。更多关于非阻塞I/O的详细结束参见Stevens [Reference 3]。 132 | 133 | To take advantage of hardware parallelism (real concurrency), multiple identical processes may be created. This is called Symmetric Multi-Process EDSM and is used, for example, in the Zeus Web Server ([Reference 4]). To more efficiently multiplex disk I/O, special "helper" processes may be created. This is called Asymmetric Multi-Process EDSM and was proposed for Web servers by Druschel and others [Reference 5]. 134 | 135 | 利用*`硬件的并行性`*(物理并行),可以创建多个相同的进程。这个被称为*`对称多进程EDSM架构`*,例如,Zeus Web服务([Reference 4])。为了更有效的复用磁盘I/O,可以创建“辅助”进程。这个称为*`非对称多进程EDSM架构`*,在Drushchel的WEB服务器和其他的一些服务被提出[Reference 5]。 136 | 137 | EDSM is probably the most scalable architecture for IAs. Because the number of simultaneous connections (virtual concurrency) is completely decoupled from the number of kernel execution vehicles (processes), this architecture has very good load scalability. It requires only minimal user-level resources to create and maintain additional connection. 138 | 139 | *EDSM*可能是互联网应用中最可伸缩的架构。由于并发连接(虚拟并发)的数量完全脱离内核执行载体(进程)的数量,这个架构有最好的负载可伸缩性,它仅需要最少的用户态资源去创建和维持额外的连接。 140 | 141 | Like MP applications, Multi-Process EDSM has very good system scalability because no resources are shared among different processes and there is no synchronization overhead. 142 | 143 | 像多进程架构应用一样,多进程*EDSM*有很好的系统可伸缩性,由于不同的进程没有共享资源,没有*数据同步*开销。 144 | 145 | Unfortunately, the EDSM architecture is monolithic rather than based on the concept of threads, so new applications generally need to be implemented from the ground up. In effect, the EDSM architecture simulates threads and their stacks the hard way. 146 | 147 | 不幸的是,*EDSM*是一套新的整体,而不是基于线程的概念,所以新的应用程序需要从头开始实现。实际上,*EDSM*采用很复杂的方式模拟线程和它们的栈。 148 | 149 | # 3. State Threads Library 150 | # 3. State Threads库 151 | 152 | The State Threads library combines the advantages of all of the above architectures. The interface preserves the programming simplicity of thread abstraction, allowing each simultaneous connection to be treated as a separate thread of execution within a single process. The underlying implementation is close to the EDSM architecture as the state of each particular concurrent session is saved in a separate memory segment. 153 | 154 | State Threads库采用了上面架构的所有优点。接口保持了和线程编程的简单,允许每一个并发连接就像在进程中的一个单独的线程处理一样。底层的实现就接近于*EDSM*,每个特定的并发会话状态存储于一个隔离的内存段。 155 | 156 | ## 3.1 State Changes and Scheduling 157 | ## 3.1 状态改变和调度 158 | 159 | The state of each concurrent session includes its stack environment (stack pointer, program counter, CPU registers) and its stack. Conceptually, a thread context switch can be viewed as a process changing its state. There are no kernel entities involved other than processes. Unlike other general-purpose threading libraries, the State Threads library is fully deterministic. The thread context switch (process state change) can only happen in a well-known set of functions (at I/O points or at explicit synchronization points). As a result, process-specific global data does not have to be protected by mutual exclusion locks in most cases. The entire application is free to use all the static variables and non-reentrant library functions it wants, greatly simplifying programming and debugging while increasing performance. This is somewhat similar to a co-routine model (co-operatively multitasked threads), except that no explicit yield is needed -- sooner or later, a thread performs a blocking I/O operation and thus surrenders control. All threads of execution (simultaneous connections) have the same priority, so scheduling is non-preemptive, like in the EDSM architecture. Because IAs are data-driven (processing is limited by the size of network buffers and data arrival rates), scheduling is non-time-slicing. 160 | 161 | 每个并发会话的状态包括它的*`栈环境(栈指针,程序计数器,CPU寄存器)和栈`*。从概念上讲,一个线程的上下文切换可以视为进程改变状态。除了进程之外没有调用任何*内核实体*。不像通常意义上的线程库,State Threads库是完全自我控制的。线程的上下文切换(进程的状态)只能在明确的一些函数中触发(比如I/O操作和明确的数据同步)。这样的话,进程的全局变量在大部分情况下不需要被互斥锁保护。整个应用程序可以随意使用所有的静态变量和不可重入的库函数,在增加性能的同时极大的简化编程和调试。这个其实和*`协程模型`*类似,除了不需要显示的调用yield,线程执行一个阻塞的I/O操作并交出控制权。所有的线程的执行(并发连接)有相同的优先级,因此调度是无优先的,就像*EDSM*。由于*IA*是事件驱动(处理受限于网络包的大小以及数据到达的速率),不按时间切片去调度。 162 | 163 | Only two types of external events are handled by the library's scheduler, because only these events can be detected by select(2) or poll(2): I/O events (a file descriptor is ready for I/O) and time events (some timeout has expired). However, other types of events (such as a signal sent to a process) can also be handled by converting them to I/O events. For example, a signal handling function can perform a write to a pipe (write(2) is reentrant/asynchronous-safe), thus converting a signal event to an I/O event. 164 | 165 | 只有两种外部事件会被库的调度器处理,因为它们能够被select(2)或者poll(2)捕获:I/O事件(I/O的fd准备就绪)和时间事件(一些超时被触发)。然而,其他的一些事件(比如有信号发送给进程)也能把它们转换为I/O事件。比如,一个信号处理函数能够执行管道的写入(write(2)可重入且也是异步安全的),因此转换信号事件为I/O事件。 166 | 167 | To take advantage of hardware parallelism, as in the EDSM architecture, multiple processes can be created in either a symmetric or asymmetric manner. Process management is not in the library's scope but instead is left up to the application. There are several general-purpose threading libraries that implement a many-to-one model (many user-level threads to one kernel execution vehicle), using the same basic techniques as the State Threads library (non-blocking I/O, event-driven scheduler, and so on). For an example, see GNU Portable Threads ([Reference 6]). Because they are general-purpose, these libraries have different objectives than the State Threads library. The State Threads library is not a general-purpose threading library, but rather an application library that targets only certain types of applications (IAs) in order to achieve the highest possible performance and scalability for those applications. 168 | 169 | 在*EDSM*中利用硬件的并行性,多进程能够创建对称或者不对称的方式。进程管理不在库的范围内,把它留给了应用程序。一些通用的线程库实现了多对一模型(多个用户态线程在一个*内核执行载体*上),State Threads库也利用相同的技术(非阻塞I/O,事件驱动调度器等等)。比如,GNU Protable Threads([Reference 6])。因为它们都是通用的,所以它们和State Threads库的目标不一样,State Threads库是一个应用库,目标是使得确定类型的*IA*获取最高的性能和柔性。 170 | 171 | ## 3.2 Scalability 172 | ## 3.2 可伸缩性 173 | 174 | State threads are very lightweight user-level entities, and therefore creating and maintaining user connections requires minimal resources. An application using the State Threads library scales very well with the increasing number of connections. 175 | 176 | State threads是在用户态上,非常轻量,因此创建和维持用户连接需要非常小的资源。使用State Threads库的应用程序能够在连接数的增加时处理得非常好。 177 | 178 | On multiprocessor systems an application should create multiple processes to take advantage of hardware parallelism. Using multiple separate processes is the only way to achieve the highest possible system scalability. This is because duplicating per-process resources is the only way to avoid significant synchronization overhead on multiprocessor systems. Creating separate UNIX processes naturally offers resource duplication. Again, as in the EDSM architecture, there is no connection between the number of simultaneous connections (which may be very large and changes within a wide range) and the number of kernel entities (which is usually small and constant). In other words, the State Threads library makes it possible to multiplex a large number of simultaneous connections onto a much smaller number of separate processes, thus allowing an application to scale well with both the load and system size. 179 | 180 | 在多处理器系统中,一个应用程序应该利用硬件的并行性可多开启几个进程。使用多个独立的进程能够达到最高可能的系统可伸缩性。在多处理器系统中阻止数据同步开销的最有效的办法是复制每一个进程的资源。创建独立的UNIX进程原生提供资源复制。再次强调,在*EDSM*中,并发数(可能非常巨大,并且改变的范围很大)和*内核实体*(通常比较小)没有直接联系。换句话来说,State Threads库可以使得非常多的并发连接复用到较小的独立进程上,因此应用能够在负载可伸缩性和系统可伸缩性上处理得比较好。 181 | 182 | ## 3.3 Performance 183 | ## 3.3 性能 184 | 185 | Performance is one of the library's main objectives. The State Threads library is implemented to minimize the number of system calls and to make thread creation and context switching as fast as possible. For example, per-thread signal mask does not exist (unlike POSIX threads), so there is no need to save and restore a process's signal mask on every thread context switch. This eliminates two system calls per context switch. Signal events can be handled much more efficiently by converting them to I/O events (see above). 186 | 187 | 性能是这个库的主要目标之一。State Threads库的实现最小化系统调用的次数,并且使得线程的创建和上下文的切换尽可能的快。比如,每个线程的信号掩码都不存在(不像POSIX线程)。因此不需要在线程切换时保存和恢复信号掩码。每次上下文切换时减少了两次系统调用。信号事件通过转换成I/O事件处理起来更加有效率。 188 | 189 | ## 3.4 Portability 190 | ## 3.4 移植性 191 | 192 | The library uses the same general, underlying concepts as the EDSM architecture, including non-blocking I/O, file descriptors, and I/O multiplexing. These concepts are available in some form on most UNIX platforms, making the library very portable across many flavors of UNIX. There are only a few platform-dependent sections in the source. 193 | 194 | 库使用了跟*EDSM*相同的基本概念,包括非阻塞I/O,fd,和I/O复用。这些概念都以某种形态存在在大多数UNIX平台上,是得库在各种风格的UNIX上非常容易移植。在代码中只有极少平台相关特性。 195 | 196 | ## 3.5 State Threads and NSPR 197 | ## 3.5 State Threads库和NSPR 198 | 199 | The State Threads library is a derivative of the Netscape Portable Runtime library (NSPR) [Reference 7]. The primary goal of NSPR is to provide a platform-independent layer for system facilities, where system facilities include threads, thread synchronization, and I/O. Performance and scalability are not the main concern of NSPR. The State Threads library addresses performance and scalability while remaining much smaller than NSPR. It is contained in 8 source files as opposed to more than 400, but provides all the functionality that is needed to write efficient IAs on UNIX-like platforms. 200 | 201 | State Threads库是从Netscape Portable Runtime library (NSPR) [Reference 7]发展而来。NSPR的最主要的目标是为系统工具提供一个平台无关层,系统工具包括线程、线程同步,以及I/O。性能和可伸缩性不是NSPR的主要关心的问题。State Threads库解决了性能和可伸缩性的问题,当时比NSPR小得多。它包含了8个源文件而不是NSPR的400个,却在类UNIX平台上提供了编写高效*IA*的所有功能。 202 | 203 | NSPR State Threads 204 | Lines of code ~150,000 ~3000 205 | Dynamic library size 206 | (debug version) 207 | IRIX ~700 KB ~60 KB 208 | Linux ~900 KB ~70 KB 209 | 210 | # Conclusion 211 | # 结论 212 | 213 | State Threads is an application library which provides a foundation for writing Internet Applications. To summarize, it has the following advantages: 214 | 215 | State Threads是一个提供了编写互联网应用程序的基础应用库。包含了以下优点: 216 | 217 | • It allows the design of fast and highly scalable applications. An application will scale well with both load and number of CPUs. 218 | • It greatly simplifies application programming and debugging because, as a rule, no mutual exclusion locking is necessary and the entire application is free to use static variables and non-reentrant library functions. 219 | 220 | • 它允许设计快速和高可伸缩性的应用程序。应用程序能够在负载可伸缩性和系统伸缩性上同时处理得很好。 221 | • 由于没有互斥锁简化了编程和调试,整个应用可以随意的使用静态变量和不可重入的库函数。 222 | 223 | The library's main limitation: 224 | 225 | 库的主要限制: 226 | 227 | • All I/O operations on sockets must use the State Thread library's I/O functions because only those functions perform thread scheduling and prevent the application's processes from blocking. 228 | • 所有在socket上的I/O操作必须使用State Thread库提供的I/O函数,因为只有这些函数执行线程调度时能够防止应用程序的进程阻塞。 229 | 230 | ###**References** 231 | 232 | 1. Apache Software Foundation, http://www.apache.org. 233 | 2. Douglas E. Comer, David L. Stevens, Internetworking With TCP/IP, Vol. III: Client-Server Programming And Applications, Second Edition, Ch. 8, 12. 234 | 3. W. Richard Stevens, UNIX Network Programming, Second Edition, Vol. 1, Ch. 15. 235 | 4. Zeus Technology Limited, http://www.zeus.co.uk. 236 | 5. Peter Druschel, Vivek S. Pai, Willy Zwaenepoel, Flash: An Efficient and Portable Web Server. In Proceedings of the USENIX 1999 Annual Technical Conference, Monterey, CA, June 1999. 237 | 6. GNU Portable Threads, http://www.gnu.org/software/pth/. 238 | 7. Netscape Portable Runtime, http://www.mozilla.org/docs/refList/refNSPR/. 239 | Other resources covering various architectural issues in IAs 240 | 8. Dan Kegel, The C10K problem, http://www.kegel.com/c10k.html. 241 | 9. James C. Hu, Douglas C. Schmidt, Irfan Pyarali, JAWS: Understanding High Performance Web Systems, http://www.cs.wustl.edu/~jxh/research/research.html. 242 | 243 | 244 | # 学习总结 245 | 246 | `负载可伸缩性`: 它能够在较大负载(并发连接数)范围内维持它的吞吐量,如果负载依赖的系统资源越多(比如进程、线程),那么其负载可伸缩性越低。 247 | `系统可伸缩性`: 应用程序每单位硬件(比如CPU)能够增加的性能。如果任务之间的数据同步越多,那么其系统可伸缩性越低,因为很多时候都等待在锁上,物理资源没有得到充分利用。 248 | `代码可读性`: 指编写以及阅读代码的复杂程度,多进程架构符合人类线性思考模式,可读性最强;多线程架构增加了同步以及互斥,可读性稍差;EDSM是非阻塞,所有代码不是线性模式,来回切换,可读性最不好。 249 | 250 | 负载可伸缩性: EDSM > 多线程架构 > 多进程架构 251 | 系统可伸缩性: EDSM > 多进程架构 > 多线程架构 252 | 代码可读性: 多进程架构 > 多线程架构 > EDSM 253 | 254 | State Threads提出了一种既有高可伸缩性以及代码可读性较好的一种模式,实现了用户态的线程,也就是`协程`的概念。 255 | `协程`是个好东东,像同步程序一样写异步server,后续要多多学习下。 -------------------------------------------------------------------------------- /article/内存对象池一对多关系处理.md: -------------------------------------------------------------------------------- 1 | # 内存对象池一对多关系处理 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | 游戏服务器经常会涉及到各种对象, 这些对象一般都由对象内存池分配, 为了使得重启数据仍然存在, 内存对象池在服务器中采用共享内存实现. 5 | 6 | 经常会碰到一个问题, 一个Player对象, 可能拥有好多其他对象. 比如一个玩家可能有多个建筑物对象, 有多个物品对象. 在程序中如何去实现一个Player对象拥有多个其他的对象, 且使得程序在重启后能够直接恢复这种拥有关系. 7 | ![image](https://github.com/zfengzhen/Blog/blob/master/img/one_obj_has_many_other_obj.jpg) 8 | 9 | ### 两个问题: 10 | ### **问题1** 11 | 如何使得一个Player对象拥有多个其他对象, 比如Building对象, Item对象? 12 | ### **问题2** 13 | 这进程coredump后, 重启后加载, 仍然能够恢复这层拥有关系. 14 | 15 | ### 假设: 16 | ShmPool为Player对象的对象内存池, 每次都是分配一个大于0的内存池索引, 用来标识该Player对象. 17 | 同样, ShmPool和ShmPool分别为Building对象和Item对象的对象内存池, 且都拥有一个字段标识是属于哪个Player对象, 这里采用Player对象的内存池索引. 18 | 19 | ### 方法1: 20 | Player对象中不去描述这种拥有关系, 而采用一个类似于全局map的动态内存方式, 比如std::map>这种方式, key为Player对象的内存池索引, 而value为Player对象拥有的所有Building对象. 这样可以解决**问题1**. 21 | 因为map是动态内存, 进程重启后, 数据消失, 这样只能在重启的时候, 遍历Building对象内存池, 将这层关系恢复出来, **问题2**也得到解决. 22 | 23 | ### 方法2: 24 | Player对象中分配一个所拥有对象的数组, 比如m_building_list[MAX_BUILDING_NUM+1]用来存储建筑物对象的内存池索引, 0为未使用, 其他为存储的建筑物对象内存池索引. 通过m_building_list我们可以查找到Player对象所拥有的所有建筑物对象, 因为通过建筑物对象的内存索引可以到建筑物对象的内存池中获取到具体数据. **问题1**解决. 25 | 因为拥有对象关系存在Player对象中, Player对象本身保存在共享内存池中, 所以**问题2**也解决, 而且不需要额外操作. 26 | 27 | 在项目中, 采用了第二种做法. 28 | 29 | ### 遇到的后续问题: 30 | ###1 31 | Player对象拥有许多其他的对象, 每个对象都需要有以下的方法. 32 | 33 | ```c 34 | get_free_inst_id() // 在m_building_list中获取空位 35 | add_inst() // 添加一个新的对象 36 | get_inst() // 根据inst_id获取对象指针 37 | del_inst() // 根据inst_id删除对象 38 | ``` 39 | 40 | 对于每个对象都得写一堆几乎一样的方法, 目前想着的一个办法是: 上述方法只各写一个, 但是传入参数都加一个枚举值, 比如get_free_inst_id(E_BUILDING)是获取建筑物对象的空闲的实例ID, get_inst(E_BUILDING, inst_id)根据inst_id的值, 获取建筑物对象. 41 | 42 | ###2 43 | 对于每一个对象的管理都有一个类去完成. 比如Player对象有PlayerMgrModule去管理, Building有BuildingMgrModule去管理, Item有ItemMgrModule去管理. 实际上这些管理类中的方法也几乎相同. **我们可以把所有的管理类合并, 统一由一个ObjMgrModule去管理**. 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /article/单进程异步模型与多进程同步模型对比.md: -------------------------------------------------------------------------------- 1 | #单进程异步模型与多进程同步模型对比 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ## 单进程异步模型 5 | 主循环在很多回调点进行事件处理, 比如是否有网络包到来, 是否有共享内存消息到来, 是否有封装好的异步api的到来等, 有相应事件, 进行相应的处理, 碰到阻塞的操作(比如网络收发包, 数据库操作, 读写磁盘文件等), 异步处理, 释放CPU, 回到主循环. 6 | 7 | ## 多进程同步模型 8 | 单进程接收事件, 进行相应的处理, 碰到阻塞操作, 通过select进行同步发送, 以及接收, select设置超时时间. 9 | 为了充分利用CPU, 开启多个上述相同的进程进行处理. 10 | 11 | ## 性能 12 | 假设单个事件中分为两大部分, 非阻塞操作(我们认为就是一些常用的系统调用, 以及读写内存操作)和阻塞操作(网络收发包, 数据库操作, 读写磁盘文件等). 13 | 非阻塞操作耗时为x 14 | 阻塞操作耗时为y 15 | 16 | 理想情况下 17 | 单进程异步模型的性能为: 1/x 次/s 18 | 多进程同步模型的性能为: n/(x+y) 次/s (n为进程数) 19 | 20 | 如果多进程同步模型的性能要好于单进程异步模型的性能, 则: 21 | n/(x+y) > 1/x 22 | y < (n-1)x 23 | 也就是说阻塞操作的耗时要小于(n-1)倍非阻塞操作时间 24 | 非阻塞操作中系统调用基本上都能达到每秒百万级别. 1/1000000 ~= 1ms 25 | 内存的带宽为20G 26 | 假设任务中要写1M的数据, 1M/20G ~= 0.05ms 27 | 28 | 一次网络收发包设置的select超时为100ms, 实际情况下假如小于100ms, 也按100ms算, 因为最坏情况下会达到100ms 29 | 30 | 100ms < (n-1)1ms 31 | 则n > 101个, 开启进程数要大于101个, 才能达到单进程异步的性能. 32 | 这只是理性情况下, 一个事件中只有一次网络收发包的情况, 实际情况下可能会有多次网络收发包的情况, 这样开启进程数要更多, 开启进程数越多的同时, 也会产生内耗, 导致性能的下降, 只能通过压测去评估最佳性能. 33 | 34 | ## 柔性 35 | 多进程同步模型中事件的超时时间取决于每个阻塞操作设置的超时时间, 某个阻塞操作超时了, 则整个事件超时, 柔性并不是很好. 遇到网络抖动的情况, 就全部超时. 36 | 单进程异步模型中, 一般都会采用事件超时定时器, 超时是关联整个事件的, 只能整个事件超时才是超时, 柔性更强. 37 | 38 | 39 | -------------------------------------------------------------------------------- /article/后台常用系统调用性能总结.md: -------------------------------------------------------------------------------- 1 | #后台常用系统调用性能总结 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | >> CPU: Intel Xeon 2.13GHz 5 | >> 内存: 64G 6 | >> 网卡: 千兆 7 | 8 | ## 存储相关 9 | ### 存储性能运行上下文 10 | 1 顺序读写open开启O_APPEND, 采用read/write系统调用, 屏蔽stdio库fread/fwrite中的stdio缓存影响 11 | 2 随机读写通过生成随机序列数组, 采用pread/pwrite进行系统调用, 按随机序列进行随机访问 12 | 3 硬盘写存在两种情况, 写kernel cache以及直写硬盘. 直写硬盘通过O_DIRECT选项 13 | 4 硬盘读也存在两种情况, 有kernel cache以及直读硬盘. 直读硬盘, 通过posix_fadvise排除缓存对磁盘读的影响 14 | 5 测试数据量为400M, 分别按4KB, 512B(一个网络请求包大小), 128B, 8B(一个uint64_t大小)数据进行写入 15 | 6 单进程测试 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 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
调用方式数据块大小性能
[内存][顺序][写]4KB1643.37M/s
[内存][随机][写]4KB1428.54M/s
[内存][顺序][写]512B812.32M/s
[内存][随机][写]512B493.95M/s
[内存][顺序][写]128B812.32M/s
[内存][随机][写]128B175.23M/s
[内存][顺序][写]8B23.63M/s
[内存][随机][写]8B13.23M/s
[硬盘][顺序][写][有kernel cache]4KB347.67M/s
[硬盘][顺序][写][无kernel cache]4KB44.42M/s
[硬盘][随机][写][无kernel cache]4KB3.57M/s
[硬盘][顺序][写][有kernel cache]512B151.47M/s
[硬盘][顺序][写][无kernel cache]512B26.36M/s
[硬盘][顺序][写][有kernel cache]128B86.19M/s
[硬盘][顺序][写][无kernel cache]128B46.61M/s
[硬盘][顺序][读][无kernel cache]4KB45.24M/s
[硬盘][顺序][读][有kernel cache]4KB3605.59M/s
[硬盘][随机][读][无kernel cache]4KB1.04M/s
[硬盘][随机][读][有kernel cache]4KB43.83M/s
120 | 121 | ### 存储性能总结 122 | 存储可以从物理带宽理解数据处理能力. 123 | 带宽=每次数据块大小*传输频率(每秒传输次数) 124 | 以下都是大概值: 125 | 千兆网卡IO带宽: 100MB/s 126 | 硬盘IO带宽: 几百MB/s 127 | 内存IO带宽: 20GB/s 128 | 在数据块较小时, 传输次数也高, 传输时内耗也大, 导致带宽利用率不高. 129 | 所以在磁盘和网络中要尽量避免小数据量传输, 可以通过合并数据到一定量后统一处理. 130 | 131 | ## 常见系统调用性能数据 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
系统调用性能
open + close1093160.21 (109W)次/s
gettimeofday1453568.66 (145W)次/s
socket + close578356.96 (58W)次/s
每次初始化一个socket, 发送512B udp数据并在同一台机器上接收203013.37 (20W)次/s
初始化socket仅一次, 发送512B udp数据并在同一台机器上接收451723.21 (45W)次/s
158 | 159 | 进程间通信对比 160 | 1 UDP通信 161 | socket仅初始化一次, 通信采用sendto给其他进程, 请求包量为512B, 处理能力为45W次/s左右 162 | 优点: 163 | 简单, 扩展性好(假如哪天某个进程要移到不同机器) 164 | 缺点: 165 | 效率稍慢 166 | 2 共享内存无锁队列 167 | 请求包量为512B, 按照内存顺序写为812.32M/s, 处理能力大概为160W次/s左右, 比UDP通信快3-4倍左右 168 | 优点: 速度较快 169 | 缺点: 做异步IO时, 只能通过增加pipe辅助, 写入后给pipe写入一个字节, 后端捕获该pipe可读事件; 进程跨机器不行 170 | 171 | ## 锁性能数据 172 | **自旋锁** 173 | 通过gcc的__sync_bool_compare_and_swap原子操作进行自旋, 申请一块共享内存, 操作其中的某个字段进行 174 | **POSIX信号量** 175 | 通过sem_open, sem_close, sem_wait(申请占用信号量), sem_wait(释放信号量)进行操作 176 | **flock** 177 | int flock(int fd,int operation); 178 | LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。 179 | LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。 180 | LOCK_UN 解除文件锁定状态。 181 | LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程. 通常与LOCK_SH或LOCK_EX 做OR(|)组合。 182 | **fcntl** 183 | int fcntl (int fd, int cmd, struct flock *lock); 184 | F_GETLK、F_SETLK 以及 F_SETLKW 185 | 186 | 8核CPU 100W请求量 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 |
锁方式\进程个数8进程抢占16进程抢占24进程抢占32进程抢占40进程抢占48进程抢占56进程抢占64进程抢占72进程抢占80进程抢占88进程抢占96进程抢占
自旋锁52631578.95 (5000W)次/s29239766.08 (3000W)次/s23041474.65 (2300W)次/s18148820.33 (1800W)次/s19193857.97 (1900W)次/s16835016.84 (1700W)次/s12820512.82 (1300W)次/s14749262.54 (1500W)次/s11261261.26 (1100W)次/s16129032.26 (1600W)次/s9074410.163 (900W)次/s16447368.42 (1600W)次/s
POSIX信号量15600624.02 (1500W)次/s12285012.29 (1200W)次/s10604453.87 (1000W)次/s9680542.11 (960W)次/s9132420.091 (910W)次/s8547008.547 (8500W)次/s8190008.19 (820W)次/s7812500 (780W)次/s7501875.469 (750W)次/s7241129.616 (720W)次/s7042253.521 (700W)次/s6849315.068 (680W)次/s
flock1866368.048 (190W)次/s1000200.04 (100W)次/s999500.2499 (100W)次/s950028.5009 (95W)次/s966930.9611 (96W)次/s952290.2581 (95W)次/s941974.3783 (94W)次/s923787.5289 (92W)次/s901469.3951 (90W)次/s903668.8957 (90W)次/s907029.4785 (90W)次/s905961.2249 (90W)次/s
fcntl1705029.838 (170W)次/s956663.1589 (95W)次/s940822.2787 (94W)次/s899685.1102 (89W)次/s927213.7228 (92W)次/s901875.9019 (90W)次/s902119.982 (90W)次/s885582.7134 (88W)次/s864977.0781 (86W)次/s871459.695 (87W)次/s881134.9018 (88W)次/s874202.2904 (87W)次/s
264 | 265 | 自旋锁性能较高, 不过不同进程抢占的波动性比较大, while循环耗CPU(1000W以上级别性能) 266 | POSIX信号量性能也不错, 随进程数增加性能递减, 波动性不大(500W-1000W级别性能) 267 | flock, fcntl都是文件锁, 性能较差, flock的系统调用比fcntl好用(100W级别性能) 268 | 269 | 270 | 271 | -------------------------------------------------------------------------------- /article/手游服务器多版本管理.md: -------------------------------------------------------------------------------- 1 | # 手游服务器多版本管理 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | 手游中经常会有多版本管理, 对于不同的客户端版本, 依然希望它能够连上服务器进行游戏, 因为强制更新会流失用户, 但是希望在新版本中修改游戏逻辑, 使得新版本的用户会玩得比旧版本爽, 慢慢的就会影响旧版本的用户去自主进行更新. 5 | 6 | gamesvr会同时管理多个逻辑代码, 以及资源, 怎样处理才能使得服务器上的代码显得比较简洁清晰呢? 7 | ![image](https://github.com/zfengzhen/Blog/blob/master/img/mobile_game_multi_version_mgr.jpg) 8 | 9 | ## 分析 10 | ### 逻辑部分: 11 | #### 1 一般都容易想到的办法: 12 | 13 | ```c 14 | if (app version 1) { 15 | play_logic_1() 16 | } else if (app version 2) { 17 | player_logic_2() 18 | } else if (...) { 19 | ... 20 | } 21 | ``` 22 | 23 | 未来有多少个版本就会有多少个if else, 而且每一个命令号都得有同模式的if else, 一般命令号会很多, 这样写起来会非常复杂, 即使是把所有的逻辑代码按版本打包成so, 比如logic_version_1.so, 加载起来还是得有一套这种if else去判定, 只是把逻辑代码打包组织起来. 24 | 25 | #### 2 采用Lua脚本的模式: 26 | 将所有的逻辑用lua编写, 函数名为play_logic_1_1类似, 其中1_1, 表示大版本1, 小版本1, 大版本用来标识必须强制更新的版本, 避免服务器上逻辑代码的版本过多 27 | 28 | string big_ver = 客户端协议带上来的 29 | string small_ver = 客户端协议带上来的 30 | 31 | ```c 32 | if (cmd == PLAY_LOGIC) { 33 | LuaEngine::call_lua_func("play_logic_" + big_ver + "_" + small_ver) 34 | } 35 | ``` 36 | 37 | 这样就会感觉比较简洁, 只用通过脚本去修改不同的逻辑代码. 38 | 在lua文件的管理中, 可以将不同版本的逻辑代码放入同一个文件夹下进行管理, 修改起来也比较方便. 39 | 40 | ### 资源部分: 41 | 资源部分建议也采用脚本方式, 这样对于策划来说, 只要变更excel表格, 所有版本的资源只要有相应字段就必须更新, 通过lua的方式, 资源只用维持一份, 老版本调用新的资源, 不必理会在老版本中没有的字段, 可以前后兼容. 42 | 43 | 上述只是个人的思考, 纯属分析. 44 | -------------------------------------------------------------------------------- /article/服务器后台进程的启动和退出.md: -------------------------------------------------------------------------------- 1 | # 服务器后台进程的启动和退出 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | 服务器后台进程启动和退出常用的一种方式 5 | 6 | ```shell 7 | ./gamesvr xxx xxx xxx start 8 | ./gamesvr stop 9 | ``` 10 | 11 | ### **start**命令, 新建一个进程, 并运行, 包括: 12 | 1. 通过getopt_long解析参数, 存入相应字段 13 | 2. 检查pid文件是否存在, 如果存在, 则退出 14 | 3. daemon化 15 | 4. 设置自定义信号捕获处理(比如处理stop命令) 16 | 5. 初始化程序 17 | 18 | ### **stop**命令, 新建一个进程: 19 | 1. 判断pid文件是否存在 20 | 2. 发送自定义stop命令, kill(proc_id, signal) 21 | 3. 退出 22 | 23 | ### **注意点**: 24 | 1. 发送自定义stop命令后, 正在运行的程序主要工作是将主循环的标记位设为停止状态, 让程序继续跑完已经正在运行的事件, signal最好使用SIGRTMAX-1自定义信号, 如果使用SIGINT可能会对其他第三方库有影响, 导致在程序运行时出现异常 25 | 2. stop命令进程中, 注意不要调用到一些start进程才会初始化的东西, 比如命令行解析后的文件, 以及日志系统的一些调用, 因为stop命令一般不会启动这些, 如果调用会导致异常 26 | -------------------------------------------------------------------------------- /article/服务器程序发布的一种方法.md: -------------------------------------------------------------------------------- 1 | # 服务器程序发布的一种方法 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ## 一般开发模式: 5 | 开发机上开发, 现网环境部署 6 | 每次都是开发机上开发, 编译, 打包, 然后上传到现网环境解压, 部署, 特别时现网测试机上, 可能改个表格就更新了, 略显繁琐. 7 | 8 | ## 思路 9 | ### 采用rsync 10 | 现网环境上部署好rsync daemon, 编译完后完rsync到现网环境, 这样解决了上述问题, 但是发现还是得登录现网环境去重启. 11 | 12 | 如果有一种机制可以, rsync完后, 现网环境能够判断rsync新的文件过来了, 自动重启. 13 | 14 | ### 添加crontab 15 | 每次rysnc的时候, 多传一个文件过去, 这个文件里面什么也没有, 现网环境部署一个crontab, 这个crontab大概执行的流程就是: 判断该文件存不存在, 如果存在, 则执行重启操作, 并删除该文件. 16 | 17 | ## 技术参考 18 | ### rysnc配置文件 /etc/rsyncd.conf 19 | 配置文件分为全局参数和模块参数. 20 | 参数形式 `name = value` value可以是字符串, 或者布尔值. true/false yes/no 0/1都可以. 21 | 22 | 常用全局参数: 23 | uid [用于传输的用户身份] 24 | gid [group id 类似于uid] 25 | pid file [daemon的PID文件, 用于判断进程是否中止] 26 | log file [日志文件] 27 | use chroot [是否使用chroot] 28 | 29 | 常用模块参数: 30 | 模块名由[]括起来, 比如[good_game]表示名字叫good_game的模块. 31 | path [设定模块的目录位置] 32 | read only [是否允许客户端上传文件, 模块中设为false, 全局设为true] 33 | list [是否允许该模块被列出, 一般设为false, 隐藏模块] 34 | uid [用于传输的用户身份] 35 | gid [group id 类似于uid] 36 | auth users [这个参数指定了允许访问该模块的用户列表, 列表中的用户名以逗号和空格分隔. 指定用户可以不必真实存在于本地系统,用户名中也可以包含shell通配符. 纯文本的用户名和密码存储在由”secrets file”参数指定的文件中.] 37 | secrets file [该参数指定一个密码文件, 用于该模块的授权验证. 只有当指定了”auth users”参数时, 这个文件才会被考虑. 密码可以包含任何字符, 最好不要超过8个字符. 该参数没有默认值, 你必须手动创建一个密码文件, 而且该文件不应该被其它用户访问.] 比如: /etc/rsyncd.secrets xxxx:123456 这种格式. 38 | hosts allow [允许访问客户端的IP或者网段] 39 | 40 | Push: rsync [OPTION...] SRC... [USER@]HOST::DEST 41 | -v, --verbose 详细模式输出 42 | -a, --archive 归档模式,表示以递归方式传输文件,并保持所有文件属性,等于-rlptgoD 43 | -S, --sparse 对稀疏文件进行特殊处理以节省DST的空间 44 | -H, --hard-links 保留硬链结 45 | --exclude=PATTERN 指定排除不需要传输的文件(可以是通配模式) 46 | ```shell 47 | rsync --daemon 48 | ``` 49 | 50 | 启动rsync服务器端 51 | 52 | ### crontab check脚本 53 | 54 | ```shell 55 | cd `dirname $0` 56 | if [ -f "rsync.txt" ]; then 57 | ./restart_all.sh 58 | sleep 1 59 | rm rsync.txt 60 | fi 61 | ``` -------------------------------------------------------------------------------- /article/游戏物品掉落分析.md: -------------------------------------------------------------------------------- 1 | # 游戏物品掉落分析 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | 魔兽世界中每次击杀boss后, 点击boss, 会出现boss掉落装备. 这种场景在游戏中是常见的. 5 | ![image](https://github.com/zfengzhen/Blog/blob/master/img/game_item_drops_wow.jpg) 6 | 如图, 魔兽中击杀伊利丹后, 物品掉落: 7 | 我们称整个掉落为一个**礼包**, **礼包**中实际上包含很多个**物品格**, 每个**物品格**都有一个掉落概率, 假如掉落命中, 则继续到该**物品格**中去判定. **礼包**的规则是: 会掉落多个**物品格**, 每个**物品格**的掉落都是独立事件. **物品格**包含多个物品, **物品格**的规则是: 多个物品中, 按权重值最终只会掉落一个. 8 | 9 | 如果boss击杀后会掉落多个物品, 而且数量是不定的时候, 我们在该boss的掉落属性中配置一个礼包ID. 击杀该boss后, 进入礼包掉落判定. 10 | 11 | 如果boss击杀后只会掉落一个物品, 这样可以直接简单的在该boss的掉落属性中配置一个物品格ID. 击杀该boss后, 进入物品格判定. 同样这种情况可以用于卡牌抽取. 卡牌抽取也是多张卡牌中抽取一张. 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 | 68 | 69 |
idnameitem_container_iditem_container_weight
intstringintint
唯一ID名字物品格ID物品格掉落概率
1001gift_set_1001200150
200210
20035
1002gift_set_1002200140
20025
20031
70 | 71 | ### 物品格表如下: 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
idnameitem_iditem_weight
intstringintint
唯一ID名字物品ID物品权重
2001item_container_2001300160
300230
2002item_container_2002300340
300475
2003item_container_2003300520
300630
128 | 129 | 简单的采用lua的math.random()做随机函数(只用于模拟, 真实情况下请自己编写), 配置掉落礼包为1001, 结果如下: 130 | 131 | ```shell 132 | item_container[2001] rand_num = 66, weight = 50 133 | item_container[2002] rand_num = 4, weight = 10 134 | get item_container[2002] 135 | item_container[2002] rand_num = 77, sum_weight = 115 136 | item_id[3003] item_weight[40] 137 | sum_item_weight[40] 138 | item_id[3004] item_weight[75] 139 | sum_item_weight[115] 140 | get item[3004] 141 | item_container[2003] rand_num = 2, weight = 5 142 | get item_container[2003] 143 | item_container[2003] rand_num = 38, sum_weight = 50 144 | item_id[3005] item_weight[20] 145 | sum_item_weight[20] 146 | item_id[3006] item_weight[30] 147 | sum_item_weight[50] 148 | get item[3006] 149 | ``` 150 | 151 | ```shell 152 | item_drops: 153 | 3004 154 | 3006 155 | ``` 156 | 157 | 掉落3004和3006两件物品. 158 | -------------------------------------------------------------------------------- /article/玩家多终端登录踢下线小结.md: -------------------------------------------------------------------------------- 1 | # 玩家多终端登录踢下线小结 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | ![image](https://github.com/zfengzhen/Blog/blob/master/img/玩家多终端登录踢下线小结.png) 5 | 6 | -------------------------------------------------------------------------------- /article/移动网络下的动作格斗类PVP游戏网络同步方案.md: -------------------------------------------------------------------------------- 1 | # 移动网络下的动作格斗类PVP游戏网络同步方案 2 | **作者: fergus (zfengzhen@gmail.com)** 3 | 4 | 5 | 移动网络下的动作格斗类PVP游戏特性 6 | 1、移动网络 --> 无线信道,信道的切换、干扰以及衰弱导致比有线网络丢包率高 7 | 2、动作格斗类 --> 快速响应 8 | 3、PVP方案 --> 权威服务器,帧同步 9 | 10 | 需要关注的两个特性:**丢包**和**时延** 11 | 12 | 在此基础上提出了[逻辑层被动拉取式智能多发可靠UDP帧同步PVP方案](https://github.com/zfengzhen/Blog/blob/master/article/%E7%A7%BB%E5%8A%A8%E7%BD%91%E7%BB%9C%E4%B8%8B%E7%9A%84%E5%8A%A8%E4%BD%9C%E6%A0%BC%E6%96%97%E7%B1%BBPVP%E6%B8%B8%E6%88%8F%E7%BD%91%E7%BB%9C%E5%90%8C%E6%AD%A5%E6%96%B9%E6%A1%88.md#逻辑层被动拉取式智能多发可靠udp帧同步pvp方案) 13 | 14 | ##丢包 15 | ![](https://github.com/zfengzhen/Blog/blob/master/img/network_pkg_loss.png) 16 | **丢包会导致数据重传** 17 | 18 | **TCP重传处理** 19 | TCP发生重传时,没有任何办法可以绕过TCP的重传机制,linux下的RTO(Retransmission TimeOut)最小值是200ms。当网络发生抖动时,我们只能被动接受高时延。这都是源于TCP是一个君子协议,TCP拥塞控制机制建立在传统有线网络丢包是拥塞唯一原因的基础上,如果丢包了,TCP认为网络容量不够了,不能立马重发,免得网络变得更加拥堵。 20 | 21 | **UDP重传处理** 22 | UDP需要自己定义丢包后的处理机制,通过浪费点带宽(移动网络下还不一定浪费了带宽),让时延可控。 23 | 24 | ##时延 25 | 参见[维基百科](https://zh.wikipedia.org/wiki/%E6%97%B6%E5%BB%B6) 26 | 参见《计算机网络--自顶向下方法》 27 | ![](https://github.com/zfengzhen/Blog/blob/master/img/network_delay.png) 28 | ##游戏PVP方案 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 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
**权威服务器模式****帧同步模式**
**优势:****短处:**
1、反作弊天然支持1、反作弊相对麻烦(帧效验,天花板或者全局推演),可解决
2、战斗逻辑只需要服务器更新2、战斗逻辑需要所有客户端及时更新
3、可以做世界这种有视野概念玩法,按区域刷新状态3、只能做单局概念玩法,需要全视野所有操作进行推演
4、可以做视野玩法4、视野数据客户端全有,客户端本地屏蔽,防视野挂难
**短处:****优势:**
1、因为要协调客户端服务器协议等,开发效率慢1、开发效率快
2、服务器承载低(逻辑运算量相对大)2、服务器承载高
3、录像相对难实现3、录像天然支持(跨版本录像需要额外考虑)
4、客户端的状态和表现很难确定在什么时间点是一致的4、客户端可以做公平竞技
5、网络流量相对较大5、网络流量相对较小
**代表作:****代表作:**
各端游以及mmo手游王者荣耀,火影忍者
98 | 99 | ##逻辑层被动拉取式智能多发可靠UDP帧同步PVP方案 100 | 101 | ###关键字拆解 102 | ![](https://github.com/zfengzhen/Blog/blob/master/img/network_pvp.png) 103 | 104 | ####逻辑层 105 | 是指对于可靠UDP的保证是针对某些逻辑协议,而不是完整的协议层的可靠UDP 106 | 107 | ####被动拉取式 108 | 被动拉取式区别于主动ack机制,借鉴linux设计理念,没有消息就是好消息,由客户端主动拉取丢包消息 109 | 110 | ####智能 111 | 是指能够根据网络情况,制定策略进行多发的控制 112 | 113 | ####多发 114 | 通过增加网络流量,减小网络丢包的概率,避免丢包后一段时间重发,降低网络时延,保证包能够在可控时延内到达,增加玩家操作的顺畅感 115 | 116 | ####可靠UDP 117 | 协议层采用UDP,传统有线网络TCP拥塞控制机制建立在网络丢包是拥塞唯一原因的基础上。如果丢包了,那就是网络容量不够了,所以不能立马重发,免得网络变得更拥塞。 118 | 对于移动网络来说,丢包很有不是网络容量不够,而是信道切换,干扰或者衰弱导致丢包。 119 | 自己通过UDP可以控制重传机制,我不管你网络情况怎么样,我要保证自己的包能在可控延时内到达,我不介意抢占带宽。 120 | 121 | 注:这里的帧同步采用不锁帧的模式,不考虑绝对公平竞技,网络卡的玩家不影响对手,逻辑帧按服务器时间轴驱动。 122 | 123 | -------------------------------------------------------------------------------- /img/PlayFrame_heaptimer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/PlayFrame_heaptimer.png -------------------------------------------------------------------------------- /img/PlayFrame游戏架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/PlayFrame游戏架构.png -------------------------------------------------------------------------------- /img/PlayeFrame_lua_coroutine_async.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/PlayeFrame_lua_coroutine_async.png -------------------------------------------------------------------------------- /img/TankGameLua.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/TankGameLua.jpg -------------------------------------------------------------------------------- /img/TankGameLua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/TankGameLua.png -------------------------------------------------------------------------------- /img/ai_behaviac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/ai_behaviac.png -------------------------------------------------------------------------------- /img/ai_impl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/ai_impl.png -------------------------------------------------------------------------------- /img/cocos2d_x_axes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_axes.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_ccaction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_ccaction.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_ccactioninstant.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_ccactioninstant.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_ccactioninterval.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_ccactioninterval.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_ccdirector.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_ccdirector.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_ccfinitetimeaction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_ccfinitetimeaction.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_cclayer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_cclayer.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_ccnode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_ccnode.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_ccnode_event.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_ccnode_event.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_ccscene.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_ccscene.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_ccsprite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_ccsprite.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_ccsprite_attr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_ccsprite_attr.jpg -------------------------------------------------------------------------------- /img/cocos2d_x_inner_layer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/cocos2d_x_inner_layer.jpg -------------------------------------------------------------------------------- /img/game_item_drops_wow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/game_item_drops_wow.jpg -------------------------------------------------------------------------------- /img/hold住你的后台程序_disk_write_read.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/hold住你的后台程序_disk_write_read.jpg -------------------------------------------------------------------------------- /img/hold住你的后台程序_memory_write.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/hold住你的后台程序_memory_write.jpg -------------------------------------------------------------------------------- /img/hold住你的后台程序_open_close.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/hold住你的后台程序_open_close.jpg -------------------------------------------------------------------------------- /img/hold住你的后台程序_time.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/hold住你的后台程序_time.jpg -------------------------------------------------------------------------------- /img/hold住你的后台程序_time_vdso.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/hold住你的后台程序_time_vdso.jpg -------------------------------------------------------------------------------- /img/hold住你的后台程序_磁盘读写背后.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/hold住你的后台程序_磁盘读写背后.jpg -------------------------------------------------------------------------------- /img/hold住你的后台程序_网络相关系统调用.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/hold住你的后台程序_网络相关系统调用.jpg -------------------------------------------------------------------------------- /img/lpi_10_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_10_1.png -------------------------------------------------------------------------------- /img/lpi_10_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_10_2.png -------------------------------------------------------------------------------- /img/lpi_12_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_12_1.png -------------------------------------------------------------------------------- /img/lpi_13_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_13_1.png -------------------------------------------------------------------------------- /img/lpi_13_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_13_2.png -------------------------------------------------------------------------------- /img/lpi_13_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_13_3.png -------------------------------------------------------------------------------- /img/lpi_14_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_14_1.png -------------------------------------------------------------------------------- /img/lpi_14_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_14_2.png -------------------------------------------------------------------------------- /img/lpi_14_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_14_3.png -------------------------------------------------------------------------------- /img/lpi_14_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_14_4.png -------------------------------------------------------------------------------- /img/lpi_14_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_14_5.png -------------------------------------------------------------------------------- /img/lpi_14_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_14_6.png -------------------------------------------------------------------------------- /img/lpi_19_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_19_1.png -------------------------------------------------------------------------------- /img/lpi_19_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_19_2.png -------------------------------------------------------------------------------- /img/lpi_23_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_23_1.png -------------------------------------------------------------------------------- /img/lpi_24_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_24_1.png -------------------------------------------------------------------------------- /img/lpi_25_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_25_1.png -------------------------------------------------------------------------------- /img/lpi_25_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_25_2.png -------------------------------------------------------------------------------- /img/lpi_34_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_34_1.png -------------------------------------------------------------------------------- /img/lpi_34_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_34_2.png -------------------------------------------------------------------------------- /img/lpi_41_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_41_1.png -------------------------------------------------------------------------------- /img/lpi_41_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_41_2.png -------------------------------------------------------------------------------- /img/lpi_41_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_41_3.png -------------------------------------------------------------------------------- /img/lpi_4_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_4_1.png -------------------------------------------------------------------------------- /img/lpi_4_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_4_2.png -------------------------------------------------------------------------------- /img/lpi_4_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_4_3.png -------------------------------------------------------------------------------- /img/lpi_51_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_51_1.png -------------------------------------------------------------------------------- /img/lpi_52_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_52_1.png -------------------------------------------------------------------------------- /img/lpi_53_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_53_1.png -------------------------------------------------------------------------------- /img/lpi_54_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_54_1.png -------------------------------------------------------------------------------- /img/lpi_55_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_55_1.png -------------------------------------------------------------------------------- /img/lpi_56_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_56_1.png -------------------------------------------------------------------------------- /img/lpi_59_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_59_1.png -------------------------------------------------------------------------------- /img/lpi_5_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_5_1.png -------------------------------------------------------------------------------- /img/lpi_5_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_5_2.png -------------------------------------------------------------------------------- /img/lpi_63_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_63_1.png -------------------------------------------------------------------------------- /img/lpi_6_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_6_1.png -------------------------------------------------------------------------------- /img/lpi_6_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_6_2.png -------------------------------------------------------------------------------- /img/lpi_6_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lpi_6_3.png -------------------------------------------------------------------------------- /img/lua_async_task_lua_co_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lua_async_task_lua_co_model.png -------------------------------------------------------------------------------- /img/lua_async_task_sync_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lua_async_task_sync_model.png -------------------------------------------------------------------------------- /img/lua_async_task_task_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lua_async_task_task_model.png -------------------------------------------------------------------------------- /img/lua_tinker_cpp_impl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/lua_tinker_cpp_impl.jpg -------------------------------------------------------------------------------- /img/markdown_basics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/markdown_basics.png -------------------------------------------------------------------------------- /img/mem_hash_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/mem_hash_arch.png -------------------------------------------------------------------------------- /img/mobile_game_multi_version_mgr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/mobile_game_multi_version_mgr.jpg -------------------------------------------------------------------------------- /img/mysql_proxy_server_2thread.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/mysql_proxy_server_2thread.jpg -------------------------------------------------------------------------------- /img/mysql_proxy_server_mutithread.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/mysql_proxy_server_mutithread.jpg -------------------------------------------------------------------------------- /img/mysql_proxy_server_mysqlmgr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/mysql_proxy_server_mysqlmgr.jpg -------------------------------------------------------------------------------- /img/network_delay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/network_delay.png -------------------------------------------------------------------------------- /img/network_pkg_loss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/network_pkg_loss.png -------------------------------------------------------------------------------- /img/network_pvp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/network_pvp.png -------------------------------------------------------------------------------- /img/one_obj_has_many_other_obj.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/one_obj_has_many_other_obj.jpg -------------------------------------------------------------------------------- /img/perf_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/perf_graph.png -------------------------------------------------------------------------------- /img/protobuf懒读取_proxy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/protobuf懒读取_proxy.jpg -------------------------------------------------------------------------------- /img/protobuf懒读取_测试结果.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/protobuf懒读取_测试结果.jpg -------------------------------------------------------------------------------- /img/tcp_system_call_send.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/tcp_system_call_send.jpg -------------------------------------------------------------------------------- /img/tolua_class_relation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/tolua_class_relation.png -------------------------------------------------------------------------------- /img/tolua_register_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/tolua_register_table.png -------------------------------------------------------------------------------- /img/toluapp_impl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/toluapp_impl.jpg -------------------------------------------------------------------------------- /img/ucontext簇函数学习_实际使用.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/ucontext簇函数学习_实际使用.png -------------------------------------------------------------------------------- /img/为互联网应用程序而生的State_Threads_fig.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/为互联网应用程序而生的State_Threads_fig.gif -------------------------------------------------------------------------------- /img/单一继承的一般继承.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/单一继承的一般继承.png -------------------------------------------------------------------------------- /img/复制构造函数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/复制构造函数.png -------------------------------------------------------------------------------- /img/多重继承.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/多重继承.png -------------------------------------------------------------------------------- /img/构造函数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/构造函数.png -------------------------------------------------------------------------------- /img/玩家多终端登录踢下线小结.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/玩家多终端登录踢下线小结.png -------------------------------------------------------------------------------- /img/重复继承.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/重复继承.png -------------------------------------------------------------------------------- /img/钻石型多重继承.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zfengzhen/Blog/8d80c18b5d71342bbdd51f4a145651e6ddbe474a/img/钻石型多重继承.png --------------------------------------------------------------------------------