C++的右值引用

右值引用主要用于移动语义(Move semantics)和完美转发(Perfect forwarding) Move semantics Move semantics能转移一个对象到另一个对象上,避免不必要的内存拷贝,被拷贝对象不能在其它地方被引用。要实现Move semantics只要给类定义移动构造函数和可选的移动赋值操作: class Memory { public: Memory(Memory&& other) noexcept { _data = other._data; _length = other._length; other._data = nullptr; other._length = 0; } Memory& operator=(Memory&& other) noexcept { if (this != &other) { delete[] _data; _data = other._data; _length = other._length; other._data = nullptr; other._length = 0; } return *this; } private: void *_data{nullptr}; int _length{0}; } Perfect forwarding Perfect forwarding主要为了减少重载函数,主要是泛型函数用引用类型作为参数。 struct W { W(int&, int&) {} }; struct Z { Z(const int&, const int&) {} }; template <typename T, typename A1, typename A2> T* factory(A1& a1, A2& a2) { return new T(a1, a2); } int a = 4, b = 5; W* pw = factory<W>(a, b); // OK Z* pz = factory<Z>(2, 2); // Valid cause 2 is right value 把上面的泛型函数参数改成右值引用就能解决这个问题: template <typename T, typename A1, typename A2> T* factory(A1&& a1, A2&& a2) { return new T(std::forward<A1>(a1), std::forward<A2>(a2)); } Z* pz = factory<Z>(2, 2); // OK References: https://learn.microsoft.com/en-us/cpp/cpp/rvalue-reference-declarator-amp-amp?view=msvc-170

January 16, 2022 · 1 min · Jee

C++跨平台项目开发

简介:记录开发一个跨平台C++项目的历程 1. 背景 我司服务端项目大部分使用一个简单的模式:C++做底层负责网络/内存/多线程/日志等,内嵌一个Lua虚拟机,业务逻辑都用Lua写。这个模式既能提供高性能,又能让业务逻辑开发简单,安全。多年来运作良好。 过去几年,服务端的开发和运行环境均在Linux下。今年要开一个新项目,我心血来潮想要在Windows下开发,当然线上还是部署在Linux中。所以就需要让这个项目同时支持Linux和Windows。业务层面好说,因为用Lua开发,本身100%跨平台。所以只需要让C++代码能跨平台编译运行即可。 2. C++跨平台编译运行 我们的C++项目主要依赖STL/Lua/Socket库。 STL本身就是跨平台的,只要有C++编译器即可。线程库用的c++11的std::thread。Easy! Lua源代码是用标准C89写,基本零依赖,几乎所有平台都可以编译,但我懒的自己编译了,所以直接用社区提供的LuaBinaries。 Socket库我们是自己开发的,所以简单封装了一个Socket类,底层根据不同平台调用不同的API,对外提供统一的抽象接口。 遇到的第一个问题是:如何"一键"在Win/Linux下编译 Linux平台下,我们一直用Makefile,所以轻车熟路的写了一个,搞定Linux下编译。 Windows平台下,就没那么简单了,绕了点弯路。由于我本身用了多年的cygwin,所以直接用cygwin下的gcc编译,还能直接用我为Linux平台写的Makefile。这一步也很简单,但问题出在Lua的C模块上,我们用了一些开源的Lua C模块,例如lua-cjson,这个东西虽然也可以在cygwin下编译,但运行时Lua虚拟机总是不能正确加载,折腾了好久最终放弃。因为考虑到以后可能还会用到其他开源的项目,而且其他项目成员也不一定都喜欢装一个cygwin,所以我还是决定弄成Windows原生的exe和dll。 安装了一个VS2019(这东西真大,十几个G),所有C++代码丢进去,配置下链接上面提到的LuaBinaries,运行完美。那开源的项目如何编译成原生dll呢?由于开源项目大多提供了Makefile或CMake的编译脚本,很少直接提供VS工程的,所以我决定用mingw。下面以编译lua-cjson为例记录下过程。 安装MingW,并将MingW的bin目录加入Path中 下载lua-cjson-2.1.0(https://www.kyne.com.au/\~mark/software/download/lua-cjson-2.1.0.tar.gz) 修改lua-cjson的Makefile,放开## Windows (MinGW)下面的注释 ## Windows (MinGW) CC = gcc # 增加此行 TARGET = cjson.dll PREFIX = c:/LuaBinaries # 改成你的LuaBinaries目录 CJSON_CFLAGS = -DDISABLE_INVALID_NUMBERS CJSON_LDFLAGS = -shared -L$(PREFIX) -llua53 # 这里根据你的Lua版本配置 然后打开CMD,cd到lua-cjson-2.1.0目录,运行: mingw32-make 踩到过的坑:链接lua dll时gcc报错"lua53.dll 无法识别的格式"。卡了半天才发现原来mingw的32位和64位是两个独立的项目。我用google搜mingw的官网,找到了(https://sourceforge.net/projects/mingw/),这个是32位的。而我下载的LuaBinaries是64位,自然链接不到。64位的mingw是这个(https://sourceforge.net/projects/mingw-w64/),注意根据你的平台选择正确的版本!

May 22, 2021 · 1 min · Jee

记录一次C++游戏服务器项目开发

我们启动了一个新的游戏项目,产品上线后虽并未取得市场上的成功,但研发过程却很有价值,让我受益匪浅,值得记录下来。在这个项目以前我一直从事游戏客户端的开发工作,端游手游都做过。这个新项目我将负责游戏服务端的开发,并且大部分工作都是我一个人在做,从零构建了服务端的绝大部工作。 1. 架构 游戏采用的是世界同服设计,即所有玩家都同一个服里面玩,不需要选择服务器。这种类型的游戏服务端必须采用可横向扩展的架构,否则客户端激增的情况下会击垮某个服务,只能选择停服维护。我们已经看到很多产品在上线当天服务端崩溃停服了数个小时才恢复。 我将整个系统分成四种服务: 网关服 游戏逻辑服 聊天服(ChatServer) 存储服(DatabaseServer) 每一个服务都是可以动态扩容的,通过在不同的物理机上启动多个实例来分担请求的压力,网关通过负载均衡将客户端路由到后端服务池中。 2. 网关服(GateServer) GateServer 作为客户端与服务端通信的第一道关卡。客户端启动后,会先发送一个 HTTP 请求到 GateServer 上来获取一些客户端需要知道的信息,包含: 服务端状态 (正常/维护/关闭等状态) 客户端是否需要更新 (如需要的话会包含商店跳转地址或热更新文件下载地址) 公告 (进入游戏场景前显示的公告) GameServer和ChatServer的IP地址 (用某种负载均衡算法从池中选择一个) Gateway对内监控着GameServer集群的健康状态,对外提供http服务跟客户端通信,所以我使用nginx来实现。但因为需要能运行一些业务逻辑会涉及部分编程,所以我采用OpenResty。OpenResty在nginx里内嵌了Lua解释器,可以很方便的提供动态服务而不需要在后面在启动一个http server。Gateway启动时从配置文件里读取GameServer集群的IP地址列表,然后为每个IP都启动一个timer,这个timer里定期发送个TCP请求去查询GameServer的状态。因为每个timer都是运行在一个coroutine里的,而tcp通讯都是非阻塞,所以整个服务运行性能良好。 Gateway提供了一些管理接口给内部使用,比如添加删除GameServer。所以整个服务不需要暂停就能动态扩展服务器的容量。这些接口都是简单http请求,所以十分的方便。 Gateway还负责了公告功能,主要用来通知玩家一个主要信息,比如停服维护。 OpenResty实现这些功能太方便了,而且IO通信都是非阻塞的,经过Lua的coroutine封装后对使用者提供同步的API。以后的游戏甚至可以考虑完全用其作为GameServer。 3. 游戏逻辑服(GameServer) GameServer处理主要的业务逻辑,考虑到服务器端有发送信息给客户端的需求,并且我希望通信能更实时一些,所以使用一条TCP长连接跟客户端通信。GameServer有多个实例,每个都是等价的,玩家连接到那一个上都能进行游戏,所以可能很方便的横向扩展来满足不同的用户容量。 3.1 编程语言选择 我本身是一名C++客户端程序员,个人开发的第一款游戏也是采用cocos2dx/C++开发。第二款游戏考虑到cocos2dx开发效率不高,所以选择用Unity/C#开发。从C++转到C#是很自然很舒服的,毕竟两者比较类似。第三款游戏我开始负责服务器端开发,在编程语言的选择上纠结了很久。考察过Python/Erlang/Java,Erlang的Actor模型是开发高并发实时系统的最佳选择,但其古怪的语法和编程思维的巨大转变让我望而却步。Python加gevent组合似乎也是不错的选择,但作为一名C++程序员我对性能和底层有着特殊的偏好。Java是很多手游公司服务器端编程语言的选择,包括COC的服务器端也是用Java开发的,其实我对Java是很熟悉,实习期间就在一家做Java开发的公司干了半年,但它无论如何也不吸引了我。最终还是选择用我最熟悉的C++来开发: 多年的C++经验让我有信心写出高质量的C++代码 大部分游戏程序员都是写C++的,将来招人会更容易些。 在网络和内存部分C++可以实现的更高效 其他语言平台并没有优秀到让我毫不犹豫的放弃C++ 并且我还选择使用C++11,std::bind和std::function以及对lambda的支持,可以很方便的开发异步程序。std::thread和std::atomic也都很好用,其他的一些小的特性也提高了开发效率,让代码更干净。 3.2 EventLoop 我希望服务器可以单机承载10000的并发用户。使用异步IO是不二之选,而且Linux平台的epoll提供了性能优良的IO多路复用机制。而外我希望在处理网络数据时内存使用必须高效,环形缓存冲可以很好的解决这个问题。最终用了1800多行代码实现这个网络模块,性能上我很满意,而且代码量小,易于维护。 为什么不使用libevent? 一是因为libevent实现的太复杂,源码有20多万行。而是因为我对evbuffer的设计很不满意,处理网络数据最好用连续内存,而evbuffer内部使用链表将内存块串起来,使得我不得不多一次内存拷贝。三是因为写一个异步IO库并不需要太多工作量,我1800行代码就搞定了,自己维护起来方便。 目前游戏已经上线半年,玩家登录了几百万次,该模块运行稳定。以后整理下代码可以考虑开源出来,完全可以取代libevent, 3.3 多线程 为了充分利用多核,我使用了多线程,并且每个线程都跑了一个EventLoop。在主线程里监听一个端口,接到用户连接后把fd传给worker线程,默认情况启动四个worker线程,这个可以根据CPU数配置。以后的网络通信都发生在worker线程,业务逻辑也跑在worker线程。另外还启动若干Job线程,用来运行一些不需要那么及时又比较耗时的操作,比如更新排行榜。 3.4 通信协议 本来想用protobuf做通信协议,结果protobuf的C#实现都不能运行在Unity中,主要是因为Unity在移动平台上使用AOT使得protobuf的C#库无法运行。也找不到其他让我满意的,于是决定自己写。我希望协议定义在一个DSL中,然后生成服务器端和客户端都能用的代码,并且不使用反射,以便能够在移动平台上使用。考虑到解析一个自定义的DSL比较麻烦,我决定使用xml来定义协议。客户端和服务器的通信大部分都是Request-Response模式,所以一个协议定义大致如下: <protocols version="1"> <status> <code name="Ok" value="0"/> <code name="PasswordError" value="1"/> </status> <protocol name="Login" id="1"> <request> <field name="account" type="string"/> <field name="password" type="string"/> </request> <response> <field name="error" type="int"/> <field name="playerData" type="binary"/> </response> </protocol> </protocols> 二进制编码使用的是messagepack,这个协议工具虽然没有protobuf强大,但完成了我们需要的功能,而且在内存使用上更加高效。最主要的是生成的代码使用起来很舒服。生成工具用paython写的,可以生成C++,C#,Go代码。 ...

February 14, 2015 · 1 min · Jee