协程在游戏服务器开发中的应用

之前在客户端开发中,就发现了协程在代码中的方便之处。比如我在获取一个资源时,不使用协程的情况下,只能使用回调函数,代码大致如下:


void ProcessPic(string picName)

{

Pictrue pic = getPic(picName);

if(pic == null)

{

requestPicFromServer(picName, ProcessPic2);

return;

}

ProcessPic2(Picture pic);

}


void ProcessPic2(Picture pic)

{

.......

}


也就是代码逻辑被打断了。而使用协程可以避免这种情况,代码大致如下:


void ProcessPic(string picName)

{

Pictrue pic = getPic(picName);

if(pic == null)

{

requestPicFromServer(picName);

do

{

yield return;

}while(null == (pic = getPic(picName)))

}

........

}


在开发服务器代码时,发现这个特点也可以用在服务器逻辑上,可以起到相同的作用。

我们的服务器架构如下:



session和客户端直接相连,负责收包和发包;

logic负责处理逻辑,他会调用script来做具体处理;

script使用脚本语言做游戏逻辑处理,比如lua语言;

transmitter负责发送调度,判断某个包应该发送给哪个session,或者应该发送给client以便其他处理;

client用与连接其他服务器,比如数据库服务器,将需要花费时间的处理发送到其他服务器,并将回包发送给logic处理,。


举一个例子,玩家登录时的处理流程,如果使用传统的回调函数处理方式,情况如下:


A1 session收到客户端的请求包,将包发送给logic;

A2 logic调用script处理,script代码发现此包是玩家登录请求,所以他需要获取玩家信息;

A3 script在本地内存中找不到对应的玩家信息,所以他将玩家信息查询数据库请求和此次操作的上下文信息返回给logic;

A4 logic将包发送到transmitter;

A5 transmitter发现此包不是发送到客户端的,而是需要发送到其他服务器的,所以将此包发送到了client;

A6 client将请求发送给数据库服务器,此次流程结束,这时客户端还没有收到玩家登录的回包。


B1 当client收到数据库返回的玩家信息时,将包发送到logic;

B2 logic调用script处理,script代码发现是玩家信息回包,并且回包中带有上次操作的上下文信息,所以他继续处理登录请求,完成后将结果包返回给logic;

B3 logic将包发送到transmitter;

B4 transmitter发现此包是发送给客户端的,所以他将包发送到指定的session;

B5 session将包发送给客户端,流程结束,客户端收到了玩家登录的回包。


在上面的流程中,步骤A3需要将操作上下文封装在包信息中,这样在步骤B2中才知道应该怎么处理这个回包,而且处理流程也被打断为多个回调函数。

如果游戏中能确保除了首次登录时需要查询数据库信息,其他时候都基本能在本地获取到信息,那么这个回调机制还是可以接受,否则的话,逻辑层将面临着处处代码都被分割为多个回调函数的尴尬情况。


如果使用协程,则可以很好地解决这个问题。示例代码如下:


文件 UserServ.lua


function UserServ:login(userId)
	local dao = require "classes.dao.userDao"
	local user = dao:fetchById(userId)
	-- 此处user永远不为空,所以不用做判断和特殊处理
	-- 处理其他逻辑
	..........
end



文件 UserDao.lua


local userTable = {}

function UserDao:fetchById(userId)
	local user = userTable[userId]
	if user == nil then
		-- 如果玩家信息为空,则填充请求玩家数据的消息
		sendRequest("queryUser", userId)
	
		-- 死循环等待玩家信息,由tick触发循环,我们服务器框架为20ms触发一次tick
		repeat
			coroutine.yield()
		until nil ~= (user = userTable[userId])
	end
		
	return user
end

-- 获取到用户信息的回调函数,填写内存中的用户表
function UserDao:setUser(user)
	userTable[user.id] = user
end



script在处理用户登录时发现本地没有用户数据,他将数据查询请求作为回包(不需要逻辑处理的上下文信息),并且将自身协程暂停。在收到数据库服务器的查询结果后,调用固定的设置函数将用户信息设置好;这样之前暂停的协程可以继续运行,返回有效的玩家信息。

从上面的代码可以看出,将获用户信息的复杂操作封装到数据层(Dao),代码主要逻辑层(Serv)中的逻辑就相当流畅了。


如果有语法错误请大家包涵,文章我在有这个想法但是在实际编码之前就写了,但是现在我们项目的整个游戏框架已经运行起来了,证明这个思想是可行的。

另外一个比较复杂的地方是逻辑层入口处对协程的管理,如果有兴趣并且遇到麻烦了的话可以进一步交流。完整源代码我不能拿出来,是公司的项目,但是可以帮助你解决遇到的问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值