这次的选择是以一个实际运营的项目做测试,其实我在以前的文章中也提到过这个应用,也许大家都并不陌生,它就是现在还比较火爆的剑侠3,一款多人在线的MMO游戏。网络上很久以前就流出了其部分的源代码,很久以前我也对它经过一定的分析,不过感觉到并没有修复和学习的必要,也就没有再看。现在想来最大的原因就是那份代码缺少的东西太多,就连一些基本的头文件都缺失,那么参考的意义就不大,除非有极深的兴趣和足够的精力。
这次选择的是几年前稍微完整的一份代码,当然也只是冲着研究和学习的目的,毕竟那么一个大型的项目还是有一定的研究价值,不过想要实际运行并没有那么容易,虽然一份可以32编译的版本,但我是索然无味的。其最大的原因,就是我想要将plain framework(PF)应用到这里面。其实从残缺的代码中可以看到其实确实比较重要的地方就是网络方面,而正好PF拥有这个能力,一切就自此开始了。
修复那流出并不太全的代码,最大的就是需要实现网络方面的接口,好在PF主要做这方面的,而在修改这部分代码之前,我还以为可能会费一番周折。可是经过一段时间的思考后,结果就如果下面的短短代码,一切都如同古语说的那样:大道至简!
// 这个线程池目前只用于重连
pf_sys::ThreadPool g_thread_pool(6);
void reconnect(
pf_net::connection::Basic *conn,
const std::string &name,
const std::string &ip,
uint16_t port) {
for (;;) {
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
KGLogPrintf(KGLOG_DEBUG,
"reconnect: %s(%s:%d)", name.c_str(), ip.c_str(), port);
auto r = conn->connect(ip.c_str(), port);
if (r) {
get_conn_mgr().add(conn); //线程不安全?
get_conn_mgr().set_connection_name(conn->get_id(), name);
conn->set_name(name);
break;
}
}
};
std::unique_ptr < pf_net::connection::manager::Connector > g_connector;
// static bool g_connector_init{ false };
pf_net::connection::manager::Connector & get_conn_mgr()
{
if (is_null(g_connector)) {
auto connector = new pf_net::connection::manager::Connector;
unique_move(pf_net::connection::manager::Connector, connector, g_connector);
g_connector->callback_disconnect([](pf_net::connection::Basic *conn){
auto flag = conn->get_param("no_reconnect"); // 如果设置了不自动重连...
if (flag == true) return;
std::string ip = conn->socket()->host();
if ('\0' == ip[0]) return;
auto port = conn->socket()->port();
std::string name = conn->name();
conn->set_empty(false);
g_thread_pool.enqueue([conn, name, ip, port]{
reconnect(conn, name, ip, port);
});
});
}
return *g_connector.get();
}
上面的代码是用于客户端连接的,也就是需要连接到服务器使用,在这个项目的构架中游戏服务器也需要连接到其他服务器的,如中心服和网关。这里在原来的基础上,实现了断线自动重连的功能,原本的服务器是断线后就必须重启所有的进程才能正常工作的,这里做了一点小小的改进,而且实现起来并不复杂。
那么面向服务器的监听的如何实现呢?其实比较简单,直接使用PF自带的服务进行创建并监听即可,代码如下:
listener = new pf_net::connection::manager::Listener;
unique_move(pf_net::connection::manager::Listener, listener, listener_);
bRetCode = listener_->init(m_nMaxPlayer, nPort, szIP);
KGLOG_PROCESS_ERROR(bRetCode);
是不是感觉使用挺容易的?不过到这里功能并没有做完,如果你需要处理连接和断开时的处理则需要注册相应的处理函数(这个是在修复这段代码时,对PF做了一点小小的功能支持调整)。
代码如下(因为原来的处理客户端连接的代码比较多,这里只截取其中一部分,如果有兴趣可以在残码中找到):
listener_->callback_connect([this] (pf_net::connection::Basic * conn) {
std::pair < KPlayerTable::iterator, BOOL > InsRet;
std::string ip;
KPlayerAgency * pPlayer = NULL;
KG_PROCESS_ERROR(m_PlayerTable.size() < (size_t) m_nMaxPlayer);
while (true) {
KPlayerTable::iterator it = m_PlayerTable.find(m_nNextPlayerIndex);
if (it == m_PlayerTable.end()) {
break;
}
m_nNextPlayerIndex++;
}
...
};
listener_->callback_disconnect([this] (pf_net::connection::Basic * conn) {
// 该设置在接受连接时进行处理
auto index = conn->get_param("player_index");
std::string ip = conn->socket()? conn->socket()->host() : "";
KPlayerAgency * pPlayer = NULL;
KGLOG_PROCESS_ERROR((index.type != pf_basic::type::kVariableTypeInvalid));
pPlayer = GetPlayer(index.get < int32_t > ());
KGLOG_PROCESS_ERROR(pPlayer);
OnDisconnect(pPlayer);
KGLogPrintf(KGLOG_INFO,
"Player disconnect from %s, index = %d
",
ip.c_str(), index.get());
Exit0:
return;
});
由于要配置这个项目的心跳逻辑,因此无法直接使用PF的运行模式(实际上是可以的,不过为了尽可能的改动不多),于是在这里PF又提供了一个基础环境初始化的接口,方便注册网络包的处理以及可以使用日志等接口。
实现如下:
// PF basic enviroment.
r = pf_engine::init_basic_env();
KG_PROCESS_ERROR(r);
使用上面的模式可以脱离pf::Application启动,而且基本的接口都能正常使用,但为了正常的使用,现在需要在你的程序逻辑成功后加上如下代码:
// 标记启动应用
GLOBALS["app.status"] = kAppStatusRunning;
目前网络上已经有了这个游戏的一键启动,在这里我也放一张进入游戏的图:
有兴趣的朋友们如果需要学习和研究这份残码,我这里可以简单总结一下,它是10年初的版本,因此大概就是1.5的70版本,网上由于流出的脚本是80的,而且很多的脚本残缺不全,导致了许多的AI甚至物品以及技能无法正常使用。以前也玩过这个游戏,是游戏刚出来的时候(大约09年左右),我觉得这个游戏的技术在当时还是很不错的,无论和画面还是其他各方面在当时国内算得上是前沿。如果作为学习和兴趣,尝试慢慢修复各种脚本需要很长一段时间,就算是比较精通脚本,要达到完整也不容易。
如果喜欢本游戏的还是建议大家到官网下载该游戏,这款游戏算是目前国内花钱不太多的游戏之一了,而且经过十多年的更替,其画质和各方面都表现的比较出色。
这次选择使用这段残码作为PF的一个实验,证明了PF在实际应用中还是具有其特性的简单、快速、高效的目的,当然为了完善在实际应用中做了略微的调整。当前PF还是有实际应用的不足,但是我相信在未来可以得到逐步的完善。
PF2.0的版本或许在将来,使用全新的如C++23进行一次重大更新。
文章来自https://www.cnblogs.com/lianyue/p/16436100.html
留言与评论(共有 0 条评论) “” |