• 网络库的设计与实现


    前言

    距离第一次发布iocpframework已经三年多了,这期间这款基于windows的网络库经过多次的修改,已经和第一版有非常大的区别了,但是整体思想架构并没有改变,这得益于当初对下载调度器的认识--模块应该是可替换的,这也是后话了。

    在阅读本文的时候,希望你能有以下的基础:

    • 熟悉C++11 ,auto、decltype、lambda、type_traits、move等
    • 熟悉Windows的网络编程、IOCP模型(IOCP仅仅是个异步队列 blocking queue,当然不止一个队列)

    如果你符合以下描述,那么恭喜你,本文就是为你而写的:

    • 追求自由,热爱自由
    • 对网络proactor IO模型细枝末节摸棱两可
    • 对IOCP机制感到非常模糊及细节难以把握
    • 对C++充满热爱与激情

    如果您具有以下特征之一,那么本文可能会给你带来不适,请谅解:

    • 披着C++外衣的Cer
    • 反模板联盟
    • 反感自造轮子

    iocpframework的github地址:https://github.com/chenyu2202863/iocpframework/

    换句话说,我所走的弯路,你一样得走(哈哈,被坑了),我所路过的捷径,也希望会成为你的捷径。祝你好运。需要C++11编译期支持,至少VS2012 CTP补丁才能编译

    一个迷你的服务器

    image

    设置好监听端口,启动。等待accept事件,针对session投递读请求,等待读事件,返回写数据。麻雀虽小五脏俱全,再来看看其中涉及到的设计思考及实现细节。

    网络库应该提供的功能:

    1. 3个半事件,详情请参考陈硕的《网络编程本质论
    2. 可扩展的内存分配方式
    3. 对网络错误处理
    4. 异步、同步IO

    网络库避免限制的决策:

    1. 对session集中式管理
    2. 对接收缓冲区与发送缓冲区的强制
    3. 需要做到协议无关

    正如《UNIX编程艺术》所说:提供机制,而不是策略

    与第一版的变化:

    1. 性能大幅度提高
    2. 接口更加灵活
    3. 增加socket pool
    4. 增加zero copy机制
    5. 增加timewheel
    6. 改用WinVista以后API,不支持WinXP

    库结构

    image

    设计实现剖析

    一、service部分

    dispatcher

    类似于asio的io_service,同时提供线程池,负责数据的调度。

    image

    这里,采用的是GetQueuedCompletionStatusEx,支持同时多个回调事件的处理,而且发生的错误信息是由OVERLAPPED_ENTRY里的Internal字段提供的,这个是文档里没有告诉我们的。这样来处理,可以不用那么麻烦的判断GetQueuedCompletionStatusEx及GetLastError的返回值的组合。

    async_result

    这是提供给socket、file、timer与dispacther交互的粘合曾,负责生命期的处理

    image

    定制了std::unique_ptr的释放方法,完成我们的RAII方式来管理资源。

    async_callback_base_t是一个基类,抽象出调用接口及释放资源的接口

    image

    dispatcher通过call调用,把指针转换为async_callback_base_ptr,然后通过基类指针p调用invoke,invoke只关心错误信息与事件回调时的数据大小(就是read、write所实际产生的大小),再来看看实际干活的async_callback

    image

    image

    持有外部传入的Handler和一个Allcoator,Handler就是用户所关心的callback,Allocator负责对象的分配释放.每当向系统投递一个异步请求的时候,就会make_async_callback,把handler、allocator传入win_async_callback_t里持有。这里就是简单的动多态与静多态的一个结合案例。就这样,通过async_callback_base就把dispatcher与file、socket、timer分离。所以说,计算机界的问题都可以引入一个间接层来解决。

    再来看看zero-copy机制的设计实现

    首先,基于C++11提供的move语义和fsocket支持非连续性内存的写入(WSASend支持多个WSABUF)。其次,需要在编译期推导传入需要写入数据的类型及个数,这会影响到WSABUF数组的个数。

    image

    把callback handler及parameters打包到param_t对象中,param_t根据参数,编译期推导出不同数据类型的个数

    image

    buffers接口就在完成填充std::array<WSABUF, details::args_count_t<tuple_t>::value>

    在这里,为了能支持用户对zero_copy的扩展,可以去特化image ,默认支持了原生类型、用户POD类型及vector、string,比如string的特化版本如下

    image

    arg_size是这个类型对需要占用几个WSABUF,因为string需要一个长度和内容,所以这里arg_size = 2。

    二、network部分

    两个问题

    1. server分几层设计实现,每一层干什么事情?
    2. 对外暴露的server接口提供哪几个?

    我的看法是,server分两层。第一层负责socket的连接断开、socket的生命期管理(当然不需要进入一个容器,采用锁机制,这是很笨拙的方式)、数据收发方式。第二层负责buffer缓冲区处理,及协议相关处理,同时需要负责心跳的活。来看开始给出的迷你服务器原型

    一个服务器,首先应该考虑的是错误信息的处理。在这里,error_handler_t就是错误信息的处理回调函数,在这里还需要考虑接收到数据包后,返回给上层,所以也需要一个数据包处理回调。

    register_callback提供注册socket连接与断开的接口,有些场景是不关心这两个事件的,所以这两个接口需要单独设置,我们的server现在就差start(port)了。

    在这一层中,提供了心跳管理,接收缓冲区(发送缓冲区不需要管理,因为我们的库提供了zero copy机制及用户保证buffer生命期两种方式)。

    image

    对于timewheel,大家可以去参考陈硕这篇文章,非常棒!《用timewheel踢掉空闲》。

    multi_buffer_decode_t是一个解码器,针对recv_buffer_里的数据进行decode,然后返回到用户设置的callback里。

    再看看提供session管理及连接、断开处理的server层。在这一层需要有两个抽象,一个session、一个server。session负责socket的持有、收发动作、错误处理。server负责session的连接及创建。具体请查看源码,这里介绍下socket的连接处理及socket pool

    image

    利用了WSAEventSelect来检测是否需要投递socket,通过socket pool来获取可用的socket,因为socket的创建是很占系统资源的。关于object pool的文章,前一篇讲过。

    image

    使用socket pool需要注意的是,必须使用DiseconnectEx,而这个系统的回调不是立马返回,而是需要在能正确断开连接后才返回,微软给出的时间默认是4分钟,也就是TIME_WAIT的时间。所以需要复用的socket需要在session析构的时候才放回到pool里

    image

    在这里,使用了一个技巧,当session需要关闭的时候,取消掉已经投递的请求

    image

    session的生命期管理采用的是陈硕提供的方式,shared_ptr、enable_shared_from_this。自己管理自己,这样就避免了一个容器,避免了锁竞争。如下

    image

    更多的细节请到源码里找吧。

    与ASIO对比

      asio iocpframework
    跨平台 目前仅支持windows
    性能 更好,需要数据支撑
    IO模型 Proactor Proactor
    支持zero copy 支持
    支持socket pool 支持
    内存分配方式 链式hook 接口参数
    其他方面 完胜 不是同一个目标定位,而且也是完败

    未来的演化方向:

    如果C++标准库加入网络库,估计这个库也就完蛋了.接下来会在performanceexample est进行更多的支持.接口也许会变

    打个小广告,如果有兴趣,请加入

    C++11的群: 165666547

    iocpframework的群:54801033

  • 相关阅读:
    redis单机安装以及简单redis集群搭建
    Linux中JDK安装教程
    微信公众号开发(一)
    easyui多图片上传+预览切换+支持IE8
    mybatis动态sql之foreach标签
    java List递归排序,传统方式和java8 Stream优化递归,无序的列表按照父级关系进行排序(两种排序类型)
    java钉钉通讯录同步
    java使用poi生成导出Excel(新)
    java 图片转base64字符串、base64字符串转图片
    Spring事务mysql不回滚:mysql引擎修改
  • 原文地址:https://www.cnblogs.com/yu-yu/p/3344199.html
Copyright © 2020-2023  润新知