您的位置:首页 > 数据库 > Redis

用nginx+uwsgi+redis实现游戏GM聊天功能

2015-07-29 14:49 751 查看

原始需求

一个客服GM能够加所有游戏服内的玩家为好友,并能进行聊天。具体功能如下:

* GM上、下线

* 加游戏玩家为好友

* 删游戏玩家为好友

* GM发送聊天消息

* 玩家推送聊天消息

额外限定:一个GM账号能够添加多个游戏玩家为好友,而一个游戏玩家只能被一个GM账号添加

需求分析

因为我们游戏内并没有跨服聊天、跨服好友这种功能,而且以后也不会支持,所以让GM在游戏里面创建角色,然后加各个游戏服的玩家进行聊天的方案是无法实施的。 而且GM实际上并不是一个游戏角色,也不用在游戏内创建。

整个的难点是,如何让各个游戏服访问到GM发过来的各种数据,如何将玩家的数据推送给GM。

具体实现

为了实现GM的数据在各个服务器传递,我们采用了一种简单的方案:将GM的数据放在我们的web服务器上,各个游戏服定时从web服务器去拿数据。

这种方案很简单,web服务器与游戏服不用长连接,直接用http的get和post方法就可以拿数据了。整个架构如下:

GM1-------运维聊天服--------游戏web服务器------游戏服务器1--------游戏客户端1
|                |                |
|                |                |
GM2             游戏服务器2       游戏客户端2


这里客服GM1和GM2都用的web界面与游戏客户端聊天。

运维聊天服存在是因为:

* GM的创建需要运维那边的审批。。

* 游戏的web服务器可以进行白名单审查,只有运维聊天服的ip可以访问游戏的web服务器

上图中只有游戏客户端与游戏服务器是采用的tcp长连接,其他都是使用http短连接来实现。

web服务器采用的是nginx,而不是nodejs。nginx方案挺成熟的,而且部署也很容易。

由于,我对python的熟悉程度比lua程度高很多,所以用了uwsgi来做代理,而不是直接用lua来写。

而数据库则采用的redis,redis设置了定时存盘了。数据格式设置可参考我之前提的文章,基本都是

gm:%d:name
这种格式,key表示gmx的名字是什么,val表示名字。

实现代码

nginx和uwsgi的配置已略去。因为隐私原因,相关IP已略去,代码里面也有足够的注释,不再赘述:

#encoding: utf-8

"""
新功能:
* GM注册
* GM上线
* GM下线
* 加游戏玩家为好友
* 删除游戏好友
* GM推聊天信息
* 玩家推聊天信息

---
消息数据格式为utf-8处理后的base64编码:游戏服和GM发过来的都是base64格式,要注意分隔符没做base64处理
GS只能用get方式推送消息,所以参数用类似于urllib quote(urlencode)进行了封装。运维客户端也用get

一个GM账号能添加多个游戏玩家为好友,而一个游戏玩家只能被一个GM账号添加

"""

from config import *
from json import dumps, loads
import base64
import urllib
import urllib2
import copy
import redis

MSG_SEPARATOR = "," #分割信息
MAX_RECV_AMOUNT = 10 #每次消息10条吧
MSG_MAX_LEN = 500 #消息不弄太长了

CONTENT_TYPE = [("Content-Type","text/html")]
HTTP_STATUS = {
200: "200 OK",
404: "404 Not Found",
}

GAME_SERVER_INFO_URL = "http://xxxxxyyyyy"

ROLE_INFO_URL = "http://xxyyy?uid=%s&hostnum=%s"

red = redis.StrictRedis(host=REDIS.HOST, port=REDIS.PORT, db=REDIS.DB)

#游戏服务器IP白名单
if not globals().has_key("gServerIP"):
gServerIP = {}
res_data = urllib2.urlopen(GAME_SERVER_INFO_URL)
res = res_data.read()
res_list = res.split("\n")
for val in res_list:
if not val:
continue
_, port, ip, _  = val.split(" ")
gServerIP[ip] = port

gGMIP = {
"xxxxyyyy" : 1,
}

def is_gm_account_exist(account_id):
if red.get("gm_account:%s:name" % account_id):
return 1
return 0

def is_inter_server(hostnum):
if ( int(hostnum) >= 1000 ):
return 0
return 1

def check_is_int(account_id):
try:
int(account_id)
except:
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": -1})
return ()

#gm client ensures the id is unique
def gm_create_account(env, params):
account_id, account_name  = params["gm_account"], urllib.unquote(params["gm_name"])
check_res = check_is_int(account_id)
if check_res: return check_res
if is_gm_account_exist(account_id):
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 1}) #1 the role exists
red.set("gm_account:%s:name" % account_id, account_name)
red.sadd("gm_online_list", account_id)
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1})

#check param
def gm_add_friend(env, params):
var = gm_account, hostnum, usernum = params["gm_account"], params["host"], params["uid"]
for num in var:
check_res = check_is_int(num)
if check_res: return check_res
if not is_gm_account_exist(gm_account):
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 2}) #2 the role doesn't exist

if ( red.get("gs_usernum:%s:friend" % usernum) ):
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 3}) #3 the usernum has gotten a friend

#内服计费没存数据,就不处理了
if not is_inter_server(hostnum):
http_res_data = urllib2.urlopen(ROLE_INFO_URL % (usernum, hostnum))
res = loads(http_res_data.read())
if (type(res) != type({})) or (res.get("code", 0) != 1):
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 4}) #4 the uid doesn't exist

red.sadd("gm_account:%s:friend" % gm_account, usernum) #两边都处理下

red.sadd("gs_hostnum:%s" % hostnum, usernum) #记录该服务器的所有玩家
red.set("gs_usernum:%s:hostnum" % usernum, hostnum) #该玩家的信息
red.set("gs_usernum:%s:friend" % usernum, gm_account) #一个玩家只能被一个gm添加为好友

red.sadd("apply_frd_list", usernum) #usernum will be added
red.hdel("remove_frd_list", usernum) #信息残留

return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1})

def gm_remove_friend(env, params):
account_id, uid = params["gm_account"], params["uid"]
if not is_gm_account_exist(account_id):
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 2}) #2 the role doesn't exist
if red.get("gs_usernum:%s:friend" % uid) != account_id:
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 5})  # the usernum has friend but isn't the gm
if not red.srem("gm_account:%s:friend" % account_id, uid):
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 4})  # the usernum is not a friend of the gm

hostnum = red.get("gs_usernum:%s:hostnum" % uid)
red.delete("gs_usernum:%s:hostnum" % uid) #合服考虑,如果合服了GM手动删除这个玩家吧
red.srem("gs_hostnum:%s" % hostnum, uid)

red.delete("gs_usernum:%s:friend" % uid)
red.hset("remove_frd_list", uid, hostnum) #uid的信息已经丢失,先额外保存下hostnum信息
red.srem("apply_frd_list", uid) #信息残留

return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1})

#GM账号很少
def gm_online(env, params):
account_id = params["gm_account"] #可能客户端bug没发下线,直接sadd吧
if not is_gm_account_exist(account_id):
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 2}) #2 the role doesn't exist
red.sadd("gm_online_list", account_id)
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1})

def gm_offline(env, params):
account_id = params["gm_account"]
if not red.srem("gm_online_list", account_id):
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0})
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1})

#存在usernum上,gs_msg和gm_msg
def gm_sendmsg(env, params):
account_id, uid, msg = params["gm_account"], params["uid"], urllib.unquote(params["msg"]) #只能向好友发
if not red.sismember("gm_account:%s:friend" % account_id, uid):
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 4})  # the usernum is not a friend of the gm
if red.get("gs_usernum:%s:friend" % uid) != account_id:
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 5})  # the usernum has friend but isn't the gm or doesn't have
red.lpush("gs_usernum:%s:msg_from_gm" % uid, msg)
red.sadd("gm_newmsg_list", uid) #gs get msg from this set
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1})

#gm轮训所有的,他那边还有个服务器...
#{gm_account:{"uid": msg, "uid2": msg2}}
def gm_receivemsg(env, params):
user_set = copy.copy(red.smembers("gs_newmsg_list"))
msg_data = {}
for uid in user_set:
gm_account = red.get("gs_usernum:%s:friend" % uid)
if not gm_account: #理论上是不会
continue
msg_list = pop_msg(uid, "msg_from_gs")
send_msg = MSG_SEPARATOR.join(msg_list)
if not send_msg:
continue
if not gm_account in msg_data:
msg_data[gm_account] = []
msg_data[gm_account].append({"uid" : uid, "msg" : send_msg})
#red.srem("gs_newmsg_list", uid)
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1, "data": base64.b64encode(dumps(msg_data))})

def pop_msg(uid, msg_type):
msg_list = []
msg_key = "gs_usernum:%s:%s" % (uid, msg_type)
msg_len = min(MAX_RECV_AMOUNT, red.llen(msg_key))
for i in xrange(msg_len):
piece_msg = red.rpop(msg_key)
msg_list.append(piece_msg)
return msg_list

#---------------------GS----------------------
#apply and remove
def get_frd_relation(env, params):
host = params["host"]

apply_user_set = copy.copy(red.smembers("apply_frd_list"))
apply_data = {}  #{"res":1 "data":base64({uid: gm_account})}
for uid in apply_user_set:
hostnum = red.get("gs_usernum:%s:hostnum" % uid)
if hostnum != host:
continue
account_id = red.get("gs_usernum:%s:friend" % uid)
if not account_id: #error
continue

apply_data[uid] = [account_id, red.get("gm_account:%s:name" % account_id)]
red.srem("apply_frd_list", uid)

del_user_list = red.hkeys("remove_frd_list")
remove_list = []
for uid in del_user_list:
hostnum = red.hget("remove_frd_list", uid)
if hostnum != host:
continue
remove_list.append(uid)
red.hdel("remove_frd_list", uid)
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1, "apply_data": base64.b64encode(dumps(apply_data)), "remove_data": base64.b64encode(dumps(remove_list))})

def gs_sendmsg(env, params):
uid, msg = params["uid"], urllib.unquote(params["msg"])
if not red.get("gs_usernum:%s:friend" % uid):
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 5})  # the usernum has friend but isn't the gm or doesn't have
red.lpush("gs_usernum:%s:msg_from_gs" % uid, msg)
red.sadd("gs_newmsg_list", uid) #gm get msg from this set
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1})

def gs_receivemsg(env, params):
host = params["host"]
user_set = copy.copy(red.smembers("gm_newmsg_list"))
total_msg_list = []
for uid in user_set:
hostnum = red.get("gs_usernum:%s:hostnum" % uid)
if hostnum != host:
continue
msg_list = pop_msg(uid, "msg_from_gm")
user_msg = MSG_SEPARATOR.join(msg_list)
if not user_msg:
continue
msg_data = {
"uid"   : uid,
"msg"   : user_msg,
}
total_msg_list.append(msg_data)
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1, "data": base64.b64encode(dumps(total_msg_list))})

def get_online_list(env, params):
host = params["host"]
send_list = []
online_list = red.smembers("gm_online_list")
for account_id in online_list:
frd_set = red.smembers("gm_account:%s:friend" % account_id)
for uid in frd_set:
if red.get("gs_usernum:%s:hostnum" % uid) == host:
send_list.append(account_id) #只有这个服务器有gm的好友,才通知
break
return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1, "data": base64.b64encode(dumps(send_list))})

#get:  action=create&gm_account&gm_name  创建账号
#get:  action=add&gm_account&host&uid 添加好友
#get:  action=del&gm_account&uid 删除好友
#get:  action=online&gm_account上线
#get:  action=offline&gm_account 下线
#get:  action=send&gm_account&uid&msg 发送消息
#get:  action=receive 轮训消息
GM_FUNC = {
"create"    : gm_create_account,
"add"       : gm_add_friend,
"del"       : gm_remove_friend,
"online"    : gm_online,
"offline"   : gm_offline,
"send"      : gm_sendmsg,
"receive"   : gm_receivemsg,
}

def handle_gm_ticket(env, params):
if not gGMIP.get(env["REMOTE_ADDR"], 0):
return HTTP_STATUS[200], CONTENT_TYPE, "%s has no access to the website" % env["REMOTE_ADDR"]
func = GM_FUNC.get(params["action"], None)
if not func:
return HTTP_STATUS[404], CONTENT_TYPE, "err action %s" % params["action"]
return func(env, params)

#get    action=relation&host
#get    action=send&uid&msg
#get    action=receive&host
#get    action=online&host
GS_FUNC = {
"relation"  : get_frd_relation,
"send"      : gs_sendmsg,
"receive"   : gs_receivemsg,
"online"    : get_online_list,
}

def handle_gs_ticket(env, params):
if not gServerIP.get(env["REMOTE_ADDR"], 0):
return HTTP_STATUS[200], CONTENT_TYPE, "%s has no access to the website" % env["REMOTE_ADDR"]
func = GS_FUNC.get(params["action"], None)
if not func:
return HTTP_STATUS[404], CONTENT_TYPE, "err action %s" % params["action"]
return func(env, params)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: