独自の要件
カスタマーサービス GM は、ゲームサーバー内のすべてのプレイヤーを友達として追加し、チャットすることができます。具体的な機能は以下の通りです:
* オンラインでもオフラインでも GM
* ゲーマーを友達として追加します
* ゲームプレイヤーを友達から削除します
* GM がチャットメッセージを送信します
* プレイヤーのプッシュチャットメッセージ
追加の制限: 1 つの GM アカウントで複数のゲーム プレーヤーをフレンドとして追加できますが、1 つのゲーム プレーヤーは 1 つの GM アカウントでのみ追加できます
要件分析
なぜなら、私たちのゲームにはクロスサーバー チャットやクロスサーバー フレンドなどの機能がないためです。 、将来的にはサポートされないため、GM がゲーム内でキャラクターを作成し、各ゲームサーバーからプレイヤーをチャットに追加するという解決策は実装できません。 また、GMは実際にはゲームキャラクターではないので、ゲーム内で作成する必要はない。
全体の難しさは、各ゲームサーバーが GM から送信されるさまざまなデータにどのようにアクセスできるようにするか、およびプレイヤーデータを GM にプッシュする方法です。
具体的な実装
さまざまなサーバーへの GM データの転送を実現するために、簡単な解決策を採用しました。GM データを Web サーバーに配置し、各ゲームサーバーが Web サーバーから定期的にデータを取得することです。
この解決策は非常に簡単で、Web サーバーとゲーム サーバーの間で長時間接続する必要はなく、http の get メソッドと post メソッドを直接使用してデータを取得できます。全体の構造は次のとおりです:
<code><span>GM1</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>运维聊天服</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>游戏web服务器</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>游戏服务器1</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>-</span><span>游戏客户端1</span><span>|</span><span>|</span><span>|</span><span>|</span><span>|</span><span>|</span><span>GM2</span><span>游戏服务器2</span><span>游戏客户端2</span></code>
ここでは、カスタマー サービス GM1 と GM2 は両方とも Web インターフェイスを使用してゲーム クライアントとチャットします。
運用および保守用のチャット サーバーが存在する理由:
※GMの作成には運用保守側の承認が必要です。 。
* ゲームの Web サーバーをホワイトリストに登録することができ、運用および保守用のチャット サーバーの IP のみがゲームの Web サーバーにアクセスできます
上の図では、ゲーム クライアントとゲーム サーバーのみが tcp ロング接続を使用し、その他は http ショート接続を使用して実装されています。
Web サーバーは、nodejs の代わりに nginx を使用します。 nginx ソリューションは非常に成熟しており、展開が簡単です。
私は lua よりも Python のほうに精通しているため、lua で直接記述する代わりに、プロキシとして uwsgi を使用しました。
データベースは Redis を使用しており、Redis はスケジュールされた保存を設定しています。データ形式の設定については以前の記事を参照してください。 基本的には gm:%d:name
の形式になっています。key は gmx の名前を表します。
実装コード
nginxとuwsgiの設定は省略しています。プライバシー上の理由から、関連する IP は省略されており、コードには十分なコメントがあるため、詳細には触れません:
<code><span>#encoding: utf-8</span><span>""" 新功能: * GM注册 * GM上线 * GM下线 * 加游戏玩家为好友 * 删除游戏好友 * GM推聊天信息 * 玩家推聊天信息 --- 消息数据格式为utf-8处理后的base64编码:游戏服和GM发过来的都是base64格式,要注意分隔符没做base64处理 GS只能用get方式推送消息,所以参数用类似于urllib quote(urlencode)进行了封装。运维客户端也用get 一个GM账号能添加多个游戏玩家为好友,而一个游戏玩家只能被一个GM账号添加 """</span><span>from</span> config <span>import</span> * <span>from</span> json <span>import</span> dumps, loads <span>import</span> base64 <span>import</span> urllib <span>import</span> urllib2 <span>import</span> copy <span>import</span> redis MSG_SEPARATOR = <span>","</span><span>#分割信息</span> MAX_RECV_AMOUNT = <span>10</span><span>#每次消息10条吧</span> MSG_MAX_LEN = <span>500</span><span>#消息不弄太长了</span>CONTENT_TYPE = [(<span>"Content-Type"</span>,<span>"text/html"</span>)] HTTP_STATUS = { <span>200</span>: <span>"200 OK"</span>, <span>404</span>: <span>"404 Not Found"</span>, } GAME_SERVER_INFO_URL = <span>"http://xxxxxyyyyy"</span>ROLE_INFO_URL = <span>"http://xxyyy?uid=%s&hostnum=%s"</span>red = redis.StrictRedis(host=REDIS.HOST, port=REDIS.PORT, db=REDIS.DB) <span>#游戏服务器IP白名单</span><span>if</span><span>not</span> globals().has_key(<span>"gServerIP"</span>): gServerIP = {} res_data = urllib2.urlopen(GAME_SERVER_INFO_URL) res = res_data.read() res_list = res.split(<span>"\n"</span>) <span>for</span> val <span>in</span> res_list: <span>if</span><span>not</span> val: <span>continue</span> _, port, ip, _ = val.split(<span>" "</span>) gServerIP[ip] = port gGMIP = { <span>"xxxxyyyy"</span> : <span>1</span>, } <span><span>def</span><span>is_gm_account_exist</span><span>(account_id)</span>:</span><span>if</span> red.get(<span>"gm_account:%s:name"</span> % account_id): <span>return</span><span>1</span><span>return</span><span>0</span><span><span>def</span><span>is_inter_server</span><span>(hostnum)</span>:</span><span>if</span> ( int(hostnum) >= <span>1000</span> ): <span>return</span><span>0</span><span>return</span><span>1</span><span><span>def</span><span>check_is_int</span><span>(account_id)</span>:</span><span>try</span>: int(account_id) <span>except</span>: <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: -<span>1</span>}) <span>return</span> () <span>#gm client ensures the id is unique</span><span><span>def</span><span>gm_create_account</span><span>(env, params)</span>:</span> account_id, account_name = params[<span>"gm_account"</span>], urllib.unquote(params[<span>"gm_name"</span>]) check_res = check_is_int(account_id) <span>if</span> check_res: <span>return</span> check_res <span>if</span> is_gm_account_exist(account_id): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>1</span>}) <span>#1 the role exists</span> red.set(<span>"gm_account:%s:name"</span> % account_id, account_name) red.sadd(<span>"gm_online_list"</span>, account_id) <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>}) <span>#check param</span><span><span>def</span><span>gm_add_friend</span><span>(env, params)</span>:</span> var = gm_account, hostnum, usernum = params[<span>"gm_account"</span>], params[<span>"host"</span>], params[<span>"uid"</span>] <span>for</span> num <span>in</span> var: check_res = check_is_int(num) <span>if</span> check_res: <span>return</span> check_res <span>if</span><span>not</span> is_gm_account_exist(gm_account): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>2</span>}) <span>#2 the role doesn't exist</span><span>if</span> ( red.get(<span>"gs_usernum:%s:friend"</span> % usernum) ): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>3</span>}) <span>#3 the usernum has gotten a friend</span><span>#内服计费没存数据,就不处理了</span><span>if</span><span>not</span> is_inter_server(hostnum): http_res_data = urllib2.urlopen(ROLE_INFO_URL % (usernum, hostnum)) res = loads(http_res_data.read()) <span>if</span> (type(res) != type({})) <span>or</span> (res.get(<span>"code"</span>, <span>0</span>) != <span>1</span>): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>4</span>}) <span>#4 the uid doesn't exist</span> red.sadd(<span>"gm_account:%s:friend"</span> % gm_account, usernum) <span>#两边都处理下</span> red.sadd(<span>"gs_hostnum:%s"</span> % hostnum, usernum) <span>#记录该服务器的所有玩家</span> red.set(<span>"gs_usernum:%s:hostnum"</span> % usernum, hostnum) <span>#该玩家的信息</span> red.set(<span>"gs_usernum:%s:friend"</span> % usernum, gm_account) <span>#一个玩家只能被一个gm添加为好友</span> red.sadd(<span>"apply_frd_list"</span>, usernum) <span>#usernum will be added </span> red.hdel(<span>"remove_frd_list"</span>, usernum) <span>#信息残留</span><span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>}) <span><span>def</span><span>gm_remove_friend</span><span>(env, params)</span>:</span> account_id, uid = params[<span>"gm_account"</span>], params[<span>"uid"</span>] <span>if</span><span>not</span> is_gm_account_exist(account_id): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>2</span>}) <span>#2 the role doesn't exist</span><span>if</span> red.get(<span>"gs_usernum:%s:friend"</span> % uid) != account_id: <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>5</span>}) <span># the usernum has friend but isn't the gm</span><span>if</span><span>not</span> red.srem(<span>"gm_account:%s:friend"</span> % account_id, uid): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>4</span>}) <span># the usernum is not a friend of the gm</span> hostnum = red.get(<span>"gs_usernum:%s:hostnum"</span> % uid) red.delete(<span>"gs_usernum:%s:hostnum"</span> % uid) <span>#合服考虑,如果合服了GM手动删除这个玩家吧</span> red.srem(<span>"gs_hostnum:%s"</span> % hostnum, uid) red.delete(<span>"gs_usernum:%s:friend"</span> % uid) red.hset(<span>"remove_frd_list"</span>, uid, hostnum) <span>#uid的信息已经丢失,先额外保存下hostnum信息</span> red.srem(<span>"apply_frd_list"</span>, uid) <span>#信息残留</span><span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>}) <span>#GM账号很少</span><span><span>def</span><span>gm_online</span><span>(env, params)</span>:</span> account_id = params[<span>"gm_account"</span>] <span>#可能客户端bug没发下线,直接sadd吧</span><span>if</span><span>not</span> is_gm_account_exist(account_id): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>2</span>}) <span>#2 the role doesn't exist</span> red.sadd(<span>"gm_online_list"</span>, account_id) <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>}) <span><span>def</span><span>gm_offline</span><span>(env, params)</span>:</span> account_id = params[<span>"gm_account"</span>] <span>if</span><span>not</span> red.srem(<span>"gm_online_list"</span>, account_id): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>}) <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>}) <span>#存在usernum上,gs_msg和gm_msg</span><span><span>def</span><span>gm_sendmsg</span><span>(env, params)</span>:</span> account_id, uid, msg = params[<span>"gm_account"</span>], params[<span>"uid"</span>], urllib.unquote(params[<span>"msg"</span>]) <span>#只能向好友发</span><span>if</span><span>not</span> red.sismember(<span>"gm_account:%s:friend"</span> % account_id, uid): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>4</span>}) <span># the usernum is not a friend of the gm</span><span>if</span> red.get(<span>"gs_usernum:%s:friend"</span> % uid) != account_id: <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>5</span>}) <span># the usernum has friend but isn't the gm or doesn't have</span> red.lpush(<span>"gs_usernum:%s:msg_from_gm"</span> % uid, msg) red.sadd(<span>"gm_newmsg_list"</span>, uid) <span>#gs get msg from this set</span><span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>}) <span>#gm轮训所有的,他那边还有个服务器...</span><span>#{gm_account:{"uid": msg, "uid2": msg2}}</span><span><span>def</span><span>gm_receivemsg</span><span>(env, params)</span>:</span> user_set = copy.copy(red.smembers(<span>"gs_newmsg_list"</span>)) msg_data = {} <span>for</span> uid <span>in</span> user_set: gm_account = red.get(<span>"gs_usernum:%s:friend"</span> % uid) <span>if</span><span>not</span> gm_account: <span>#理论上是不会</span><span>continue</span> msg_list = pop_msg(uid, <span>"msg_from_gs"</span>) send_msg = MSG_SEPARATOR.join(msg_list) <span>if</span><span>not</span> send_msg: <span>continue</span><span>if</span><span>not</span> gm_account <span>in</span> msg_data: msg_data[gm_account] = [] msg_data[gm_account].append({<span>"uid"</span> : uid, <span>"msg"</span> : send_msg}) <span>#red.srem("gs_newmsg_list", uid)</span><span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>, <span>"data"</span>: base64.b64encode(dumps(msg_data))}) <span><span>def</span><span>pop_msg</span><span>(uid, msg_type)</span>:</span> msg_list = [] msg_key = <span>"gs_usernum:%s:%s"</span> % (uid, msg_type) msg_len = min(MAX_RECV_AMOUNT, red.llen(msg_key)) <span>for</span> i <span>in</span> xrange(msg_len): piece_msg = red.rpop(msg_key) msg_list.append(piece_msg) <span>return</span> msg_list <span>#---------------------GS----------------------</span><span>#apply and remove</span><span><span>def</span><span>get_frd_relation</span><span>(env, params)</span>:</span> host = params[<span>"host"</span>] apply_user_set = copy.copy(red.smembers(<span>"apply_frd_list"</span>)) apply_data = {} <span>#{"res":1 "data":base64({uid: gm_account})}</span><span>for</span> uid <span>in</span> apply_user_set: hostnum = red.get(<span>"gs_usernum:%s:hostnum"</span> % uid) <span>if</span> hostnum != host: <span>continue</span> account_id = red.get(<span>"gs_usernum:%s:friend"</span> % uid) <span>if</span><span>not</span> account_id: <span>#error </span><span>continue</span> apply_data[uid] = [account_id, red.get(<span>"gm_account:%s:name"</span> % account_id)] red.srem(<span>"apply_frd_list"</span>, uid) del_user_list = red.hkeys(<span>"remove_frd_list"</span>) remove_list = [] <span>for</span> uid <span>in</span> del_user_list: hostnum = red.hget(<span>"remove_frd_list"</span>, uid) <span>if</span> hostnum != host: <span>continue</span> remove_list.append(uid) red.hdel(<span>"remove_frd_list"</span>, uid) <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>, <span>"apply_data"</span>: base64.b64encode(dumps(apply_data)), <span>"remove_data"</span>: base64.b64encode(dumps(remove_list))}) <span><span>def</span><span>gs_sendmsg</span><span>(env, params)</span>:</span> uid, msg = params[<span>"uid"</span>], urllib.unquote(params[<span>"msg"</span>]) <span>if</span><span>not</span> red.get(<span>"gs_usernum:%s:friend"</span> % uid): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>0</span>, <span>"errno"</span>: <span>5</span>}) <span># the usernum has friend but isn't the gm or doesn't have</span> red.lpush(<span>"gs_usernum:%s:msg_from_gs"</span> % uid, msg) red.sadd(<span>"gs_newmsg_list"</span>, uid) <span>#gm get msg from this set</span><span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>}) <span><span>def</span><span>gs_receivemsg</span><span>(env, params)</span>:</span> host = params[<span>"host"</span>] user_set = copy.copy(red.smembers(<span>"gm_newmsg_list"</span>)) total_msg_list = [] <span>for</span> uid <span>in</span> user_set: hostnum = red.get(<span>"gs_usernum:%s:hostnum"</span> % uid) <span>if</span> hostnum != host: <span>continue</span> msg_list = pop_msg(uid, <span>"msg_from_gm"</span>) user_msg = MSG_SEPARATOR.join(msg_list) <span>if</span><span>not</span> user_msg: <span>continue</span> msg_data = { <span>"uid"</span> : uid, <span>"msg"</span> : user_msg, } total_msg_list.append(msg_data) <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>, <span>"data"</span>: base64.b64encode(dumps(total_msg_list))}) <span><span>def</span><span>get_online_list</span><span>(env, params)</span>:</span> host = params[<span>"host"</span>] send_list = [] online_list = red.smembers(<span>"gm_online_list"</span>) <span>for</span> account_id <span>in</span> online_list: frd_set = red.smembers(<span>"gm_account:%s:friend"</span> % account_id) <span>for</span> uid <span>in</span> frd_set: <span>if</span> red.get(<span>"gs_usernum:%s:hostnum"</span> % uid) == host: send_list.append(account_id) <span>#只有这个服务器有gm的好友,才通知</span><span>break</span><span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, dumps({<span>"res"</span>: <span>1</span>, <span>"data"</span>: base64.b64encode(dumps(send_list))}) <span>#get: action=create&gm_account&gm_name 创建账号</span><span>#get: action=add&gm_account&host&uid 添加好友</span><span>#get: action=del&gm_account&uid 删除好友</span><span>#get: action=online&gm_account上线</span><span>#get: action=offline&gm_account 下线</span><span>#get: action=send&gm_account&uid&msg 发送消息</span><span>#get: action=receive 轮训消息</span> GM_FUNC = { <span>"create"</span> : gm_create_account, <span>"add"</span> : gm_add_friend, <span>"del"</span> : gm_remove_friend, <span>"online"</span> : gm_online, <span>"offline"</span> : gm_offline, <span>"send"</span> : gm_sendmsg, <span>"receive"</span> : gm_receivemsg, } <span><span>def</span><span>handle_gm_ticket</span><span>(env, params)</span>:</span><span>if</span><span>not</span> gGMIP.get(env[<span>"REMOTE_ADDR"</span>], <span>0</span>): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, <span>"%s has no access to the website"</span> % env[<span>"REMOTE_ADDR"</span>] func = GM_FUNC.get(params[<span>"action"</span>], <span>None</span>) <span>if</span><span>not</span> func: <span>return</span> HTTP_STATUS[<span>404</span>], CONTENT_TYPE, <span>"err action %s"</span> % params[<span>"action"</span>] <span>return</span> func(env, params) <span>#get action=relation&host</span><span>#get action=send&uid&msg</span><span>#get action=receive&host</span><span>#get action=online&host </span> GS_FUNC = { <span>"relation"</span> : get_frd_relation, <span>"send"</span> : gs_sendmsg, <span>"receive"</span> : gs_receivemsg, <span>"online"</span> : get_online_list, } <span><span>def</span><span>handle_gs_ticket</span><span>(env, params)</span>:</span><span>if</span><span>not</span> gServerIP.get(env[<span>"REMOTE_ADDR"</span>], <span>0</span>): <span>return</span> HTTP_STATUS[<span>200</span>], CONTENT_TYPE, <span>"%s has no access to the website"</span> % env[<span>"REMOTE_ADDR"</span>] func = GS_FUNC.get(params[<span>"action"</span>], <span>None</span>) <span>if</span><span>not</span> func: <span>return</span> HTTP_STATUS[<span>404</span>], CONTENT_TYPE, <span>"err action %s"</span> % params[<span>"action"</span>] <span>return</span> func(env, params) </code>
著作権に関する声明: この記事はブロガーによるオリジナルの記事であり、異なる可能性があります。ブロガーの許可なく転載されています。
上記では、nginx+uwsgi+redis を使用してゲーム GM チャット機能を実装する方法を、ゲームの側面も含めて紹介しました。PHP チュートリアルに興味のある友人に役立つことを願っています。