副标题:高性能网络服务器的demo 级别实现
- fork
- select
- epoll
- IO事件库,以 libevent 举例。
c 语言实现高性能网络服务器
以 fork 方式
(父进程)每收到一个连接,就 fork 一个子进程。
1 | // 在 tcp_server.c 的基础上进行改写 |
问题:
- 资源被长期占用
- 分配子进程花费时间长
select 实现
- 属于异步 IO,即以事件触发的机制对 IO 操作进行处理。
- 系统开销小,不必创建进程或者线程,也不必维护。
- 默认只有 1024 个连接,epoll 无此限制
步骤:
遍历文件描述符集中所有描述符,找到有变化的描述符。
- os 底层发现socket 有数据过来时,select 自己还需要做判断,确定到底有没有数据过来。
监听 socket 跟数据处理 socket 要区别对待。
socket 必须设置为非阻塞方式工作,但 select 是阻塞的。
重要 API:
api | FD_ZERO | FD_SET | FD_ISSET | FD_CLEAR |
---|---|---|---|---|
文件描述符集中的所有描述符全部清掉 | 将某描述符设置到集内 | 判断某描述符是否在集内 | ||
api | flag fcntl(fd, F_SETFL/F_GETFL,flag) | events select(nfds, readfds, writefds, exceptfds, timeout) | ||
文件描述符控制函数,用于控制阻塞/ 非阻塞 | ||||
tips:
- events——select 找出哪些事件被触发了
- nfds——最大的文件描述符+1(约定俗成,意义不明)
- readfds、writefds、exceptfds——读、写、异常的文件描述符集
- timeout——超时时间,select 以阻塞方式执行,所以达到超时时间后,select 结束掉,进入下一个循环。一般 500 毫秒。
select demo:
1 | // 在 tcp_server.c 的基础上进行改写 |
epoll 实现
特点:
- 没有文件描述符的限制,数量远远超过 1024。
- 时间复杂度 O(1),不受文件描述符数量而影响效率。
- epoll 经过内核级优化。
- 触发模式:
- 水平触发Level trigger,默认,如果数据没有处理完,下次过来时会告知。 select 也属于水平触发的情况。一般用于比较重要的场合。
- 边缘触发edge trigger,效率更高,但开发难度高。仅触发一次,不会管你有无处理完所有数据。
重要 api:
api | int epoll_create() | int epoll_ctl(epfd,op,fd,struct epoll_event *event) | int epoll_wait(epfd,events, maxevents,timeout) | |
---|---|---|---|---|
创建一个 epoll 的文件描述符 | 向 epoll 添加事件fd | 等待事件 | ||
tips:
1. epfd:由 epoll_create 创建出的 fd
2. events:发生改变的事件数组
3. maxevents:每次返回时最大返回的事件数量
4. timeout:超时时间,没有事件时,会等待到timeout 后停止 epoll,然后再来一轮 ,500 毫秒即可
epoll 的事件:
- EPOLLET:可以用来设置成边缘触发模式 。
- EPOLLIN:数据来的事件。
- EPOLLOUT:数据往外写的事件。
- EPOLLPRI:出现问题、中断的事件。
- EPOLLERR:读写出现问题的事件。
- EPOLLHUP:挂起的事件。
epoll_ctl 相关操作:
- EPOLL_CTL_ADD:将文件描述符 add 到 epoll 里
- EPOLL_CTL_MOD: 修改 epoll 里的文件描述符
- EPOLL_CTL_DEL: 删除 epoll 里的文件描述符
epoll 重要结构体
1 | typedef union epoll_data{ |
epoll demo:
1 | // 在 tcp_server.c 的基础上进行改写 |
epoll + fork
实现demo:
1 | // 在执行 epoll 前(即上文 for 死循环前)执行: |
libevent
重要函数:
event_base_new:创建实例,并初始化
event_base_dispatch: 事件触发器
event_new: 创建 event 队列,还有event_add、event_del、event_free 等都属于较底层 api。
evconnlistener_new_bind: 综合了socket监听和触发的功能,属于较高级的 api
1 | typedef void (*evconnlistener_cb) (struct evconnlistener *, |
tips:
- event_base :通过event_base_new生成。
- evconnlistener_cb:是一种回调函数,当有连接进来时,会触发这个回调函数
- *ptr 是一个指针,指向 evconnlistener_cb函数的参数
- flags:事件的标识,libevent 提供有重复触发。。。各种标识。
- backlog:socket 相关,相当于排队请求的。。。(不明)
- sockaddr:服务请求的地址,包括 ip 和 port
- socklen:socket 的长度
libevent demo(使用高级 api):
1 | // 初始化 event_base |
以上涉及了accept_conn_cb
,accept_error_cb
这些回调函数,需要另外实现。
接收数据事件的回调函数accept_conn_cb:
1 | // 举例:接收数据事件的回调函数: |
以上又涉及了echo_read_cb
,echo_event_cb
等回调函数,此处略去不表。