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


    上一部分将服务器的具体代码的实现介绍给了大家,想必大家也了解到了服务器处理一次消息的复杂度。如果大家能够将各个过程掌握清楚,就会发觉其实整个逻辑与交互过程是比较清晰的。那么服务器与服务器之间的通讯,其实也就是相当于客户端与服务器的通讯又是如何实现的呢?本文将用一个实例来将这个过程展示给大家。

     CODE

    复制代码
    bool ServerManager::connectserver() {
      uint8_t step = 0;
      __ENTER_FUNCTION
        bool result = false;
        pap_server_common_net::packets::serverserver::Connect* connectpacket = NULL;
        pap_common_net::socket::Base* billingsocket = NULL;
        const char *kServerIp = "192.168.200.100";
        const uint16_t kServerPort = 12680;
        billingsocket = billing_serverconnection_.getsocket();
        try {
          result = billingsocket->create();
          if (!result) {
            step = 1;
            Assert(false);
          }
          result = billingsocket->connect(
              kServerIp,
              kServerPort);
          if (!result) {
            step = 2;
            printf("exception 2");
            goto EXCEPTION;
            Assert(false);
          }
          result = billingsocket->set_nonblocking();
          if (!result) {
            step = 3;
            printf("exception 3");
            Assert(false);
          }
    
          result = billingsocket->setlinger(0);
          if (!result) {
            step = 4;
            printf("exception 4");
            Assert(false);
          }
          g_log->fast_save_log(kBillingLogFile,
                               "ServerManager::connectserver()"
                               " ip:%s, port: %d, success",
                               kServerIp,
                               kServerPort);
        }
        catch(...) {
          step = 5;
          Assert(false);
        }
        result = addconnection(
            (pap_server_common_net::connection::Base*)&billing_serverconnection_);
        if (!result) {
          step = 6;
          Assert(false);
        }
        connectpacket = new pap_server_common_net::packets::serverserver::Connect();
        connectpacket->set_serverid(9999);
        connectpacket->set_worldid(0);
        connectpacket->set_zoneid(0);
        result = billing_serverconnection_.sendpacket(connectpacket);
        SAFE_DELETE(connectpacket);
        if (!result) {
          step = 7;
          Assert(false);
        }
        g_log->fast_save_log(kBillingLogFile, 
                             "ServerManager::connectserver() is success!");
        return true;
    EXCEPTION:
        g_log->fast_save_log(
            kBillingLogFile, 
            "ServerManager::connectserver() have error, ip: %s, port: %d, step: %d",
            kServerIp,
            kServerPort,
            step);
        billing_serverconnection_.cleanup();
        return false;
      __LEAVE_FUNCTION
        return false;
    }
    复制代码

      我在这里简单介绍下以上代码:billing_serverconnection_是服务器的主连接,我们以这个连接连接到目标IP为192.168.200.100,port为12680的服务器。连接成功后,我们会发送一个连接信息的包到目标服务器上。可以看出来这个包里设置了三个参数,serverid(服务器id)、worldid(世界id)、zoneid(区域id)。至于这个方法需要放置的位置,必须置于服务器初始化完成后,因为我们要让他创建一个可用的主套接字连接。

      接着我们看看在服务器,执行完这个方法后又进行了哪些处理。

      

    复制代码
    bool ServerManager::processoutput() {
      __ENTER_FUNCTION
        if (SOCKET_INVALID == maxfd_&& SOCKET_INVALID == minfd_)
          return false;
        uint16_t i;
        uint16_t connectioncount = billingconnection::Manager::getcount();
        for (i = 0; i < connectioncount; ++i) {
          if (ID_INVALID == connectionids_[i]) continue;
          billingconnection::Server* serverconnection = NULL;
          //serverconnection = g_connectionpool->get(connectionids_[i]);
          serverconnection = &billing_serverconnection_;
          Assert(serverconnection);
          int32_t socketid = serverconnection->getsocket()->getid();
          if (socketid_ == socketid) continue;
          if (FD_ISSET(socketid, &writefds_[kSelectUse])) {
            if (serverconnection->getsocket()->iserror()) {
              removeconnection(serverconnection);
            }
            else {
              try {
                if (!serverconnection->processoutput()) 
                  removeconnection(serverconnection);
              }
              catch(...) {
                removeconnection(serverconnection);
              }
            }
          }
        }
        return true;
      __LEAVE_FUNCTION
        return false;
    }
    复制代码
    复制代码
    bool ServerManager::processcommand() {
      __ENTER_FUNCTION
        if (SOCKET_INVALID == maxfd_&& SOCKET_INVALID == minfd_)
          return false;
        uint16_t i;
        uint16_t connectioncount = billingconnection::Manager::getcount();
        for (i = 0; i < connectioncount; ++i) {
          if (ID_INVALID == connectionids_[i]) continue;
          billingconnection::Server* serverconnection = NULL;
          //serverconnection = g_connectionpool->get(connectionids_[i]);
          serverconnection = &billing_serverconnection_;
          Assert(serverconnection);
          int32_t socketid = serverconnection->getsocket()->getid();
          if (socketid_ == socketid) continue;
          if (serverconnection->getsocket()->iserror()) {
            removeconnection(serverconnection);
          }
          else { //connection is ok
            try {
              if (!serverconnection->processcommand(false)) 
                removeconnection(serverconnection);
            }
            catch(...) {
              removeconnection(serverconnection);
            }
          }
        }
        return true;
      __LEAVE_FUNCTION
        return false;
    }
    复制代码

      特别注意代码中的注释部分,因为验证服务器是等待连接的,没有这种主动连接别的服务器的需要,所以整个功能设计上没有这部分,这个连接的方法也是我临时添加上的。既然是验证服务器的连接发送出去的包,那么对应处理输出流和命令处理自然也应该是这个连接,否则我们的包就发送不出去了。

      下面我们就来看看服务器端对这个包的处理代码(handler):

    复制代码
    #include "server/common/net/packets/serverserver/connect.h"
    #include "server/billing/connection/server.h"
    #include "server/common/base/log.h"
    
    namespace pap_server_common_net {
    
    namespace packets {
    
    namespace serverserver { 
    
    uint32_t ConnectHandler::execute(Connect* packet, 
                                     connection::Base* connection) {
      __ENTER_FUNCTION
        g_log->fast_save_log(kBillingLogFile, 
                             "ConnectHandler::execute(...) serverid: %d ...success", 
                             packet->get_serverid());
        return kPacketExecuteStatusContinue;
      __LEAVE_FUNCTION
        return kPacketExecuteStatusError;
    }
    
    } //namespace serverserver
    
    } //namespace packets
    
    } //namespace pap_server_common_net
    复制代码

      包收到后,我们需要进行一段逻辑处理,这个包是最简单的连接,只是对包里面这个服务器id进行了输出处理。

    RESULT

       服务器启动:

      这个例子中,我以两个服务器作为客户端,分别在windows和linux平台下。

    LINUX

      配置:

      连接失败:

    连接成功:

      服务器接收成功:

    WINDOWS

      配置:

      连接失败:

      连接成功:

      服务器接收成功:

    SERVER

      同时接收连接:

      下一部分将对网络部分的设计进行总结,同时这些文章也会不断更新。

  • 相关阅读:
    学机器学习,不会数据分析怎么行——数据可视化分析(matplotlib)
    关于 tensorflow-gpu 中 CUDA 和 CuDNN 版本适配问题
    人工智能学习资料
    JAVA Socket通信 打造属于自己的网盘
    在 Windows 10 中使用 OpenAI Spinning Up
    【LeetCode】回文串专题
    【LeetCode】45.跳跃游戏2
    【LeetCode】23.最大子序和
    【LeetCode】3. 无重复字符的最长子串(典型滑动窗口)
    【LeetCode】202.快乐数
  • 原文地址:https://www.cnblogs.com/liuzhi/p/4084571.html
Copyright © 2020-2023  润新知