此文归纳整理和libhv源码分析相关的文章,献给感兴趣钻研libhv源码的同学。
如有其他同学有写过不错的libhv源码分析文章,欢迎联系我加上。

libhv是一个比libevent、libev、libuv更易用的跨平台c/c++国产网络库,用来开发TCP/UDP/SSL/HTTP/WebSocket/MQTT客户端/服务端。

项目地址:https://github.com/ithewei/libhv.git
码云镜像:https://gitee.com/libhv/libhv.git
QQ技术交流群:739352073

事件循环和IO多路复用机制介绍

https://hewei.blog.csdn.net/article/details/113724474

事件循环是libevent、libev、libuv、libhv这类网络库里最核心的概念,即在事件循环里处理IO读写事件、定时器事件、自定义事件等各种事件;

IO多路复用即在一个IO线程监听多个fd,如最早期的select、后来的polllinux的epollwindows的iocpbsd的kqueuesolaris的port等,都属于IO多路复用机制。非阻塞NIO搭配IO多路复用机制就是高并发的钥匙

libhv下的event模块正是封装了多种平台的IO多路复用机制,提供了统一的事件接口,是libhv的核心模块。

事件的定义以及数据结构介绍

libhv中的事件包括IO事件timer定时器事件idle空闲事件自定义事件
所有事件都是继承自公共的基类hevent_t,便于放入事件队列中统一调度。

事件结构体hevent_t定义在事件循环模块对外头文件 hloop.h 里:

struct hevent_s {
    hloop_t*            loop;           // 事件所属的事件循环
    hevent_type_e       event_type;     // 事件类型
    uint64_t            event_id;       // 事件ID
    hevent_cb           cb;             // 事件回调
    void*               userdata;       // 事件用户数据
    void*               privdata;       // 事件私有数据
    struct hevent_s*    pending_next;   // 下一个事件(用于实现事件队列)
    int                 priority;       // 事件优先级
};

事件循环结构体hloop_t定义在内部 hevent.h 头文件里:

struct hloop_s {
    uint32_t    flags;			// 事件循环flags
    hloop_status_e status; 		// 状态:running、stop、pause
    uint64_t    start_ms;       // 开始时间(现实时间realtime)
    uint64_t    start_hrtime;   // 开始时间(线性时间,不受系统时间调整影响)
    uint64_t    end_hrtime;   	// 结束时间
    uint64_t    cur_hrtime;		// 当前时间
    uint64_t    loop_cnt;		// 循环计数
    long        pid;			// 进程ID
    long        tid;			// 线程ID
    void*       userdata;		// 用户数据
//private:
    // events
    uint32_t                    intern_nevents;	// 内部激活事件数
    uint32_t                    nactives;	// 激活事件数
    uint32_t                    npendings;	// 行将发生事件数
    // pendings: with priority as array.index
    hevent_t*                   pendings[HEVENT_PRIORITY_SIZE]; // 事件优先级队列
    // idles
    struct list_head            idles;	// 空闲事件链表
    uint32_t                    nidles;	// 空闲事件数
    // timers
    struct heap                 timers;  // 定时器事件堆
    uint32_t                    ntimers; // 定时器事件数
    // ios: with fd as array.index
    struct io_array             ios; 	// IO事件数组
    uint32_t                    nios;	// IO事件数
    // one loop per thread, so one readbuf per loop is OK.
    hbuf_t                      readbuf;	// 读缓存
    void*                       iowatcher;	// IO事件监视器
    // custom_events
    int                         eventfds[2]; 	// 事件FD,用于唤醒事件循环
    event_queue                 custom_events;  // 自定义事件队列
    hmutex_t                    custom_events_mutex; // 互斥锁,用于自定义事件队列的线程安全性
};

事件的优先级机制

https://blog.csdn.net/qu1993/article/details/111194770

IO事件

定时器事件

https://blog.csdn.net/qu1993/article/details/111197841

空闲事件

https://blog.csdn.net/qu1993/article/details/111275642

自定义事件

https://blog.csdn.net/qu1993/article/details/111297130

心跳和保活机制

https://blog.csdn.net/qu1993/article/details/111246515

TCP如何处理粘包与分包

libhv提供了设置拆包规则接口,c接口见hio_set_unpack,c++接口见SocketChannel::setUnpack,支持固定包长、分隔符、头部长度字段三种常见的拆包方式,调用该接口设置拆包规则后,内部会根据拆包规则处理粘包与分包,保证回调上来的是完整的一包数据,大大节省了上层处理粘包与分包的成本,该接口具体定义如下:

typedef enum {
    UNPACK_BY_FIXED_LENGTH  = 1,    // 根据固定长度拆包
    UNPACK_BY_DELIMITER     = 2,    // 根据分隔符拆包,如常见的“\r\n”
    UNPACK_BY_LENGTH_FIELD  = 3,    // 根据头部长度字段拆包
} unpack_mode_e;

#define DEFAULT_PACKAGE_MAX_LENGTH  (1 << 21)   // 2M

// UNPACK_BY_DELIMITER
#define PACKAGE_MAX_DELIMITER_BYTES 8

// UNPACK_BY_LENGTH_FIELD
typedef enum {
    ENCODE_BY_VARINT        = 1,                // varint编码
    ENCODE_BY_LITTEL_ENDIAN = LITTLE_ENDIAN,    // 小端编码
    ENCODE_BY_BIG_ENDIAN    = BIG_ENDIAN,       // 大端编码
} unpack_coding_e;

typedef struct unpack_setting_s {
    unpack_mode_e   mode; // 拆包模式
    unsigned int    package_max_length; // 最大包长度限制
    // UNPACK_BY_FIXED_LENGTH
    unsigned int    fixed_length; // 固定包长度
    // UNPACK_BY_DELIMITER
    unsigned char   delimiter[PACKAGE_MAX_DELIMITER_BYTES]; // 分隔符
    unsigned short  delimiter_bytes; // 分隔符长度
    // UNPACK_BY_LENGTH_FIELD
    unsigned short  body_offset; // body偏移量(即头部长度)real_body_offset = body_offset + varint_bytes - length_field_bytes
    unsigned short  length_field_offset; // 头部长度字段偏移量
    unsigned short  length_field_bytes; // 头部长度字段所占字节数
    unpack_coding_e length_field_coding; // 头部长度字段编码方式,支持varint、大小端三种编码方式,通常使用大端字节序(即网络字节序)
#ifdef __cplusplus
    unpack_setting_s() {
        // Recommended setting:
        // head = flags:1byte + length:4bytes = 5bytes
        mode = UNPACK_BY_LENGTH_FIELD;
        package_max_length = DEFAULT_PACKAGE_MAX_LENGTH;
        fixed_length = 0;
        delimiter_bytes = 0;
        body_offset = 5;
        length_field_offset = 1;
        length_field_bytes = 4;
        length_field_coding = ENCODE_BY_BIG_ENDIAN;
    }
#endif
} unpack_setting_t;

HV_EXPORT void hio_set_unpack(hio_t* io, unpack_setting_t* setting);

ftp为例(分隔符方式)可以这样设置:

unpack_setting_t ftp_unpack_setting;
memset(&ftp_unpack_setting, 0, sizeof(unpack_setting_t));
ftp_unpack_setting.package_max_length = DEFAULT_PACKAGE_MAX_LENGTH;
ftp_unpack_setting.mode = UNPACK_BY_DELIMITER;
ftp_unpack_setting.delimiter[0] = '\r';
ftp_unpack_setting.delimiter[1] = '\n';
ftp_unpack_setting.delimiter_bytes = 2;

mqtt为例(头部长度字段方式)可以这样设置:

unpack_setting_t mqtt_unpack_setting = {
    .mode = UNPACK_BY_LENGTH_FIELD,
    .package_max_length = DEFAULT_PACKAGE_MAX_LENGTH,
    .body_offset = 2,
    .length_field_offset = 1,
    .length_field_bytes = 1,
    .length_field_coding = ENCODE_BY_VARINT,
};

具体实现代码在 event/unpack.c 中,在内部readbuf的基础上直接原地拆包与组包,基本做到零拷贝,比抛给上层处理更高效,感兴趣的可以研究一下。

具体示例可参考 examples/jsonrpcexamples/protorpc

回显、聊天、代理三种经典服务实现详解

https://hewei.blog.csdn.net/article/details/115332093

回显、聊天、代理是每个后端工程师必须掌握的基础demo,开发服务端程序无非是基于这三种经典服务做业务开发;

多线程/多进程服务端编程

https://hewei.blog.csdn.net/article/details/120366024

为了充分利用现代计算机的多核处理器,掌握多线程服务端编程也就必不可少了。

libhvexamples/multi-thread 目录下给出了几种常见的多线程/多进程模式的具体写法:

  • multi-acceptor-processes:多accept进程模式
  • multi-acceptor-threads:多accept线程模式
  • one-acceptor-multi-workers:一个accept线程+多worker线程

网络编程十宗罪

https://hewei.blog.csdn.net/article/details/121313149

网络编程过程中十条最容易踩的坑,我称之为网络编程十宗罪,名字取的比较骇人,也是为了警醒大家在网络编程过程中切勿触犯以上条例,做到按例排查,写出更为健壮的网络程序。

致谢

last

最后祝愿大家都能通过libhv掌握网络编程,欢迎大家加入libhv的开源贡献中来,完善libhv的网络生态。想参与贡献的可参考 libhv后续规划。另外开源创作实属不易,如果对你有帮助,别忘了在 githubstar下哦。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐