在服务器中,一般都有一个代表客户端或玩家的类,用来处理一些相关逻辑并保存必要数据。也就是NGServer中的Player类,在网络模型中,一般一个Player对应一次会话(Session),因此在很多服务器模型中,客户端类直接从Session类派生,这样客户端可以直接通过父类Session的接口发送数据,并且通过实现Sessoin的虚函数对数据进行处理。这种模型的好处在于简单,客户端类能够完全控制网络IO,并且对IO事件进行及时地处理。比如连接断开,那么客户端类可以通过实现Session的OnClose()函数完成一些业务逻辑上的处理,比如保存用户数据。而这种编程模型,将客户端和网络会话的耦合性提到了最高:Client is a Session。方便的同时,很大程度上限制了模块的可拓展性,比如客户端的断线重连,由于这种继承关系,导致Session在销毁的同时必然导致Client”逻辑上”的断线,这样玩家重连的时候,数据只能重新加载,建立新的Session和Client。这种情况还会发生在客户端异处登录时,原有客户端被挤下线的同时,逻辑上的数据也丢失了,而新的客户端将重新加载数据。除了断线重连之外,该模型还会造成不必要的编译依赖。因此我们需要将逻辑上的客户端和底层的网络Session解耦。
一种可行的解耦方式是让Session和Player以”包含”的方式并存。即让Session指针或引用作为Player的一个成员。如此数据的发送仍然比较简单,而数据的接收和处理则需要Session通知Player类,这里有两种方式:
让Session也包含一个Player的引用,如此Session在收到数据或连接关闭时也能调用Player接口进行业务逻辑上的处理。
通过std::bind直接让Player的数据解码接口(Decoder)交给Session,Session的数据接收和关闭均通过Decoder交由Player,如此实现更弱的耦合性。
NGServer采用第二种方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void PlayerManager::OnConnect(const std::shared_ptr<Socket>& socket) { uint32_t id = ++_connect_id; // 创建Player和Session 并将Player和Session关联 std::shared_ptr<Session> session = std::make_shared<Session>(socket, id); std::shared_ptr<Player> player = std::make_shared<Player>(session, LoginService::sDefaultSid); std::function<int32_t(const char*, size_t len)> decoder = std::bind(&Player::Decode, player, std::placeholders::_1, std::placeholders::_2); player->SetConnId(id); session->SetDecoder(decoder); AddPlayer(player); session->StartRecv(); }
PlayerManager管理所有Player的连接,它继承于AsyncTcpListener,一个连接监听器,提供OnConnect接口处理客户端连接事件。因此PlayerManager负责Session和Player的创建和管理,并将Session和Player关联。当有新用户连接时,在OnConnect中,创建Player和Session,并相互关联。Session将收到的数据通过Player::Decode解码,该函数返回解包完成后缓冲区的剩余长度,以便Session调整缓冲区。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 // Session收到新的数据 void Session::ReadComplete(const boost::system::error_code& err, size_t bytes_transferred) { if (err || bytes_transferred == 0) { DisConnect(); return; } _recv_total += bytes_transferred; _recv_off += bytes_transferred; // 处理数据 if (ProcessData()) { // 继续接收数据 AsyncReadSome(); } } // 对缓冲区中的数据解包 返回false则断开连接 bool Session::ProcessData() { assert(_decoder); // 将数据交由解码器处理 返回处理之后的缓冲区剩余字节数 返回-1表示服务器主动断线 int32_t remain = _decoder(_recv_buf,_recv_off); // 服务器断开连接 if (remain < 0) { ShutDown(ShutDownType::shutdown_receive); DisConnect(); return false; } // 处理之后的偏移 if (remain > 0 && remain < kBufferSize) { size_t remain_off = _recv_off - remain; _recv_off = (size_t)remain; memcpy(_recv_buf, _recv_buf + remain_off, _recv_off); } else { _recv_off = 0; } return true; }
网络底层部分到此结束,焦点将由Player::Decode转向逻辑层。