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

  同时接收连接:

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

 

posted @ 2014-03-07 16:16  恋月  阅读(1939)  评论(6编辑  收藏  举报