Skip to content

Latest commit

 

History

History
39 lines (26 loc) · 4.45 KB

README.md

File metadata and controls

39 lines (26 loc) · 4.45 KB

How To Write A IOEvent Library

什么是IO事件驱动库

如果读者有做过服务端开发的话,应该都听说过 LibeventLibuv 等IO事件驱动函数库,著名的缓存服务器 memcached 就是使用了 Libevent 作为IO事件驱动的,而 Libuv 则是由 node.js 的作者编写的一个IO事件驱动函数库,作为 node.js 异步的核心模块。

那么什么是 IO事件驱动库 呢?我们都知道在对一个客户端连接(socket)进行处理时,如果连接没有数据可读写,那么进程(线程)将会被阻塞。所以当进程被一个客户端连接阻塞的时候,其他的客户端连接也不能被处理,从而导致服务器停止服务。

那么有什么解决方案呢?解决方案有很多种:

  • 当一个客户端连接到来时,创建一个新的进程(线程)去处理这个客户端连接。这种方案的好处是,处理逻辑比较简单,对编程技巧的要求比较低。缺点就是,每个连接需要创建一个进程,如果客户端请求量较大时,系统中就会存在大量的进程。进程占用较多的系统资源,并且进程的调度也会消耗一定的CPU时间,早期的Apache就是使用这种方式来处理请求的。

  • 使用多 路复用IO 处理客户端请求。多路复用IO 通俗的解释是:把客户端连接放置在一个池(pool) 中,然后对这个池进行监听,如果池中某些客户端连接可读写,那么就通知进程哪些客户端连接能够处理。这种方案的好处是,不用为每个客户端连接创建一个进程来处理,节省了系统资源,另外也没有进程间的切换。缺点是对编程技巧有较高的要求,因为使用者必须记录连接处理的状态。

  • 使用 异步IO 处理客户端请求。这种方案跟多路复用IO差不多,但需要使用者有更高的编程技巧,因为异步IO使用的是事件回调的方式通知进程,用过 node.js 的同学可能体验过 回调地狱 的痛苦。

那么哪种方案比较好呢?当然如果只考虑性能的话,使用 多路复用IO异步IO 是不错的选择,但需要使用者有较高的编程水平,而且容易出错。所以,如果使用者的编程水平一般,那么使用多进程(线程)的方式也是一个折中的方案,当然我们在使用多进程(线程)时,可以使用进程池(线程池)来解决进程(线程)无限增加。

说了那么多,还没有解释什么是 IO事件驱动库 啊,其实 IO事件驱动库 的对 多路复用IO异步IO 进行一种封装的库。因为不同的操作系统有着不同的 多路复用IO异步IO 接口(譬如Linux系统的epoll、FreeBSD系统的kqueue和Windows系统的IOCP),所以为了程序的可移植性,对用户必须提供一种统一的接口,但对不同的操作系统使用不同处理。除了对外提供统一的接口外,还可以增加一些辅助的功能来降低编程的复杂性。

怎样编写一个IO事件驱动库

我觉得想编写一个功能库,最好的方式是参考开源的项目。譬如编写IO事件驱动库可以参考Libevent或者Libuv,但这两个都比较庞大,分析起来比较困难。所以可以参考一下 redis 源码的 ae.c 这个文件,这是一个比较独立的,而且较为简单的IO事件驱动库。当然我们这篇文章说的是怎么编写一个IO事件驱动库,所以肯定不会叫你参考一下开源项目就完事了。Let's Go!

本文中的IO事件驱动库使用C语言实现,所以如果没有C语言基础的同学,可以先了解下C语言相关的知识。

定义库接口

函数库就是为用户提供服务,所以要编写一个函数库时,首先要规划好我们这个库提供什么服务,应该有哪些接口等等。编写一个最简单的IO事件驱动库,必须提供一下几个接口:

  • 添加客户端连接(socket)到库中进行监听
  • 从库中删除客户端连接(socket)
  • 开始监听库中的客户端连接(socket)是否可处理

除了上面三个接口外,你也可以增加自己的功能,当为了简单起见,我们先来实现这三个接口。

当然,一个项目首先要有个炫酷的名字,我们先为这个库起个名,由于我个人比较喜欢漫威,所以就叫 Marvel 吧,希望漫威不要告我。

所以我们定义这三个接口为:

1. marvel_add()
2. marvel_del()
3. marvel_dispatch()