0

0

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

php中文网

php中文网

发布时间:2016-08-08 09:19:43

|

1424人浏览过

|

来源于php中文网

原创

原始需求

一个客服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表示名字。

实现代码

RoboNeo
RoboNeo

专注影像与设计的AI助手

下载

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白名单ifnot 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: 
        ifnot val:
            continue
        _, port, ip, _  = val.split(" ") 
        gServerIP[ip] = port

gGMIP = {
        "xxxxyyyy" : 1,
}

defis_gm_account_exist(account_id):if red.get("gm_account:%s:name" % account_id):
        return1return0defis_inter_server(hostnum):if ( int(hostnum) >= 1000 ):
        return0return1defcheck_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 uniquedefgm_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 paramdefgm_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
    ifnot is_gm_account_exist(gm_account):
        return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 2}) #2 the role doesn't existif ( 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#内服计费没存数据,就不处理了ifnot 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}) 

defgm_remove_friend(env, params):
    account_id, uid = params["gm_account"], params["uid"]
    ifnot is_gm_account_exist(account_id):
        return HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 0, "errno": 2}) #2 the role doesn't existif 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 gmifnot 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账号很少defgm_online(env, params):
    account_id = params["gm_account"] #可能客户端bug没发下线,直接sadd吧ifnot 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})

defgm_offline(env, params):
    account_id = params["gm_account"]
    ifnot 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_msgdefgm_sendmsg(env, params):
    account_id, uid, msg = params["gm_account"], params["uid"], urllib.unquote(params["msg"]) #只能向好友发ifnot 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 gmif 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 setreturn HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1})

#gm轮训所有的,他那边还有个服务器...#{gm_account:{"uid": msg, "uid2": msg2}}defgm_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)
        ifnot gm_account: #理论上是不会continue
        msg_list = pop_msg(uid, "msg_from_gs")
        send_msg = MSG_SEPARATOR.join(msg_list)
        ifnot send_msg:
            continueifnot 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))})

defpop_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 removedefget_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)
        ifnot 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))})   

defgs_sendmsg(env, params):
    uid, msg = params["uid"], urllib.unquote(params["msg"])
    ifnot 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 setreturn HTTP_STATUS[200], CONTENT_TYPE, dumps({"res": 1})

defgs_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)
        ifnot 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))})

defget_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的好友,才通知breakreturn 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,
}

defhandle_gm_ticket(env, params):ifnot 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)
    ifnot 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,
}

defhandle_gs_ticket(env, params):ifnot 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)
    ifnot func:
        return HTTP_STATUS[404], CONTENT_TYPE, "err action %s" % params["action"]
    return func(env, params)

版权声明:本文为博主原创文章,未经博主允许不得转载。

以上就介绍了用nginx+uwsgi+redis实现游戏GM聊天功能,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。

相关文章

在线游戏
在线游戏

海量精品小游戏合集,无需安装即点即玩,休闲益智、动作闯关应有尽有,秒开即玩,轻松解压,快乐停不下来

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法
pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法

本专题系统整理pixiv网页版官网入口及登录访问方式,涵盖官网登录页面直达路径、在线阅读入口及快速进入方法说明,帮助用户高效找到pixiv官方网站,实现便捷、安全的网页端浏览与账号登录体验。

616

2026.02.13

微博网页版主页入口与登录指南_官方网页端快速访问方法
微博网页版主页入口与登录指南_官方网页端快速访问方法

本专题系统整理微博网页版官方入口及网页端登录方式,涵盖首页直达地址、账号登录流程与常见访问问题说明,帮助用户快速找到微博官网主页,实现便捷、安全的网页端登录与内容浏览体验。

194

2026.02.13

Flutter跨平台开发与状态管理实战
Flutter跨平台开发与状态管理实战

本专题围绕Flutter框架展开,系统讲解跨平台UI构建原理与状态管理方案。内容涵盖Widget生命周期、路由管理、Provider与Bloc状态管理模式、网络请求封装及性能优化技巧。通过实战项目演示,帮助开发者构建流畅、可维护的跨平台移动应用。

91

2026.02.13

TypeScript工程化开发与Vite构建优化实践
TypeScript工程化开发与Vite构建优化实践

本专题面向前端开发者,深入讲解 TypeScript 类型系统与大型项目结构设计方法,并结合 Vite 构建工具优化前端工程化流程。内容包括模块化设计、类型声明管理、代码分割、热更新原理以及构建性能调优。通过完整项目示例,帮助开发者提升代码可维护性与开发效率。

20

2026.02.13

Redis高可用架构与分布式缓存实战
Redis高可用架构与分布式缓存实战

本专题围绕 Redis 在高并发系统中的应用展开,系统讲解主从复制、哨兵机制、Cluster 集群模式及数据分片原理。内容涵盖缓存穿透与雪崩解决方案、分布式锁实现、热点数据优化及持久化策略。通过真实业务场景演示,帮助开发者构建高可用、可扩展的分布式缓存系统。

54

2026.02.13

c语言 数据类型
c语言 数据类型

本专题整合了c语言数据类型相关内容,阅读专题下面的文章了解更多详细内容。

29

2026.02.12

雨课堂网页版登录入口与使用指南_官方在线教学平台访问方法
雨课堂网页版登录入口与使用指南_官方在线教学平台访问方法

本专题系统整理雨课堂网页版官方入口及在线登录方式,涵盖账号登录流程、官方直连入口及平台访问方法说明,帮助师生用户快速进入雨课堂在线教学平台,实现便捷、高效的课程学习与教学管理体验。

15

2026.02.12

豆包AI网页版入口与智能创作指南_官方在线写作与图片生成使用方法
豆包AI网页版入口与智能创作指南_官方在线写作与图片生成使用方法

本专题汇总豆包AI官方网页版入口及在线使用方式,涵盖智能写作工具、图片生成体验入口和官网登录方法,帮助用户快速直达豆包AI平台,高效完成文本创作与AI生图任务,实现便捷智能创作体验。

598

2026.02.12

PostgreSQL性能优化与索引调优实战
PostgreSQL性能优化与索引调优实战

本专题面向后端开发与数据库工程师,深入讲解 PostgreSQL 查询优化原理与索引机制。内容包括执行计划分析、常见索引类型对比、慢查询优化策略、事务隔离级别以及高并发场景下的性能调优技巧。通过实战案例解析,帮助开发者提升数据库响应速度与系统稳定性。

56

2026.02.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号