用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)
相关文章推荐
- Redis 和 Jedis
- redis.conf详解
- 重构set redis
- windows下安装redis
- redis+keepalived高可用
- centos 安装 redis
- Redis消息通知系统的实现
- 基于redis的二级缓存
- Redis相关命令
- 关于Redis五种类型对象的学习笔记
- redis的PHP封装
- php+nginx+redis安装
- NoSQL数据库:Redis适用场景及产品定位
- Redis教程4--Redis数据存储优化机制
- Redis教程3--Redis键值设计
- Redis教程2--Redis数据类型及相关命令
- Redis教程1--入门篇
- 图文介绍PHP添加Redis模块及连接
- python操作redis
- redis 快速入门实战