IO模型
普通模型
-
阻塞IO
- 在进行recvfrom系统调用、等待数据准备的过程中,进程将会堵塞,直到数据准备好后函数返回
- 此模型下,进程将休眠等待
-
非阻塞IO
- 在进行recvfrom系统调用时,如果数据没有准备好,函数将直接返回EWOULDBLOCK错误
- 此模型下,需要定期调用recvfrom函数
复用模型
-
select函数
- 传入想要监听的文件描述符位数组,通过select系统调用得到可读取的位数组,并逐一检查,然后通过recvfrom函数读取
- 缺点
- 每次调用select后,都需要循环检查包含所有文件描述符的位数组,找到值为1的进行处理
- 每次调用select时,都要通过“构造或拷贝”传入监视对象信息,这一项是最大的性能弱点
- 原型
#include <sys/select.h> #include <sys/time.h> int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);- maxfd 监视的文件描述符fd的数量
- 一般取 最大的fd值再加1
- readset 注册所有关注“是否有待读取数据”的fd
- 函数将修改此项,更新为“存在”待读取数据的fd
- writeset 注册所有关注“是否可传输无阻塞数据”的fd
- 函数将修改此项,更新为“可传输”无阻塞数据的fd
- exceptset 用于注册“是否发生异常”的fd
- 函数将修改此项,更新为“发生异常”的fd
- timeout 为防止select函数陷入无限阻塞,需传递超时信息
- 函数将修改此项,更新为返回时剩余的时间
- 结构体定义如下
struct timeval { long tv_sec; //seconds long tv_usec; //microseconds }
- maxfd 监视的文件描述符fd的数量
- fd_set位数组
- fd_set是一个位数组,每一位代表一个文件描述符fd
- 调用select前,若某个位为1,代表此处fd是监视对象
- 调用select后,若某个位为1,代表此处fd可读取/可传输/有异常
- 操作位数组的宏
FD_ZERO(fd_set* fdset); //全部置0 FD_SET(int fd, fd_set* fdset); //注册某个fd,置1 FD_CLR(int fd, fd_set* fdset); //清除某个fd的监视,置0 FD_ISSET(int fd, fd_set* fdset); //判断某个fd值是否为1,用于验证调用结果
- fd_set是一个位数组,每一位代表一个文件描述符fd
- 例子select.c
#include <stdio.h> #include <unistd.h> #include <sys/select.h> #include <sys/time.h> #define BUF_SIZE 50 int main(int argc, char* argv[]) { fd_set reads, temps; FD_ZERO(&reads); FD_SET(0, &reads); //0是标准输入 struct timeval timeout; char buf[BUF_SIZE]; int result, str_len; while (1) { temps = reads; //由于传入后会被修改,难以下次循环复用,所以每次都要传副本 timeout.tv_sec = 5; //传入后也会被修改,所以每次都要重设 timeout.tv_usec = 0; result = select(1, &temps, 0, 0, &timeout); if (result == -1) { puts("select() error"); break; } else if (result == 0) { puts("timeout"); } else { if (FD_ISSET(0, &temps)) { str_len = read(0, buf, BUF_SIZE); buf[str_len] = 0; printf("message from console: %s", buf); } } } }
-
epoll函数
-
仅Linux可用,内核版本2.5.44以上(含)才支持,在Window上要用另一种实现IOCP
-
只要创建出保存文件描述符的空间,然后请求操作系统注册文件描述符,即可通过不断调用epoll_wait函数得到发生变化的文件描述符
-
原型
#include <sys/epoll.h>int epoll_create(int size);- 用于创建文件描述符保存空间
- size —— 向操作系统提供的空间建议大小(在2.6.8内核之后此参数将失去作用)
- 返回epoll空间的文件描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);- epfd —— 上面创建的文件描述符
- op —— 操作行为
- EPOLL_CTL_ADD —— 添加
- EPOLL_CTL_DEL —— 移除
- EPOLL_CTL_MOD —— 更改
- fd —— 关注的文件描述符
- event —— 是一个epoll_event结构体
- 内部存放具体的事件events以及关注的文件描述符fd
- 结构体定义
struct epoll_event { __uint32_t events; epoll_data_t data; } typedef union epoll_data { void* ptr; int fd; __uint32_t u32; __uint64_t u64; } - events可选值
- EPOLLIN —— 需要读取数据
- EPOLLOUT —— 需要立即发送数据
- EPOLLPRI —— 收到OBB紧急数据
- EPOLLRDHUP —— 连接断开或半关闭
- EPOLLERR —— 发生错误
- EPOLLET —— 设置以边缘触发方式得到通知
- EPOLLONESHOT —— 设置发生一次事件后,相应fd不再被通知,需要EPOLL_CTL_MOD后才能再次使用
- 以上值可通过“位或"叠加后传递
- 使用例子
int epfd = epoll_create(50); //创建 struct epoll_event event; event.events = EPOLLIN event.data.fd = sockfd; epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event) //注册
int epoll_wait(int epfd, struct epoll_event, int maxevents, int timeout)- epfd —— 上面创建的文件描述符
- events —— 是一个epoll_event结构体指针
- 代表连续的多个结构体,需要自行计算并分配空间,详情看例子
- 函数会把发生事件的文件描述符存入这里面
- maxevents —— 第二个参数可以保存的最大事件数
- 可使用定义的常量EPOLL_SIZE
- timeout —— 超时时间,以毫秒为单位
- 若传递-1,则一直等待到有事件发生
- 返回发生事件的文件描述符数量
- 使用例子
int event_cnt; struct epoll_event* ep_events; ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE); event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); //查询
-