这部分是jabberd的核心通信框架,有点复杂,所述之处如有疏漏错误,还望指正.
从数据结构入手最容易看清代码作者的思路,这里有4个主要的结构体来承担I/O数据,并分别分布在mio.h
和mio_impl.h
中.
顾名思义,impl是I/O的流程实现,各个静态函数都直接在头文件中写完.作者的意图就是让mio.h
的数据和函数(其实这两个组合起来就和对象没多大区别了,因此为了方便理解索性就用UML类图表示)暴露给c2s,router和sm各个模块,这些模块对于mio来说不需要关心实现细节.
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; 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;
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
| #define MIO(m) ((mio_priv_t) m) #define FD(m,f) ((mio_priv_fd_t) f)
|
MIO其实对mio模块的使用者来说是透明的,因为mio.h
暴露给外部的实际上就是FD和一个包含若干接口的实例(即MIO),作者将宏发挥到极致,甚至连所有的接口也封装成宏,下面就说说作者如何利用这两大结构体来完成框架的耦合与解耦.
源码的作者利用了两个小技巧来实现与底层I/O模型无关的通用通信框架:
- 利用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;
...
if((m = calloc(1, sizeof(struct mio_priv_st))) == NULL) { fprintf(stderr,"Cannot allocate MIO memory! Exiting.\n"); exit(EXIT_FAILURE); }
*m = &mio_impl; MIO(m)->maxfd = maxfd;
MIO_INIT_VARS(m);
return m; }
|
- 利用宏扩展抽象出通用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.