Jabberd2 通信框架(mio)模型(上)

这部分是jabberd的核心通信框架,有点复杂,所述之处如有疏漏错误,还望指正.

从数据结构入手最容易看清代码作者的思路,这里有4个主要的结构体来承担I/O数据,并分别分布在mio.hmio_impl.h中.
顾名思义,impl是I/O的流程实现,各个静态函数都直接在头文件中写完.作者的意图就是让mio.h的数据和函数(其实这两个组合起来就和对象没多大区别了,因此为了方便理解索性就用UML类图表示)暴露给c2s,router和sm各个模块,这些模块对于mio来说不需要关心实现细节.

Image

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
48
49
50
typedef struct mio_fd_st
{
    int fd;
} *mio_fd_t;

typedef struct mio_priv_fd_st
{
    struct mio_fd_st mio_fd;

    mio_type_t type;
    /* app event handler and data */
    mio_handler_t app;
    void *arg;

    MIO_FD_VARS
} *mio_priv_fd_t;

typedef struct mio_st
{
  void (*mio_free)(struct mio_st **m);

  struct mio_fd_st *(*mio_listen)(struct mio_st **m, int port, const char *sourceip,
                      mio_handler_t app, void *arg);

  struct mio_fd_st *(*mio_connect)(struct mio_st **m, int port, const char *hostip,
                       const char *srcip, mio_handler_t app, void *arg);

  struct mio_fd_st *(*mio_register)(struct mio_st **m, int fd,
                       mio_handler_t app, void *arg);

  void (*mio_app)(struct mio_st **m, struct mio_fd_st *fd,
            mio_handler_t app, void *arg);

  void (*mio_close)(struct mio_st **m, struct mio_fd_st *fd);

  void (*mio_write)(struct mio_st **m, struct mio_fd_st *fd);

  void (*mio_read)(struct mio_st **m, struct mio_fd_st *fd);

  void (*mio_run)(struct mio_st **m, int timeout);
} **mio_t;

/** now define our master data type */
typedef struct mio_priv_st
{
    struct mio_st *mio;

    int maxfd;
    MIO_VARS
} *mio_priv_t;

带有_fd_名称的结构体代表每一个I/O实体连接,而mio_st(或者说mio_priv_st, Manager I/O)是管理整个程序的I/O载体,在程序结束前都始终存在,有效.
这两者在代码中的体现即是下面这两个宏, 因此文章也按源码的描述用FD 和 MIO来分别指代二者:

1
2
3
/* lazy factor */
#define MIO(m) ((mio_priv_t) m)
#define FD(m,f) ((mio_priv_fd_t) f)

MIO其实对mio模块的使用者来说是透明的,因为mio.h暴露给外部的实际上就是FD和一个包含若干接口的实例(即MIO),作者将宏发挥到极致,甚至连所有的接口也封装成宏,下面就说说作者如何利用这两大结构体来完成框架的耦合与解耦.

源码的作者利用了两个小技巧来实现与底层I/O模型无关的通用通信框架:

  1. 利用struct内存布局模拟继承关系
    初始化的_mio_new函数可以看出作者抛出的是**mio_st,但是底层的具体实现需要由mio_priv_st完成,因此对于不同的实现需要有不同的变量布局在mio_priv_st中.
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
static mio_t _mio_new(int maxfd)
{
    static struct mio_st mio_impl = {
        _mio_free,
        _mio_listen, _mio_connect, _mio_setup_fd,
        _mio_app,
        _mio_close,
        _mio_write, _mio_read,
        _mio_run
    };
    mio_t m;

    ...

    /* allocate and zero out main memory */
    if((m = calloc(1, sizeof(struct mio_priv_st))) == NULL) {
        fprintf(stderr,"Cannot allocate MIO memory! Exiting.\n");
        exit(EXIT_FAILURE);
    }

    /* set up our internal vars */
    *m = &mio_impl;
    MIO(m)->maxfd = maxfd;

    MIO_INIT_VARS(m);

    return m;
}
  1. 利用宏扩展抽象出通用I/O模型
    通过宏定义在编译时期确定使用的底层I/O复用模型.不得不佩服作者能将各种模型的通用I/O提炼出来,近乎实现了一个小型的libevent库.在mio.c中通过预定义的宏来确定调用哪种实现.对应上一点,MIO中的MIO_INIT_VARS宏就必须扩展为对应I/O的变量,epoll中就需要包含epoll的文件描述符和epoll_event集合, 而FD中的MIO_FD_VARS宏则扩展为在epoll的事件类型掩码.然后在_mio_new的最后也要求扩展I/O初始化的宏MIO_INIT_VARS,以完成初始化动作,例如epoll_create.这些宏扩展都包含在对应的mio_***.h中.
1
2
3
4
5
6
7
#define MIO_FD_VARS \
    uint32_t events;

#define MIO_VARS \
    int defer_free;      \
    int epoll_fd;        \
    struct epoll_event res_event[32];

除此之外还要求宏实现对FD的释放和设读写位的一些其它操作.

到目前为止觉得mio模块还是很清晰明朗的,对底层和外部的解耦做得很好.但是个人感觉实际操作起来却和另一个XMPP协议相关模块(sx)的交互很繁琐(网上评论说jabberd2的扩展性差是不是就在这里?).

mio的数据结构就是这么多,其运行机制下篇再继续post.