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( 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。
下一部分将用一个服务器的实例来讲诉网络部分一次访问的过程。