• 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_
    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( 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_
    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( 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_
    packet_factorymanager

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

      5、服务器select模式

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

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

    作者:viticm 出处: http://www.cnblogs.com/lianyue/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。
  • 相关阅读:
    外观模式
    装饰器模式
    eclipse在运行main方法时在console里面报内存溢出的错误解决办法
    windows7安装node
    Eclipse-低版本离线集成svn步骤
    IDEA-JetBrains产品永久破解
    Java对字符串使用MD5进行加密(亲测有效)
    windows下的java项目打jar分别编写在windows与linux下运行的脚本( 本人亲测可用!)
    在linux中运行main方法所在的java类(亲测有效!!!)
    linux常用命令
  • 原文地址:https://www.cnblogs.com/lianyue/p/3584488.html
Copyright © 2020-2023  润新知