本文内容是游戏服务器之主动连接线程池,功能是实现处理服务器之间主动发起的连接的网络的数据收发。
一般来说,在场景服务器里连接其他服务器进程的连接是启动了新的线程的(如 场景服务器的主动连接:db服务器、社会服务器、日志服务器)。
但是也有不启动线程的客户端连接的做法。对于这类方式,他们的网络收发是统一由主动连接线程池来处理。
目前使用案例:
例如游戏服务器的网关服务器的主动连接:社会服务器、db服务器、所有场景服务器。
本文内容:
1、线程类型
(1)连接测试线程
(2)验证线程
(3)网络处理线程
2、连接池初始化
3、创建连接到连接池
4、主动连接会话结构
1、线程类型
客户端连接池有3类:连接测试线程、验证线程、网络收发处理线程。
(1)连接测试线程
connection_thread_manager<check_connect_thread> checkconnectThread
连接测试线程是来处理主动连接是否能连接成功,如果成功就转到验证线程来处理,如果失败就一直连接(几秒的间隔)。
(2)验证线程
connection_thread_manager<check_wait_thread> checkwaitThread
在构造函数里创建epoll描述符,
在例程里处理转过来的连接任务列表,处理该TCP连接的验证,如果验证不通过,需要回收这个连接。
验证方式是处理该连接的epoll事件看有没有错误,处理连接自定义的checkRebound ,这个根据需求,是否需要检查该连接的合法性。
如:中心服务器主动连接到登录服务器(使用主动连接线程池),需要在登录服务器创建会话对象后,在同步线程中返回消息给中心服务器后,中心服务器验证线程的对该主动连接的验证工作才算成功通过。
void check_wait_thread::run()
{
...
if (!tasks.empty())
{
int retcode = epoll_wait(kdpfd, &epfds[0], task_count, 0);
if (retcode > 0)
{
for(int i = 0; i < retcode; i++)
{
tcp_client *task = (tcp_client *)epfds[i].data.ptr;
if (epfds[i].events & (EPOLLERR | EPOLLPRI))
{
//套接口出现错误
remove(task);
task->resetState();
}
else if (epfds[i].events & EPOLLIN)
{
switch(task->checkRebound())//验证连接
{
case 1:
//验证成功,获取下一个状态
remove(task);
if (!pool->addMain(task))
task->resetState();
break;
case 0:
//超时,下面会处理
break;
case -1:
//验证失败,回收任务
remove(task);
task->resetState();
break;
}
}
}
}
}
...
}
(3)网络处理线程
connection_thread_manager<main_client_thread> clienttaskThread
处理网络的实际数据的收发。
详细参考:http://blog.csdn.net/chenjiayi_yun/article/details/31765803 内容大致是相同的。
只是那里是被动连接的数据收发处理为例,这里是主动连接的数据收发处理。
2、连接池初始化
初始化线程池中的各类线程的数量
bool tcp_client_pool::init(const uint32 perThreadSize)
{
//测试线程数量1个
if (!checkconnectThread.init(1, 1, "checkconnectThread",this))
{
return false;
}
//验证线程数量1个
if (!checkwaitThread.init(1, 1, "checkwaitThread",this))
{
return false;
}
main_client_thread::settMaxSize(perThreadSize);
int maxThreadCount = (maxConns + main_client_thread::getMaxSize() -1)/main_client_thread::getMaxSize();
//网络处理线程数量跟连接最大限制有关
if (!clienttaskThread.init(1, maxThreadCount, "clienttaskThread",this))
{
return false;
}
return true;
}
3、创建连接到连接池
例如创建主动到数据库的连接,并添加到线程池。
网关跟档案服务器的连接 。
dbClient = new db_client("Record", serverEntry->pstrExtIP, serverEntry->wdExtPort,serverEntry->ServerID);
if(!dbClient || !clientPool->put(dbClient))
{
g_log->error("没有足够内存,不能建立db 服务器客户端实例");
return false;
}
线程池会把连接加入到连接线程,等到线程池启动后,连接测试线程会处理这些主动连接的连接测试,
连接失败会一直连接,连接成功后会验证连接,验证成功后就转到网络处理线程(网络处理线程可能有多个,选择其中一个来处理)。
bool tcp_client_pool::put(tcp_client *task)
{
check_connect_thread *pThread = checkconnectThread.getOne();
if (pThread)
{
pThread->_add(task);
}
else
{
g_log->fatal("%s: 不能得到一个空闲线程", __PRETTY_FUNCTION__);
}
return true;
}
其中:
class db_client : public client_no_msg_queue ,没有使用消息队列,那么消息的处理就会在线程clienttaskThread(网络数据处理线程)里处理了
4、主动连接会话结构
客户端连接tcp_client包含的内容含:
连接任务类型 uint32 taskType;
底层套接口 tcp_socket *pSocket;
连接状态volatile ConnState state;
是否含有读事件 bool fdsradd;
是否支持压缩 const bool compress;
服务器地址 const std::string ip;
服务器端口 const unsigned short port;
使用套接口收发。
...