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

原文作者:http://www.cnblogs.com/lianyue

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

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( viticm@126.com )
 * @license
 * @user viticm<viticm@126.com>
 * @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_

这是服务器的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_

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

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_

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

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

/**
 * PAP Engine ( -- )
 * $Id packet.h
 * @link -- for the canonical source repository
 * @copyright Copyright (c) 2013-2013 viticm( viticm@126.com )
 * @license
 * @user viticm<viticm@126.com>
 * @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_

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( viticm@126.com )
 * @license
 * @user viticm<viticm@126.com>
 * @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_

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

5、服务器select模式

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,694评论 0 3
  • 主要参考: Android深入浅出之Binder机制 http://www.cnblogs.com/innost/...
    Kevin_Junbaozi阅读 999评论 0 7
  • 大纲 一.Socket简介 二.BSD Socket编程准备 1.地址 2.端口 3.网络字节序 4.半相关与全相...
    VD2012阅读 2,321评论 0 5
  • 今天我就来给大家说一下什么叫正义吧! 今天早上我看到一个瓶子,一直没人捡,我也不想去捡,因为我觉得这...
    根永阅读 247评论 0 0
  • 图爱阅读 158评论 0 0