从零开始写一个简单好用的游戏服务器引擎[3] - 网络

写网络模块的时候比较了很多方案,最后选择了libevent.主要出于以下几点:
1.比较轻量.而且源代码也比较容易看懂,遇上了什么不理解的做法,看看他的代码能很快明白原理.我觉得这点十分重要.作为服务端我觉得在代码层面上尽量不要用自己不能完整驾驭的东西.原先也想用boost::asio,但这个就属于我感觉自己短时间不能完整驾驭.万一有什么点上没有理解创作者的意图用错了,又没办法从源码里理解,风险就大了.
2.和其它模块耦合度低.这一点也是我最终抛弃boost::asio的原因之一.因为asio里关联使用了太多boost的其它工具.如果用asio要把很多boost的东西引用进来.增加了出错的风险和维护的难度.

关于libevent,官方文档还是非常齐全的.

我把它封装成了我想要的模式.
主要有以下几个部分:
网络基础模块:SocketBase(定义接口.负责libevent自己的callback处理再通过接口交给具体子类处理具体网络收发逻辑)
网络线程:SocketThread(管理一个独立的线程,开启一个新的libevent核心对象,用来调度一个网络模块)
服务端模块:Server(定义服务端接口,实现创建一个服务端需要的逻辑)
连接者模块:Connector(继承于SocketBase,用于和服务端模块配合.在服务端模块Accept了一个新的连接的时候创建一个连接者实例用来管理连接者相关的信息以便于和相应的连接者通讯.)
客户端模块:Client(继承于SocketBase,因为有时服务端也需要作为客户端主动去Connect一个目标,所以要有客户端模块,典型的如网关服务器,Listen玩家接入的同时,也要Connect到不同的逻辑服务器上去作转发.定义客户端接口,实现创建一个客户端需要的逻辑.其中客户端分为独立线程客户端和共享线程客户端.区别是独立线程客户端是每一个客户端实例自己独占一个线程,用于单个客户端吞吐量大,但不会频繁大量创建的场景.共享线程客户端是所有客户端实例都共用一个线程,通过一个管理者线程管理.适用于客户端实例需要频繁创建销毁或大量创建的场景)

主要就是这样.所以定义好之后结构大致是这样的:

// 网络基础模块
class ISocketBase
{
    ...
}

// 网络线程
class ISocketThread
{
    ...
}

// 服务端模块
class CSocketServer : public ISocketThread
{
    ...
}

// 连接者模块
class CSocketConnector : public ISocketBase
{
    ...
}

// 客户端模块
// * 独立客户端模块
class CSocketClient : public ISocketBase,public ISocketThread
{
    ...
}

// * 共享客户端模块
class CSocketMutiClient : public ISocketBase
{
    ...
}

// * 共享客户端模块管理者
class CSocketMutiClientMgr : public ISocketThread
{
    ...
}

嗯,大致结构和继承关系就是这样了.

其中每一个ISocketThread都会管理一个event_base指针,作为一个事件循环去处理一个独立的网络事件(比如一个服务端监听和收发,一个/一组客户端连接和收发).这样在高并发的情况下可以最大程度的压榨cpu的资源,使其能够承载更多的并发.

接下来说说收发缓冲区.

如果没有收发缓冲区,如果突然有比较大的流量,超出了系统的缓冲区的话就会导致收取的包丢失或者发送的包发送失败.所以必须要通过一个缓冲区把要收发的数据存起来,慢慢收慢慢发.遇上错误还可以再次拼装再次重发.
所以收缓冲区做成了一个可以无限扩展的buffer模式:

class CBuffer
{
...
private:
        int m_nBufferLenth; // 记录buffer长度
        int m_nBufferMaxLength;// 记录当前buffer最大长度
        char* m_pBuf;// 记录buffer数据
}

m_pBuf 是一片堆内存.使用时会先预分配一块内存.网络收到的数据先写在这里.当内存即将超出最大长度时则重新new一片更大的块将原来的内存复制过去.

这样在使用的时候就可以根据前后端协议.
每次先暂时的取一个协议头.如果取到了协议头再根据协议头里的数据大小取下来数据.确保取完了再把这段内存删掉.确保逻辑万无一失.

发缓冲区则做成了一个内存块的队列.

class CSendBuffer
{
...
private:
        CSendBufferBlock* m_pBlockSending;
        queue<CSendBufferBlock*> m_queueSnd;
        int m_nQueueLenMax;
        CPool<CSendBufferBlock, 64> m_poolBlock;
}

每一个CSendBufferBlock都代表了单次要发送的内存块.
有要发送的数据则先放到这个队列中排队.
发送的时候就从队列m_queueSnd依次发送出去.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值