MMORPG大型游戏设计与开发(part4 of net)

上一节简单的介绍了服务器消息处理的流程,想必大家对这方面有了初步的认识,接下来我们需要知道和掌握的便是其中一些重要的方法,进一步深入熟悉整个构架。

  1、FD_*系列宏函数

  FD_ZERO(fd_set *fdset) 将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。

  FD_SET(fd_set *fdset) 用于在文件描述符集合中增加一个新的文件描述符。FD_CLR(fd_set *fdset) 用于在文件描述符集合中删除一个文件描述符。

  FD_ISSET(int fd,fd_set *fdset) 用于测试指定的文件描述符是否在该集合中。

  2、Socket操作类

/**
 * PAP Engine ( -- )
 * $Id socket.h
 * @link -- for the canonical source repository
 * @copyright Copyright (c) 2013-2013 viticm( [email protected] )
 * @license
 * @user viticm<[email protected]>
 * @date 2013-12-31 17:34:43
 * @uses server net model socket class
 */
#ifndef PAP_SERVER_COMMON_NET_SOCKET_H_
#define PAP_SERVER_COMMON_NET_SOCKET_H_

#include "common/net/socket/base.h"

namespace pap_server_common_net {

class Socket {

 public:
   Socket(uint16_t port, uint32_t backlog = 5);
   ~Socket();

 public:
   void close();
   bool accept(pap_common_net::socket::Base* socket);
   uint32_t getlinger() const;
   bool setlinger(uint32_t lingertime);
   bool is_nonblocking() const;
   bool set_nonblocking(bool on = true);
   uint32_t getreceive_buffersize() const;
   bool setreceive_buffersize(uint32_t size);
   uint32_t getsend_buffersize() const;
   bool setsend_buffersize(uint32_t size);
   int32_t getid() const;

 protected:
   pap_common_net::socket::Base* socket_;

};

}; //namespace pap_server_common_net

#endif //PAP_SERVER_COMMON_NET_SOCKET_H_

serversocket

  这是服务器的socket操作类,构造函数(Socket(uint16_t port, uint32_t backlog = 5))需要提供一个监听端口。

  void close(); //关闭套接字。

  bool accept(pap_common_net::socket::Base* socket); //接受sokcet连接

  uint32_t getlinger() const; //获取延时

  bool setlinger(uint32_t lingertime); //设置延时

  bool is_nonblocking() const; //是否为non-blocking模式

  bool set_nonblocking(bool on = true); //设置non-blocking

  uint32_t getreceive_buffersize() const; //获取接收的buffer大小

  bool setreceive_buffersize(uint32_t size); //设置接收的buffer大小

  uint32_t getsend_buffersize() const; //获取发送的buffer大小

  bool setsend_buffersize(uint32_t size); //设置发送buffer大小

  int32_t getid() const; //获取套接字ID

  其中大家可能不了解的是non-blocking的IO模式,有一篇详细的文中供大家参考:IO模式

  3、套接字输入流和输入流操作类

#ifndef PAP_COMMON_NET_SOCKET_INPUTSTREAM_H_
#define PAP_COMMON_NET_SOCKET_INPUTSTREAM_H_

#include "common/net/config.h"
#include "common/lib/vnet/vnet.hpp"
#include "common/net/socket/base.h"
#include "common/net/packet/base.h"

namespace pap_common_net {

namespace socket {

class InputStream {

 public: //construct and destruct
   InputStream(
       Base* socket,
       uint32_t bufferlength = SOCKETINPUT_BUFFERSIZE_DEFAULT,
       uint32_t bufferlength_max = SOCKETINPUT_DISCONNECT_MAXSIZE);
   virtual ~InputStream();

 public:
   uint32_t read(char* buffer, uint32_t length);
   bool readpacket(packet::Base* packet);
   bool peek(char* buffer, uint32_t length);
   bool skip(uint32_t length);
   uint32_t fill();
   void init();
   bool resize(int32_t size);
   uint32_t reallength();
   bool isempty();
   void cleanup();
   void setkey(unsigned char const* key);
   int32_t get_keylength();
   Base* getsocket();

 private:
   Base* socket_;
   struct packet_t* packet_;
   struct endecode_param_t* endecode_param_;

};

}; //namespace socket

}; //namespace pap_common_net

#endif //PAP_COMMON_NET_SOCKET_INPUTSTREAM_H_

sokcet_inputstream

  套接字的输入流操作类,可以看到构造函数需要提供一个套接字对象初始化。

  functions:

  uint32_t read(char* buffer, uint32_t length); //读取一段数据

  bool readpacket(packet::Base* packet); //读取网络包

  bool peek(char* buffer, uint32_t length); //校验数据内容

  bool skip(uint32_t length); //跳过大小长度的一段数据

  uint32_t fill(); //数据修正

  void init(); //初始化

  bool resize(int32_t size); //重新分配流大小

  uint32_t reallength(); //数据实际长度

  bool isempty(); //数据是否为空

  void cleanup(); //流内部数据清理

  void setkey(unsigned char const* key); //流加密KEY设置,如果设置了这个,则网络数据全部被加密

  int32_t get_keylength(); //获取加密KEY的长度

  variables:

  Base* socket_; //套接字对象

  struct packet_t* packet_; //网络包结构

  struct endecode_param_t* endecode_param_; //加密数据结构

#ifndef PAP_COMMON_NET_SOCKET_OUTPUTSTREAM_H_
#define PAP_COMMON_NET_SOCKET_OUTPUTSTREAM_H_

#include "common/net/config.h"
#include "common/lib/vnet/vnet.hpp"
#include "common/net/socket/base.h"
#include "common/net/packet/base.h"

namespace pap_common_net {

namespace socket {

class OutputStream {

 public:
   OutputStream(
     socket::Base* socket,
       uint32_t bufferlength = SOCKETOUTPUT_BUFFERSIZE_DEFAULT,
       uint32_t bufferlength_max = SOCKETOUTPUT_DISCONNECT_MAXSIZE);
   ~OutputStream();

 public:
   uint32_t write(const char* buffer, uint32_t length);
   bool writepacket(const packet::Base* packet);
   uint32_t flush();
   void init();
   bool resize(int32_t size);
   uint32_t reallength();
   bool isempty();
   void cleanup();
   void setkey(unsigned char const* key);
   int32_t get_keylength();
   void getbuffer(char* buffer, uint32_t length);
   Base* getsocket();

 private:
   Base* socket_;
   struct packet_t* packet_;
   struct endecode_param_t* endecode_param_;

};

}; //namespace socket

}; //namespace pap_common_net

#endif //PAP_COMMON_NET_SOCKET_OUTPUTSTREAM_H_

socket_outputstream

  其方法和参数其实与输入流大同小异,我在这里就不一一列举了。

  4、网络包操作类和网络包管理器

/**
 * PAP Engine ( -- )
 * $Id packet.h
 * @link -- for the canonical source repository
 * @copyright Copyright (c) 2013-2013 viticm( [email protected] )
 * @license
 * @user viticm<[email protected]>
 * @date 2014-1-2 11:36:54
 * @uses server and client net pakcet class
 */
#ifndef PAP_COMMON_NET_PACKET_BASE_H_
#define PAP_COMMON_NET_PACKET_BASE_H_

#include "common/net/config.h"
#include "common/net/socket/inputstream.h"
#include "common/net/socket/outputstream.h"

#define GET_PACKETINDEX(a) ((a) >> 24)
#define SET_PACKETINDEX(a,index) ((a) = (((a) & 0xffffff) + ((index) << 24)))
#define GET_PACKETLENGTH(a) ((a) & 0xffffff)
#define SET_PACKETLENGTH(a,length) ((a) = ((a) & 0xff000000) + (length))
//note cn:
//消息头中包括:uint16_t - 2字节;uint32_t - 4字节中高位一个字节为消息序列号,
//其余三个字节为消息长度
//通过GET_PACKETINDEX和GET_PACKETLENGTH宏,
//可以取得UINT数据里面的消息序列号和长度
//通过SET_PACKETINDEX和SET_PACKETLENGTH宏,
//可以设置UINT数据里面的消息序列号和长度
#define PACKET_HEADERSIZE (sizeof(uint16_t) + sizeof(uint32_t))

typedef enum {
  kPacketExecuteStatusError = 0, //表示出现严重错误,当前连接需要被强制断开
  kPacketExecuteStatusBreak, //表示返回后剩下的消息将不在当前处理循环里处理
  kPacketExecuteStatusContinue, //表示继续在当前循环里执行剩下的消息
  kPacketExecuteStatusNotRemove, //表示继续在当前循环里执行剩下的消息,
                                  //但是不回收当前消息
  kPacketExecuteStatusNotRemoveError,
} packet_executestatus_enum;

namespace pap_common_net {

namespace packet {

class Base {

 public:
   Base();
   virtual ~Base();

 public:
   int8_t status_;
   int8_t index_;

 public:
   virtual void cleanup() {};
   virtual bool read(socket::InputStream& inputstream) = 0;
   virtual bool write(socket::OutputStream& outputstream) const = 0;
   virtual uint32_t execute(
       pap_server_common_net::connection::Base* connection) = 0;
   virtual uint16_t getid() const = 0;
   virtual uint32_t getsize() const = 0;
   int8_t getindex() const;
   void setindex(int8_t index);
   uint8_t getstatus() const;
   void setstatus(uint8_t status);

};

}; //namespace packet

}; //namespace pap_common_net

#endif //PAP_COMMON_NET_PACKET_BASE_H_

packet_base

  functions:

  virtual void cleanup() {}; //内部数据清理
  virtual bool read(socket::InputStream& inputstream) = 0; //读取网络包
  virtual bool write(socket::OutputStream& outputstream) const = 0; //写入网络包
  virtual uint32_t execute(
  pap_server_common_net::connection::Base* connection) = 0; //网络包执行
  virtual uint16_t getid() const = 0; //获取包ID
  virtual uint32_t getsize() const = 0; //获取包大小
  int8_t getindex() const; //获取包索引
  void setindex(int8_t index); //设置包索引
  uint8_t getstatus() const; //获取包状态
  void setstatus(uint8_t status); //设置包状态

  variables:

  int8_t status_; //状态

  int8_t index_; //索引

/**
 * PAP Engine ( -- )
 * $Id factorymanager.h
 * @link -- for the canonical source repository
 * @copyright Copyright (c) 2013-2013 viticm( [email protected] )
 * @license
 * @user viticm<[email protected]>
 * @date 2014-1-3 10:11:38
 * @uses server and client net packet factory manager
 */
#ifndef PAP_COMMON_NET_PACKET_FACTORYMANAGER_H_
#define PAP_COMMON_NET_PACKET_FACTORYMANAGER_H_

#include "common/net/config.h"
#include "common/net/packet/factory.h"
#include "common/sys/thread.h"

namespace pap_common_net {

namespace packet {

class FactoryManager {

 public:
   FactoryManager();
   ~FactoryManager();

 public:
   uint32_t* packet_alloccount_;

 public:
   bool init();
   //根据消息类型从内存里分配消息实体数据(允许多线程同时调用)
   Base* createpacket(uint16_t pakcetid);
   //根据消息类型取得对应消息的最大尺寸(允许多线程同时调用)
   uint32_t getpacket_maxsize(uint16_t packetid);
   //删除消息实体(允许多线程同时调用)
   void removepacket(Base* packet);
   void lock();
   void unlock();
   static bool isvalid_packetid(uint16_t id); //packetid is valid

 private:
   Factory** factories_;
   uint16_t size_;
   pap_common_sys::ThreadLock lock_;

 private:
   void addfactory(Factory* factory);
   void addfactories_for_billinglogin();
   void addfactories_for_serverserver();
   void addfactories_for_clientlogin();
   void addfactories_for_loginworld();
   void addfactories_for_serverworld();
   void addfactories_for_clientserver();

};

}; //namespace packet

}; //namespace pap_common_net

extern pap_common_net::packet::FactoryManager* g_packetfactory_manager;

#endif //COMMON_NET_PACKETFACTORY_H_

packet_factorymanager

  网络包工厂管理器,即是把所有包对象加入到一个管理器中,然后有需要的时候再从工厂中取出来使用。

  5、服务器select模式

  上一部分的代码中,大家应该也看到了这个代码。那么为什么要select呢?服务器一次普通阻塞之下,一次只能一对一的问答,如果多个同时访问,就要讲究先来后到了。为了避免这样的问题,解决多用户同时访问不需要等待,则使用了select模式。那么我们来看看这个函数的具体用法与解释,还是引用一个前人已经总结出来的文章:socket的select

  下一部分将用一个服务器的实例来讲诉网络部分一次访问的过程。

时间: 2024-08-07 08:40:31

MMORPG大型游戏设计与开发(part4 of net)的相关文章

MMORPG大型游戏设计与开发(UI SYSTEM SHOW)

接下来一段时间,这些文件可能不再更新,期间我会学习和掌握一些前端知识.虽然我非常欣赏剑侠网络版叁和九阴真经的画面,但是那是一个庞大的游戏引擎,一般人是无法窥伺的,除非你是天才而且要拥有机器毫无中断的毅力.我也很羡慕国外诸如刺客信条系列.古墓丽影系列,因为在画面和操作方面都做到了世界级水平,这也正是我想研究其实现原理的原因之一.况且那些大型游戏,在中端机器上运行的都比较流畅,也是我很想弄明白的.至于虚幻引擎,这是给那些大型公司,诸如腾讯拿去折腾的,个人根本用不起,那么只能选择开源了.UI我选择了C

MMORPG大型游戏设计与开发(part1 of net)

网络模块的设计,是大型多人在线游戏中比较重要的一部分.我之所以将网络模块放到最前面,是因为许许多多的开发者面对这一块的时候充满了疑惑,而且也觉得很神秘和深奥.这些我们面对到的困难,其实是由于我们对这方面了解的不足以及太过陌生. 本次设计中参考到了天龙八部/武侠世界的网络模块的设计,进行了封装调整,而且天龙八部其实也参考了韩国经典网游的设计,所以在稳定这方面还是有一定的积累. 在前面的构架中,大家可以看到一次交互的大致流程图,玩家登陆.创建角色.删除角色.选择角色等都由登陆服务器(login)进行

MMORPG大型游戏设计与开发(规范)

一件事如果没有规范.章法,那么做这件事起来往往会遇到许多难题,特别是在多人协作的时候,没有到规范通常让每个人多多少少都面临着头疼的困难.举个例子,多个人要做一桌美味的饺子,有买材料.做面皮.弄肉(菜)馅等.如果没有分工,做面皮的人也可以去弄肉馅,买材料的人也可以由弄肉馅去,这样一来可能导致这一桌香喷喷的饺子做的极慢,而且很可能导致这几个人各怀意见.所以如果规定了谁去做某件事,则大家都无异议,效率上也就不言而喻了. 1.目录规范 不同的语言有着不同的目录结构设计,但是一定要记住:区分模块功能.目录

MMORPG大型游戏设计与开发(服务器 AI 概述)

游戏世界中我们拥有许多对象,常见的就是角色自身以及怪物和NPC,我们可以见到怪物和NPC拥有许多的行为,比如说怪物常常见到敌对的玩家就会攻击一样,又如一些NPC来游戏世界中走来走去,又有些怪物和NPC有的时候还会发出一些奇怪的谈论.我们都知道物体是死的,没有生命的,程序其实就是一种物体,它本身是不会进行任何的操作的,比如场景中的角色我们不操作则傻站着一样.但是NPC和怪物似乎有自己的判断力,谁该打谁不该打,还会排队行走,这些不是有生命的才能实现的吗?这就是AI,全称为Actificial Int

MMORPG大型游戏设计与开发(服务器 游戏场景 地图和区域)

地图的数据以及区域的信息是场景的重要组成部分,这些数据同时存在客户端和服务器,而且都是由编辑器生成的.那么保存的文件数据结构是怎样的?一张3D的场景地图又是怎样处理这些数据的?同时告诉大家这里同样只是讲的理论与设计,理论和设计往往都很空洞,但是却很灵活,需要靠每个人怎么运用. 一些图片 区域和格子 从上面的截图可以看出游戏场景其实是由格子来区分的,不管是矩形的格子还是其他形状的格子也好,一张地图不可能只有一个点(即多点组成一张地图).在3D场景中似乎格子的位置总伴随着高度信息,所以让人感觉有些迷

MMORPG大型游戏设计与开发(part6 of net)

上一部分,讲述了一个服务器与服务器之间的通信实例,客户端其实原理大同小异.网络部分准备就暂时讲到这里,不过我们不妨再回头过来想想在这过程中有没有优化和改进的地方.这部分讲解的是以网络包代码作分析,实现自动生成其代码的功能. 网络包代码 /** * PAP Engine ( -- ) * $Id connect.h * @link -- for the canonical source repository * @copyright Copyright (c) 2013-2013 viticm(

MMORPG大型游戏设计与开发(客户端架构)

首先为所有等待的朋友说一声歉意,实在让大家等的太久.客户端的设计本来就是一个大的工程,而且工作的关系,也没有太多时间在这方面做研究.不过在私下有空的时间,我还是继续着这方面的研究,很遗憾没有用期望的ogre+cegui最新的版本作为开发,这方面原因是新的版本资料实在不多,对于没有什么经验的人来说实在是一大难事,所以最终选择了同天龙八部/武侠世界版本接近的源码作为开发.好了,废话不多说,今天好介绍的是客户端的基本构架,天龙八部/武侠世界的设计模式. CLIENT 功能实现 本次功能实现了vengi

MMORPG大型游戏设计与开发(服务器 AI 基础接口)

一个模块都往往需要统一的接口支持,特别是对于非常大型的模块,基础结构的统一性非常重要,它往往决定了其扩展对象的通用性.昨天说了AI的基本概述以及组成,作为与场景模块中核心一样重要的地位,基础部分的设计尽量的统一.详细.通用.精简. 游戏截图 基础接口(base) 1.管理方法 初始化(init).释放(release).获得NPC队伍指针(get npc team).内部逻辑循环函数(activate). 2.状态方法(ing) 休闲(idle).闲逛(wander).巡逻(partol).警戒

MMORPG大型游戏设计与开发(服务器 游戏场景 搜索机)

双十一注定是忙碌的日子,所以到了现在我才将今天自己学习的内容拿出来跟大家分享.搜索机是我自己暂时取的名字,其实简单的说就是场景里提供搜索的一个工具,负责场景对象的范围搜索和获取.空洞的理论总是让人一头雾水,如果玩过游戏的朋友不妨想一想查看附近的玩家.选择附近的玩家.点击任务怪物名称就可以自动寻路打怪这些功能就大致有个印象了. 一张截图 搜索机 1.数据 1. 状态 typedef enum operator_status_enum { kOperatorStatusContinue, //扫描继