本文内容是游戏服务器之连接接收。
主要功能是接收和创建连接会话并添加到服务器被动连接线程池(到验证连接线程).功能的运行是在进程的主线程,
代码的实现是在服务器的基类。
1、服务器网络的初始化
(1)初始化epoll
(2)初始化接收socket
2、服务器循环,创建会话
1、服务器网络的初始化
(1)初始化epol
volatile bool shutdown_server = false;//多线程使用的标识关闭网络服务
server_base::server_base(const std::string& s_name)
{
sock_handler = -1;
epoll_handler = epoll_create(1);
assert(-1 != epoll_handler);
id = 0;
ServerType = 0;
name = s_name;
taskPool = NULL ;
}
(2)初始化监听socket
bool server_base::bind_socket(uint16 port)
{
.....
struct sockaddr_in addr;
if (-1 != sock_handler)
{
g_log->error("服务器可能已经初始化");;
return false;
}
(2-1)创建接收socket
//监听socket设置
sock_handler = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (-1 == sock_handler)
{
g_log->error("创建套接口失败");
return false;
}
(2-2)设置监听socket可重用
//设置套接口为可重用状态
int reuse = 1;
if (-1 == ::setsockopt(sock_handler, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)))
{
g_log->error("不能设置套接口为可重用状态");
TEMP_FAILURE_RETRY(::close(sock_handler));
sock_handler = -1;
return false;
}
(2-3)设置监听socket发送接收缓冲(128k)
//设置套接口发送接收缓冲(128k),并且服务器的必须在accept之前设置
socklen_t window_size = 128 * 1024;
if (-1 == ::setsockopt(sock_handler, SOL_SOCKET, SO_RCVBUF, &window_size, sizeof(window_size)))
{
TEMP_FAILURE_RETRY(::close(sock_handler));
return false;
}
if (-1 == ::setsockopt(sock_handler, SOL_SOCKET, SO_SNDBUF, &window_size, sizeof(window_size)))
{
TEMP_FAILURE_RETRY(::close(sock_handler));
return false;
}
(2-4)监听socket绑定地址和端口
//设置网络服务绑定的地址和端口
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
int retcode = ::bind(sock_handler, (struct sockaddr *) &addr, sizeof(addr));
if (-1 == retcode)
{
g_log->error("不能绑定服务器端口:%u",port);
TEMP_FAILURE_RETRY(::close(sock_handler));
sock_handler = -1;
return false;
}
(2-5)监听socket开始监听
retcode = ::listen(sock_handler, MAX_WAITQUEUE);
if (-1 == retcode)
{
g_log->error("监听套接口失败");
TEMP_FAILURE_RETRY(::close(sock_handler));
sock_handler = -1;
return false;
}
(2-6)把监听socket加入到epoll的监听事件里
//把监听socket加入到epoll的监听事件里
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = NULL;
assert(0 == epoll_ctl(epoll_handler, EPOLL_CTL_ADD, sock_handler, &ev));
g_log->info("绑定服务器 %s:%u 成功", this->name.c_str(), port);
return true;
}
2、服务器网络接收功能
(1)服务器循环
//服务器的循环。
void server_base::loop()
{
//初始化服务器,并等待依赖的服务器都启动后,中心服务器发来的可以启动该消息后,开始进入服务器循环
if(init() && start_ok())
{
while(!shutdown_server)
{
if (!service_callback())//一直接收连接
{
break;
}
}
}
else
{
g_log->error("服务器还没初始化完毕");
}
final();
}
(2)接收连接
//回调处理网络连接事件
//处理接收socket,有连接事件的话就建立新的 连接session
bool server_base::service_callback()
{
struct sockaddr_in addr;
bzero(&addr, sizeof(struct sockaddr_in));
int retcode = -1;
socklen_t len = sizeof(struct sockaddr_in);
struct epoll_event ev;
int rc = epoll_wait(epoll_handler, &ev, 1, T_MSEC);// epoll描述符来监听事件(每次只监听一个事件)
if (1 == rc && (ev.events & EPOLLIN))//有接收事件才接收监听socket的连接
{
retcode = TEMP_FAILURE_RETRY(::accept(sock_handler, (struct sockaddr *)&addr, &len));//如果
}
if (retcode >= 0)
{
newTCPTask(retcode, &addr);//把接收的socket来建立新的会话
}
return true;
}
btw:
TEMP_FAILURE_RETRY 是一直尝试执行指定的函数,
如果返回-1并且errno被设置为中断(就是收到的是中断信号)就仍继续该表达式,
使用场景是对于处理中断信号会返回-1的表达式,中断过后可以继续该表达式的逻辑(这里处理的是系统函数::accept)
/* Evaluate EXPRESSION, and repeat as long as it returns -1 with `errno'
set to EINTR. */
# define TEMP_FAILURE_RETRY(expression) \
(__extension__ \
({ long int __result; \
do __result = (long int) (expression); \
while (__result == -1L && errno == EINTR); \
__result; }))
#endif
(3)创建连接会话
创建连接会话后会添加到被动连接线程池的验证线程里。
例如,在场景服务器创建会话(在子类实现创建新的会话,场景服务器继承服务器基类)。
void scene_server::newTCPTask(const int sock, const struct sockaddr_in *addr)
{
//g_log->debug(__PRETTY_FUNCTION__);
scene_session *tcpTask = new scene_session(sock, addr);
if(!tcpTask)
//内存不足,直接关闭连接
TEMP_FAILURE_RETRY(::close(sock));
else if(!taskPool->addVerify(tcpTask))//得到了一个正确连接,添加到验证队列中(验证队列在验证线程中,添加时要加锁)
{
SAFE_DELETE(tcpTask);
}
}
//添加到验证队列
bool ::addVerify(tcp_session *task)
{
verify_thread *pThread = verifyThreads.getOne();
if(pThread)
{
// state_notuse -> state_verify
/*
* cjy
* 先设置状态再添加容器
*/
task->getNextState();
pThread->add(task);
}
else
{
g_log->error("没有找到验证线程添加任务");
}
return true;
}