同步操作将从 leo/Engine 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
C++服务器编程底层库
项目使用xmake管理,使用方法详见xmake手册
注:Linux下建议使用GCC的
-Wl,-rpath,.
连接选项指定运行期动态连接库的优先查找目录,以方便分发部署
#include <Application.h>
class GameApp : public Application {
public:
GameApp() {}
virtual bool OnInit(const Commandline & cmd) override {
if (cmd.Has("--debug")) {
Logger::Instance().Initialize("game", "logs", ELog::Debug);
} else {
Logger::Instance().Initialize("game", "logs", ELog::Info);
}
/// 锁帧
LockFPS(20);
/// 填写其他初始化逻辑,当返回false时程序直接退出
return true;
}
virtual void OnBreath() override {
/// 这里填写需要每帧更新的逻辑
}
};
RUN_APP(GameApp)
头文件Socket.h
,通常结合IOListener.h
,一个简单的客户端实现:
class Client : public IOListener {
public:
Client() : _socket(new Socket()) {
}
~Client() {
Close();
delete _socket;
}
bool Connect(const std::string & ip, int port) {
bool succ = _socket->Connect(ip, port);
if (succ) BindIO(_socket->ctx, IO_READ); //! 我们只需要网络有数据来时通知
return succ;
}
/// 实现数据可读时消息处理
virtual void OnReadable() override {
char buf[1024] = {0};
int readed = 0;
int recv = 0;
while (true) {
recv = _socket->Recv(buf, 1024);
if (recv > 0) {
//! TODO: process received data.
} else if (recv == 0) {
break;
} else {
Close(recv);
break;
}
}
}
void Close(int reason = 0) {
UnbindIO(); //! 关闭之前请取消IO事件监听
_socket->Close();
}
bool Send(const char * p, size_t s) {
return _socket->Send(p, s);
}
private:
Socket * _socket;
}
- 设计原则:Lua只负责逻辑,对象生存管理交由C++(可以注册管理到Lua)
- 涉及到Get操作,需要try...catch以捕获类型异常(C++注册到Lua的接口内Get不需要,调用函数不需要)
- Property可以为地址方式,也可以为Getter(TG (void))、Setter(void (TS))方式注册
- Method必须为
int (*f)(LuaState &)
- Lua不可用于多线程,只能在主线程中使用,但可以使用协程。
#include <Script.h>
/// 注册公共变量或函数到Lua
GLua.Register("GameSetting") //! 所有下面注册的属性或函数放在GameSetting中
.Property("nPlayerCounter", &GPlayerCount, false) //! 以地址方式注册属性,同时设置不可写
.Property("nTime", &GetTime) //! 以Getter方式注册属性,同时不注册属性的写方法(不可写)
.Method("GetAById", &GetAById); //! 注册全局Lua方法
/// 注册C++类到Lua
GLua.Register<A>("LuaA")
.Property("nId", &A::id, false)
.Property("sName", &A::GetName, &A::SetName)
.Method("Msg", &A::SendMessage);
try {
A * p = GLua.Get<A *>("me"); // 需要使用try,因为可能类型不匹配
} catch (...) {}
if (GLua.Is<A *>("me")) {} // 不需要try
GLua.Set<A *>("me", new A) // 不需要try
GLua.Call("GameSetting", "GetAById", false, 100); // 不需要try
int GetAById(LuaState & r) {
int n = r.Get<int>(1); // 不需要try
...
}
LUA中扩展C++注册的类或名空间(注只能扩展方法,不可扩展属性)
-- 扩展名空间的方法
function XXX.yyy()
end
-- 扩展类静态方法
function LuaA.Test()
print("hehe")
end
-- 扩展类成员方法,注意:这里用的是':',因为需要使用self
function LuaA.apis:YYY()
end
内置的其他基本函数
函数 | 功能 |
---|---|
print(...) | 使用Logger重载的print接口[Logger::Level::Info] |
print_err(...) | 使用Logger重载的print接口[Logger::Level::Error] |
loadbits(n, start, end) -> integer | 读取一个int32中[start, end]字节表示的值 |
setbits(n, start, end, v) -> integer | 设置一个int32中[start, end]字节表示的值 |
json.encode(v) -> string | 将lua变量序列化成json字串 |
json.decode(s) -> var | 将json字串反序列化成lua值 |
scheduler.timer(delay, func[, is_loop]) -> integer(id) | 注册一个定时器 |
scheduler.task(hour, min, sec, func) -> integer(id) | 注册一个每天hour:min:sec执行的操作 |
scheduler.is_valid(id) -> bool | 测试一个定时器或计划是否存在 |
scheduler.remain(id) -> double | 返回一个定时器或计划还需要多少毫秒运行 |
scheduler.cancel(id) | 取消一个定时器或计划任务 |
- 由于本人能力有限,经实际效率测试,目前仅保留非线程安全的对象Pool(Pool.h)
- Pool加锁后可用于多线程,但经测试效率还不及系统的new,但Linux下相差不大,如果考虑到无内存碎片的优点,可以自行添加。
- 如果采用Application的模型,Pool基本上是够用的。因为逻辑主要在主线程的Tick中触发
First. 编写线程内的具体工作类,继承IThreadJob.
#include <Threads.h>
class DemoTask : public IThreadJob {
public:
DemoTask(...) { ... } //! 这里为该工作参数初始化
virtual ~DemoTask() { ... } //! 这里为工作结束时清理操作
virtual void OnRun() { ... } //! 工作的具体内容
private:
... //! 参数声明
};
Second. 创建线程池及工作对象容器
int main() {
Threads workers(4); //! 创建含有一个4个工作线程的容器
/// 增加100个并发任务(多余的会暂时等待空闲线程)
for (int i = 0; i < 100; ++i) {
workers.AddJob<DemoTask>(...); // 传入工作需要的参数,这里自动调用 new DemoTask(...);
}
/// 等待所有的工作结束,如果不执行该操作,mgr超出生存期时会放弃未执行的任务。
workers.Wait();
return 0;
}
/// 添加一个500毫秒后执行的定时器
GScheduler.Add(500, [](uint64_t id) {
printf("Timer's id : %llu", id);
});
/// 添加一个每500毫秒执行一次的定时器
GScheduler.Add(500, [](uint64_t id) {
printf("Timer's id : %llu", id);
}, true);
/// 注册每天05:00:00时执行的计划任务
GScheduler.Add(5, 0, 0, [](uint64_t id) {
printf("Task's id : %llu", id);
});
/// 是否存在定时器或计划任务
bool valid = GScheduler.IsValid(timer_id);
/// 取得一个定时器或计划多少毫秒后执行
double left = GScheduler.GetRemainTime(timer_id);
/// 取消一个定时器或计划任务
GScheduler.Cancel(timer_id);
scheduler.timer(500, function(id) end);
scheduler.timer(500, function(id) end, true);
scheduler.task(5, 0, 0, function(id) end);
scheduler.is_valid(timer_id);
scheduler.remain(timer_id);
scheduler.cancel(timer_id);
日志生成的结构说明
RootOfLogs 指定的日志根目录
|-- 20160803 首先日志会根据“年月日”分文件夹
| |-- main_01_00_00.000.log 其次日志会按指定大小分文件记录,文件名为指定的"Name_时_分_秒.毫秒.log"
| |-- main_01_27_18.193.log
/// 初始化日志。日志名为main, 放在logs目录下,输出等级为DEBUG,每个文件最大为4M
Logger::Instance().Initialize("main", "logs", Logger::Debug, 4 * 1024 * 1024);
/// 写日志
LOG_INFO("Hello");
LOG_DEBUG("Hello %d", 2);
LOG_ERR("Error : %s", "Test");
LOG_WARN("You have a warning");
头文件 Crypto.h
uint32_t CalcCRC(const char * mem, size_t size, uint32_t pre_crc = 0)
uint32_t CalcHash(const char * mem, size_t size)
class MD5
class SHA1
class HMAC_SHA1
头文件OS.h
。包含:
高精度时间:
OS::Tick()
OS::Now()
OS::GetTimeZone()
OS::ParseDataTime(year, month, day, hour, min, sec)
文件系统:
OS::Exists(path)
OS::CreateDir(dir)
OS::GetWorkDir()
OS::ChangeWorkDir(path)
OS::GetFullPath(path)
OS::GetDirName(path)
OS::GetFileName(path)
OS::GetFiles(path, recursive)
创建GUID/uuid
OS::CreateID();
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。