如何糅合各种网络协议?

做服务器的开发工作也有一年了,得益于最近的业务工作没有那么的繁忙,让我有了些许的空闲时间来好好思考这一年时间的点点滴滴

虽然项目千奇百怪各不相同,但其实大部分的时候我们都是在做重复的事情,比如:网络,数据库。

刚好最近在研究 ARK 开源项目,从中的学习到了一些优秀的设计,今天在此记录一下 ARK 中的网络设计以及我自己的一些思考,以免我这榆木脑袋过几天就忘记了

核心问题?

  • 网络协议多而风格迥异,如何做到 高性能 + 业务层无感知(只需要配置使用那种协议?
  • 有关高性能的话题就涉及到一些底层的 epollmulti-reactor 之类的话题,我自己也还在持续学习中,大家可以关注我的一些关于 io 的博客,在这儿就不赘述了,本文更多的侧重于如何设计好网络这部分,让各种网络协议可以通用的切换。

ARK 中的 Net 插件

  • 首先是 ARK 中的三大基石,项目中的一些设计都是基于他们的

net message

  • 所有的网络消息都会被解析成为如下的格式
  • 对于流式的消息,先取包头进行解析,再解析body
  • udp,ws 是 package 形式的消息,收到一个包就直接进行解析即可,解析成为如下的格式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct AFNetMsg
    {
    // head
    uint16 protocolID
    uint32 length
    uint64 actor_id_{0}; // Actor id
    uint32 src_bus_{0}; // Source bus id
    uint32 dst_bus_{0}; // Destination bus id
    // body
    char* data // 具体的业务数据
    }

net event

  • 描述网络事件,新连接建立 & 连接断开
  • 这个目前正在考虑需不需要和 net message 进行合并

session

  • 一个新连接建立会对应一个 session
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class AFSession
    {
    uint32_t head_len_{0}; // 定义消息头的长度,收到的消息按照这个长度进行解析
    conv_id_t session_id_{0}; // 每一个连接会对应一个 sessionID,连接进来就创建一个 id
    AFBuffer buffer_; // 对应流式数据(tcp),收到消息之后直接存入这个可自动伸缩的 buffer 中

    // TODO: merge msg_queue and event_queue together?
    // cuz recv_msg is a event too.
    AFLockFreeQueue<AFNetMsg*> msg_queue_;
    AFLockFreeQueue<AFNetEvent*> event_queue_;
    const SessionPTR session_; // 具体的连接,源码中这个是一个范型

    // 描述状态
    volatile bool connected_{false};
    volatile bool need_remove_{false};
    }

如何适配各种协议,来进行统一的管理呢?

  • 服务器这件事情其实说起来也很简单,就是与连接(笼统的说法,udp是没有连接的概念)进行发包 & 收包
  • 这儿就可以进行一次抽象,分成两层:
    • 上层:每一个连接对应一个 session,我维护好所有的 session 即可,定期的遍历 session,处理每个session中的 msg & event
      • 这儿我有一个想法就是不遍历所有的session,有消息的才进行遍历(类似于epoll的做法)
    • 下层:负责收包并且解析成上面所说的那种格式(AFNetMsg,并且可以进行发送数据
  • 每个session中对应了一个 const SessionPTR session_ ,把这个部分抽象成一个 连接接口 即可, 类似于这样
1
2
3
4
5
6
class ConnInterface
{
virtual Send() = 0;
virtual Recive() = 0;
virtual Close() = 0;
}
  • 这样我可以各种协议类型的 server 只需要:

    • 建立socket + bind port + listen
    • accept 一个 ConnInterface + 创建session,抛到上层即可
  • 这样我各种业务的处理函数可以在上层注册(协议号+处理函数)

  • 这样就实现了业务网络的分离,并且将各种协议糅合在一起,很棒