加入收藏 | 设为首页 | 会员中心 | 我要投稿 上饶站长网 (https://www.0793zz.com.cn/)- 数据库平台、视觉智能、智能搜索、决策智能、迁移!
当前位置: 首页 > 站长资讯 > 外闻 > 正文

教你从头写游戏服务器框架

发布时间:2019-03-14 09:15:45 所属栏目:外闻 来源:腾讯游戏学院
导读:1.需求 由于越通用的代码,就是越没用的代码,所以在设计之初,我就认为应该使用分层的模式来构建整个系统。按照游戏服务器的一般需求划分,最基本的可以分为两层: 底层基础功能:包括通信、持久化等非常通用的部分,关注的是性能、易用性、扩展性等指标

在协议层面,最基本的需求有“分包”“分发”“对象序列化”等几种需求。如果要支持“请求-响应”模式,还需要在协议中带上“序列号”的数据,以便对应“请求”和“响应”。另外,游戏通常都是一种“会话”式的应用,也就是一系列的请求,会被视为一次“会话”,这就需要协众需要有类似 Session ID 这种数据。为了满足这些需求,设计一个层次为:Protocol。

拥有了以上两个层次,是可以完成最基本的协议层能力了。但是,我们往往希望业务数据的协议包,能自动化的成为编程中的对象,所以在处理消息体这里,需要一个可选的额外层次,用来把字节数组,转换成对象。所以我设计了一个特别的处理器:ObjectProcessor ,去规范通信模块中对象序列化、反序列化的接口。

Transport

此层次是为了统一各种不同的底层传输协议而设置的,最基本应该支持 TCP 和 UDP 这两种协议。对于通信协议的抽象,其实在很多底层库也做的非常好了,比如 Linux 的 socket 库,其读写 API 甚至可以和文件的读写通用。C# 的 Socket 库在 TCP 和 UDP 之间,其 API 也几乎是完全一样的。但是由于作用游戏服务器,很多时候还会接入一些特别的“接入层”,比如一些代理服务器,或者一些消息中间件,这些 API 可是五花八门的。另外,在 html5 游戏(比如微信小游戏)和一些页游领域,还有用 HTTP 服务器作为游戏服务器的传统(如使用 WebSocket 协议),这样就需要一个完全不同的传输层了。

服务器传输层在异步模型下的基本使用序列:

  1. 在主循环中,不断尝试读取有什么数据可读
  2. 如果上一步返回有数据到达了,则读取数据
  3. 读取数据处理后,需要发送数据,则向网络写入数据

根据上面三个特点,可以归纳出一个基本接口:

  1. class Transport { 
  2. public:    
  3.    /** 
  4.     * 初始化Transport对象,输入Config对象配置最大连接数等参数,可以是一个新建的Config对象。 
  5.     */    
  6.    virtual int Init(Config* config) = 0; 
  7.  
  8.    /** 
  9.     * 检查是否有数据可以读取,返回可读的事件数。后续代码应该根据此返回值循环调用Read()提取数据。 
  10.     * 参数fds用于返回出现事件的所有fd列表,len表示这个列表的最大长度。如果可用事件大于这个数字,并不影响后续可以Read()的次数。 
  11.     * fds的内容,如果出现负数,表示有一个新的终端等待接入。 
  12.     */ 
  13.    virtual int Peek(int* fds, int len) = 0; 
  14.  
  15.    /** 
  16.     * 读取网络管道中的数据。数据放在输出参数 peer 的缓冲区中。 
  17.     * @param peer 参数是产生事件的通信对端对象。 
  18.     * @return 返回值为可读数据的长度,如果是 0 表示没有数据可以读,返回 -1 表示连接需要被关闭。 
  19.     */ 
  20.    virtual int Read( Peer* peer) = 0; 
  21.  
  22.    /** 
  23.     * 写入数据,output_buf, buf_len为想要写入的数据缓冲区,output_peer为目标队端, 
  24.     * 返回值表示成功写入了的数据长度。-1表示写入出错。 
  25.     */ 
  26.    virtual int Write(const char* output_buf, int buf_len, const Peer& output_peer) = 0; 
  27.  
  28.    /** 
  29.     * 关闭一个对端的连接 
  30.     */ 
  31.    virtual void ClosePeer(const Peer& peer) = 0; 
  32.  
  33.    /** 
  34.     * 关闭Transport对象。 
  35.     */ 
  36.    virtual void Close() = 0; 
  37.  

在上面的定义中,可以看到需要有一个 Peer 类型。这个类型是为了代表通信的客户端(对端)对象。在一般的 Linux 系统中,一般我们用 fd (File Description)来代表。但是因为在框架中,我们还需要为每个客户端建立接收数据的缓存区,以及记录通信地址等功能,所以在 fd 的基础上封装了一个这样的类型。这样也有利于把 UDP 通信以不同客户端的模型,进行封装。

  1. ///@brief 此类型负责存放连接过来的客户端信息和数据缓冲区 
  2. class Peer { 
  3. public: 
  4.     int buf_size_;      ///< 缓冲区长度 
  5.     char* const buffer_;///< 缓冲区起始地址 
  6.     int produced_pos_;  ///< 填入了数据的长度 
  7.     int consumed_pos_;  ///< 消耗了数据的长度 
  8.  
  9.     int GetFd() const; 
  10.     void SetFd(int fd);    /// 获得本地地址 
  11.     const struct sockaddr_in& GetLocalAddr() const; 
  12.     void SetLocalAddr(const struct sockaddr_in& localAddr);    /// 获得远程地址 
  13.  
  14.     const struct sockaddr_in& GetRemoteAddr() const; 
  15.     void SetRemoteAddr(const struct sockaddr_in& remoteAddr); 
  16.  
  17. private: 
  18.     int fd_;                            ///< 收发数据用的fd 
  19.     struct sockaddr_in remote_addr_;    ///< 对端地址 
  20.     struct sockaddr_in local_addr_;     ///< 本端地址 
  21. }; 

(编辑:上饶站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读