zl程序教程

您现在的位置是:首页 >  Java

当前栏目

卡牌类游戏游戏大厅——上篇

2023-02-18 16:23:55 时间

阿巩

要睡个好觉喔

通常在打开一个游戏后的基础流程包括登录/注册->选择游戏区域->认证->进入大厅->选择房间/建立房间->进入房间->等待游戏/建立游戏->游戏中。以上环节中,游戏大厅包含了登录和进入游戏前的策略,针对其中的每一环节都需要思考其中的实现细节,今天阿巩将以卡牌类游戏为例,来看实现一个游戏大厅的流程及需要注意的细节。It will all be okay,日拱一卒,我们开始吧!

对于大厅的登录流程,如果游戏使用HTTP/HTTPS连接方式,可以使用反向代理来做负载均衡给出对应区域的ip地址。简易版伪代码如下:

connect_domains = ('a.xxx.com', 'b.xxx.com', 'c.xxx.com')if incoming_ip == 'xxxx':    connect(connect_domains[1])

如果游戏使用TCP/Websocket连接方式,则需要做多服务器同步并做负载均衡。(Websocket事实上是建立在TCP基础上的HTML5协议)然后是进行注册/登录和选择游戏区域,许多游戏是将注册放到网页上,或者在应用内嵌入浏览器,直接打开网页来节约工作量。

对于登录和选游戏区域,其实这两步的顺序并不固定,如果是登录前选择服务器,会先将玩家的选择项传输到网关服务器,再按下确定后,网关通知服务器集群用户登入,之后走登录或者注册的流程;

对于先登录再选服务器区域,由于是短链接(上一篇中有提到过),玩家的信息会完整的存在一台服务器上,所有其他服务器从这台玩家数据的机器获取数据。

假设在数据库中存放了以下字段,这里的game_server用于记录登录区域,在玩家下次登录时直接默认选择。

index, username, password, game_server, level, coins, items, ...

以上字段对于卡牌游戏来说都是必不可少的,如果存放到一台服务器的数据库,当所有服务都走这台服务器的数据库时,对数据库压力可想而知,所以这种方式必须要注意数据备份和读写分离。

第二种方式是将除玩家基础数据外的时常变动的字段如金币,玩家道具等放到游戏服务器。这种方式缓解了方式一数据库的压力,不过缺点是玩家数据被分离,如果切换游戏区域game_server会导致重新计算等级level。

为解决以上两个问题,可以使用数据合并的方式,为每个游戏区域创建单独的数据服务,定时同步更新到主数据库。不过数据同步到主数据库会有时间差,这部分可以增加缓存或者使用中间件来解决。

中间件的定义十分广泛,从不同角度或者不同层次上对中间件的分类有所不同。中间件的出现是为屏蔽操作系统和网络协议的差异,为应用程序提供多种通信机制,并提供相应的平台以满足不同的需求的。这里我们看下最常提起的一类:远程过程调用中间件,即RPC中间件。

一个应用程序使用RPC来远程调用一个位于同地址空间中的过程,其效果和从本地调用一样。一个RPC应用有两部分:server和 client。server提供一个或多个远程过程,client向sever发出远程调用。这里的RPC通信是同步的,采用线程可以进行异步调用。但是由于client与server之间是直接连接,没有中间机构处理请求,RPC需要一些网络细节来定位server;在client发起请求的同时,需要保证server是活动状态。

在进入游戏大厅后,除了游戏内容相关部分,聊天和互动模块也是必不可少的。通常情况下,我们会把聊天服务单独分出来比如:私信,站内信等放到一台高性能的物理机上独立出来。聊天服务的逻辑主要是接受聊天内容,并广播或者单独发给某个玩家,也就是做一个消息转发的服务。当然除此之外,需要增加内容过滤和聊天记录存储等等逻辑。这就要提前将需过滤的词汇放到数据库、缓存或者文件中,生成一份过滤表。如果数据量不大时,可以读取文本并放入列表中,与过滤表进行匹配。

从一个客户端接收到聊天内容转发到另一个客户端,这个过程可以使用HTTP或者TCP两种方式实现。先来看HTTP方式,这种方式是目前游戏中常用的。根据约定好的api规则,客户端通过post请求方式上传聊天内容,服务器端再将聊天内容分发给需要接收消息的人,伪代码如下:

from http.server import BaseHTTPRequestHandler, HTTPServer
import cgi
import json

class PostHandler(BaseHTTPRequestHandler):
    def do_post(self):
        form = cgi.FieldStorage(
            fp=self.rfile,
            headers=self.headers,
            environ={
                'REQUEST_METHOD': 'POST',
                .....
                'CONTENT_TYPE': self.headers['Content-Type'],
            }
        )
        self.responses(200)
        self.end_headers()
        self.wfile.write('Client: %s\n' % str(self.client_address))
        self.wfile.write('User-agent: %s\n' % str(self.headers['user-agent']))
        self.wfile.write('Path: %s\n' % str(self.path))
        for field in form.keys():
            field_item = form[field]
            if field_item.xxx:
                data = field_item.xxx
                len = len(file_data)
                j = json.dump(file_fata)
                return j

if __name__ == '__main__':
    server = HTTPServer(('localhost', 8080), PostHandler)
    server.serve_forever()

第二种方式是使用TCP协议,自己制定协议规则和内容,传统网游采用的是这种方式。假如我们需要这样的协议格式:[header|size|body],伪代码如下:

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 8080)
sock.bind(server_address)
sock.listen(1)
while True:
    print('Waiting for a connection')
    connection, client_address = sock.accept()
    try:
        print(f'from: {client_address}')
        data = connection.recv(1024)
        print(f'receive: {data}')  # do something about data
    finally:
        connection.close()

为了避免篇幅过长,我把大厅与游戏对接的部分放到《卡牌类游戏游戏大厅——下篇》了,明日更新~?

END