online游戏服务器架构--数据库及事件相关

简介:

Online服务器的第三部分就是数据层,send_request_to_db开始了数据层的处理逻辑:

int send_request_to_db(int cmd, sprite_t* p, int body_len, const void* body_buf, uint32_t id);

在该函数里首先以懒惰的方式连接数据库服务器,获取一个网络连接,注意参数p,如果该参数为空,那么就说明不关心数据库代理服务器返回的数据:

if (!p) pkg->seq = 0;

else pkg->seq = (sprite_fd (p) << 16) | p->waitcmd;

注意以上的代码,如果不关心返回数据,那么直接将pkg的seq字段设置为0即可,如果关心返回结果,就需要用这个seq字段保存一些信息了,比如当前处理的业务协议是什么,还有就是这个客户端实体p的对应的父进程的套结字描述符是多少,然后将这个pkg连同信息体一起发送给数据库代理服务器,等到代理服务器返回的时候会进入worker_handle_net中处理,注意handle_process函数里关于子进程的执行路线:

int worker_handle_net(int fd, void* buf, int len, sprite_t** u)

{

assert( len >= sizeof(server_proto_t) );

server_proto_t* dbpkg = buf;

} else if (fd == proxysvr_fd) {

return handle_db_return(fd, dbpkg, len, u);

}

return 0;

}

执行流进入handle_db_return:

static int handle_db_return(int fd, server_proto_t* dbpkg, int len, sprite_t** sp)

{

int waitcmd = dbpkg->seq & 0xFFFF;

int conn = dbpkg->seq >> 16;

if (!dbpkg->seq || waitcmd == PROTO_LOGOUT) //如果不关心返回数据,则在send_request_to_db就已经将seq设置成了0,于是直接返回,否则取出保存的fd信息

return 0;

if (!(*sp = get_sprite_by_fd(conn)) || (*sp)->waitcmd != waitcmd) {

//出错

}

int err = -1;

switch (dbpkg->ret) {

case 0:

break; //成功

…//处理各种错误码

在处理各种错误码的时候可以根据不同的协议进行不同的动作,协议保存在sprite_t的waitcmd字段中。在没有错误的情况下就会进入数据层的回调处理:

#define DO_MESSAGE(n, func) /

case n: err = func(*sp, dbpkg->id, dbpkg->body, len - sizeof (server_proto_t)); break

和协议层的处理十分类似,也是回调函数的形式,只不过这里没有提前注册,只是简单的封装了一下switch-case开关。对于前面的例子就是:

DO_MESSAGE(SVR_PROTO_RACE_SIGN, race_sign_callback);

int race_sign_callback(sprite_t* p, uint32_t id, char* buf, int len)

{

uint32_t itms[2] = {12999, 12998};

CHECK_BODY_LEN(len, 4);

p->teaminfo.team = *(uint32_t*)buf;

if (p->teaminfo.team != 1 && p->teaminfo.team != 2) {

ERROR_RETURN(("race failed/t[%u %u]", p->id, p->teaminfo.team), -1);

}

db_single_item_op(0, p->id, itms[p->teaminfo.team - 1], 1, 1);

response_proto_uint32(p, p->waitcmd, p->teaminfo.team, 0); //一定要向客户端回应,否则客户端将挂起

return 0;

}

一定要返回给客户端一个数据,因为客户端和服务器是一问多答式的,服务器的应答可以分好几部分来返回给客户端,比如一共需要返回5次,那么在这5次全部返回之间,服务器是不接受同一个客户端的别的请求的,必然是一问多答,而不是多问多答。注意send_to_self的最后一个参数的意义:

int send_to_self(sprite_t *p, uint8_t *buffer, int len, int completed)

如果completed为1,那么在该函数中就会将p的waitcmd设置为0,代表当前的这个协议已经处理完毕,online可以处理下一个协议请求了,否则就意味着当前的协议还没有处理完毕,online不接收新的协议请求,这个在dispatch_protocol中体现:

if (p->waitcmd != 0) {

send_to_self_error(p, cmd, -ERR_system_busy, 0);

WARN_RETURN(("wait for cmd=%d, id=%u, new cmd=%d", p->waitcmd, p->id, cmd), 0);

}

Onlien服务器通过这种方式解决了一些同步问题,一条协议没有处理完是不接受另外的协议的。关于数据同步,其实online服务器使用了另外的方案,并没有使用传统的锁之类的,而是使用了一个全局变量,并且onlien中不存在线程的概念,因此基本不存在处理数据时的数据共享访问,因此一个子进程同时只能处理一个客户的请求,因此全局变量msg被定义出来,用来保存需要返回给客户端的消息,注意包含协议头部。最后的问题就是请求和回应时的数据组织了,对于请求包,用UNPKG_UINT32来解析包的内容,j是游标号,需要在外部定义然后在外部使用,初始值就是需要开始解析的位置距离包(也就是b)开始的以字节为单位的大小,比如一个buffer,协议头为8个字节,我们需要解析协议体,也就是有效载荷,那么我们需要如下代码:

Int j = 8, v = count;

UNPKG_UINT32(buffer, count, j);

只要看看下面的定义就一目了然了:

#define UNPKG_UINT32(b, v, j) /

do { /

(v) = ntohl( *(uint32_t*)((b)+(j)) ); (j) += 4; /

} while (0)

对于封包同样的方式,只是将流程反过来了:

#define PKG_UINT32(b, v, j) /

do { /

*(uint32_t*)((b)+(j)) = htonl(v); (j) += 4; /

} while (0)

在往客户端返回包的时候,封包的过程就是用的PKG_UINT32,如果连包头一起封装,那么就是下面的流程:

int j = sizeof(protocol_t); //空余了包头的空间

PKG_UINT32(msg, intbuf1, j); //从包头的下一个字节开始打包

PKG_UINT32(msg, intbuf2, j); //继续打包

关于事件处理器是和数据库相关的处理器并列的逻辑处理器,这个处理器主要处理系统事件的,由于事件分为好多种,如果写进一个协议处理回调函数会使得这个函数的职责太多,不明确,如果每个事件作为一个协议封装,那么又会使整个协议处理器的架构主次不分,很含糊,因此就专门为事件处理单独列一个更低级的层次进行处理,也就是和协议处理不在一个层次,而专门为所有事件单独封装一个协议处理回调函数,然后为了协议处理的清晰,在这个协议处理钩子中将事件分发到不同的事件处理器中,如此一来,事件处理就单独成了一个子层次。



 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1274107

相关文章
|
1月前
|
弹性计算 运维 监控
ECS事件告警
ecs事件告警
32 2
|
1月前
|
SQL NoSQL 前端开发
基于BS架构的饰品购物平台设计与实现(程序+文档+数据库)
基于BS架构的饰品购物平台设计与实现(程序+文档+数据库)
|
8天前
|
关系型数据库 MySQL 数据库
卸载云服务器上的 MySQL 数据库
卸载云服务器上的 MySQL 数据库
22 0
|
3天前
|
关系型数据库 MySQL 数据库连接
Django(四):Django项目部署数据库及服务器配置详解(MySQL)
Django(四):Django项目部署数据库及服务器配置详解(MySQL)
25 11
|
12天前
|
存储 缓存 NoSQL
Redis 服务器指南:高性能内存数据库的完整使用指南
Redis 服务器指南:高性能内存数据库的完整使用指南
|
13天前
|
弹性计算 关系型数据库 MySQL
阿里云数据库服务器价格表,数据库创建、连接和使用教程
阿里云数据库使用流程包括购买和管理。选择所需数据库类型如MySQL,完成实名认证后购买,配置CPU、内存和存储。确保数据库地域与ECS相同以允许内网连接。创建数据库和账号,设置权限。通过DMS登录数据库,使用账号密码连接。同一VPC内的ECS需添加至白名单以进行内网通信。参考官方文档进行详细操作。
70 3
|
20天前
|
SQL 关系型数据库 数据库
OceanBase数据库常见问题之OAT添加服务器预检查的时候报错如何解决
OceanBase 是一款由阿里巴巴集团研发的企业级分布式关系型数据库,它具有高可用、高性能、可水平扩展等特点。以下是OceanBase 数据库使用过程中可能遇到的一些常见问题及其解答的汇总,以帮助用户更好地理解和使用这款数据库产品。
|
29天前
|
存储 机器学习/深度学习 并行计算
阿里云服务器X86计算、Arm计算、GPU/FPGA/ASIC、高性能计算架构区别
在我们选购阿里云服务器的时候,云服务器架构有X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器、高性能计算可选,有的用户并不清楚他们之间有何区别,本文主要简单介绍下不同类型的云服务器有何不同,主要特点及适用场景有哪些。
阿里云服务器X86计算、Arm计算、GPU/FPGA/ASIC、高性能计算架构区别
|
29天前
|
存储 SQL 分布式计算
TiDB整体架构概览:构建高效分布式数据库的关键设计
【2月更文挑战第26天】本文旨在全面概述TiDB的整体架构,深入剖析其关键组件和功能,从而帮助读者理解TiDB如何构建高效、稳定的分布式数据库。我们将探讨TiDB的计算层、存储层以及其他核心组件,并解释这些组件是如何协同工作以实现卓越的性能和扩展性的。通过本文,读者将能够深入了解TiDB的整体架构,为后续的学习和实践奠定坚实基础。
|
1月前
|
监控 JavaScript 安全
监控内网电脑软件设计与实现:基于Node.js的服务器端架构分析
在当今信息技术高度发达的时代,监控内网电脑的需求日益增长。企业需要确保网络安全,个人用户也需要监控家庭网络以保护隐私和安全。本文将介绍一种基于Node.js的服务器端架构,用于设计和实现监控内网电脑软件。
86 0