除了网络架构,业务逻辑的处理更加复杂,为了保证实时性,在处理业务逻辑的时候尽量少用搜索技术,而应该用空间换时间,静态数组是不错的选择,业务逻辑的处理架构其实就是消息映射服务器,通过POST_MSG注册一个回调函数,这个回调函数就是处理具体的业务逻辑的,业务逻辑由协议实现,就是两端商量好的约定俗成的东西:
#define POST_MSG(n,h) if (dispatch[n]) { return -1; } else dispatch[n] = (dispatcher_t)h
Shm_push将业务数据从父进程压入对应子进程的共享内存当中,父进程对数据一无所知,然后子进程从shm_pop开始处理历程,其实就是在net_loop中的handle_recv_queue中调用的shm_pop,这是个循环处理的过程。一个switch开关将pop出来的数据进程分类,大致上分成了关闭包,数据包等等,如果是数据包的话就要进入handle_process的流程:
int handle_process(uint8_t* recvbuf, int rcvlen, int fd, int is_conn)
{
int err = 0;
sprite_t* p;
if (is_conn) {
fdsession_t* fdsess = get_fdsess(fd);
if (fdsess) {
fdsess->last_tm = now.tv_sec;
if ( (err = parse_protocol(recvbuf, rcvlen, fdsess)) )
shm_ctl_block_push(&(config_cache.bc_elem->sendq), fd, FIN_BLOCK, 1);
}
} else { //如果是子进程的话,由于它要和数据库代理服务器进行交互,所以也要处理网络连接
if ( (err = worker_handle_net(fd, recvbuf, rcvlen, &p)) && p )
del_sprite_conn(p, 1);
}
return 0;
}
parse_protocol是核心的调用,在经过一些错误判断和处理后直接将处理流路由到dispatch_protocol函数,在说这个函数之前,有一点要注意,在parse_protocol中会从数据头得到一个session,从这个session中可以得到用户的ID,由于所有的登录用户都会由一个叫做sprite_t的解构体表征,而且只要登录后没有退出的时间段里,这个结构体一直都在,系统会将这些结构体放到一个hash中,这个hash的索引其实就是父进程接收的客户端连接的套接字描述符,由于所有的套接字描述符都在父进程处理,因此它们都是唯一的。等到parse_protocol中调用get_sprite_by_fd将之取出,由于是一个:
sprite_t* get_sprite_by_fd(int fd)
{
sprite_t* p = g_hash_table_lookup(all_sprites, &fd);
if ( !p || IS_NPC_ID(p->id) ) {
return 0;
}
return p;
}
如果说hash是通过fd来唯一索引的原因是因为父进程中套接字连接描述符的唯一性,那么子进程本身也要处理网络连接,比如和数据库代理服务器以及心跳服务器的连接,并且子进程的网络业务处理逻辑和父进程放入共享内存的数据的处理逻辑使用一套框架,如果这些网络连接描述符也加入到hash的话,势必会引起冲突,毕竟父子进程的描述符不必唯一。解决办法就是将请求发往数据库代理服务器的时候连同这个请求的发起者的客户端实体也一并发过去,这样在数据库代理的应答包当中就可以方便的取出原来的客户端实体,这里是用客户端的套结字描述符来作为客户实体标示的,在数据库的应答包当中可以取出来这个描述符fd,然后同样调用get_sprite_by_fd就可以取出关于这个请求的客户端的sprite_t。
总的来说,dispatch_protocol之前的处理逻辑都是和业务协议无关的,仅仅是数据本身以及用户会话实体方面的处理,接下来就是dispatch_protocol了,这个函数里面就开始了特定用户实体的业务协议相关的处理流程了,这个函数最终调用err = dispatch[cmd](p, body, len);,看看dispatch数组:
dispatcher_t dispatch[MAX_PROC_MSG_NUM];
刚才说到的POST_MSG宏的含义就是注册这些回调函数,这个dispatch_protocol函数可以解析数据包的协议类型,然后将请求路由到正确的处理函数中,以一个例子说明,比如一个报名协议的处理函数是race_sign_cmd:
int race_sign_cmd(sprite_t* p, const uint8_t* body, int bodylen)
{
return send_request_to_db(SVR_PROTO_RACE_SIGN, p, 0, NULL, p->id);
}
这个协议没有数据体,仅仅是一个头部,由于在online端无法处理这个和数据相关的请求,下一步就需要将请求进一步转发给数据库代理服务器,也就是send_request_to_db调用。以下就是数据层的处理了,如果online子进程可以处理客户端的清求,那么就会直接返回处理结果,这样就不需要进入数据层了,处理逻辑会朝着刚才说的相反的方向返回,如果进入数据层的话,就有点复杂了。
本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1274106