文章目录
不同于传统的“一个进程处理一个客户端请求”的方式,IO复用可以让一个进程处理多个客户端的请求,更加节省资源。
一个简单地服务端可能是这样的:
调用socket()创建套接字
bind()绑定地址和端口
listen()监听套接字
while(1){
调用accept()连接客户端
fork()创建进程B来处理客户端的需求/使用新的线程来执行任务
}
释放资源
当使用上面这种方式来处理客户端的请求时,如果客户端数量特别多,服务端就会创建很多进程或线程来执行任务。这种一个进程/线程对应一个客户端的方式其实是挺浪费资源的,如果让一个进程或线程就能够处理多个客户端的连接,那么就能够减少很多不必要的资源浪费。IO就可以解决这个问题。
函数API
int select (int maxfdp, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数:
maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,因为文件描述符是从0开始的
readfds检测可读的文件描述符集合
writefds检测可写的问价描述符集合
exceptfds检测异常条件出现的文件描述符
timeout超时时间
阻塞:设置NULL,会一直阻塞,直到有描述符准备好IO
立即返回:必须设置timeval结构体,但其中的值为0
等待一段时间:在规定时间内如果发生IO活动就马上返回,如果一直没有就等超时后再返回。
返回值:
返回发生所检测操作的fd总数,错误时返回SOCKET_ERROR。
发生io活动的fd存储在相应的参数中(会删除所有传入的fd,只留下发生io活动的)
fd_set
为long类型数组,存储文件描述符。可以用下面几个宏来设置。
FD_ZERO(fd_set *fdset) 将指定的文件描述符集清空
FD_SET(fd_set *fdset) 用于在文件描述符集合中增加一个新的文件描述符。
FD_CLR(fd_set *fdset) 用于在文件描述符集合中删除一个文件描述符。
FD_ISSET(int fd,fd_set *fdset) 用于测试指定的文件描述符是否在该集合中。
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds,时间单位比其他的要更小
};
使用示例:
poll原理跟select基本是一样的,但是fd数量没有了限制。
函数API
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
参数:
pollfd数组传入要检测的IO活动,和返回发生的IO活动
nfds表示文件描述符的最大值加1.
timeout表示超时的毫秒数,负数表示无限阻塞,0表示马上返回。
使用示例:
函数API
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
int epoll_create(int size);
参数:
size以前是用来作fd数目参考,linux2.6.8之后已经不用了
返回值:
如果成功返回一个非负的文件描述符,失败返回-1.
//epoll描述符的控制接口
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
epfd,epoll实例的文件描述符。
op是请求的操作:
EPOLL_CTL_ADD
EPOLL_CTL_MOD
EPOLL_CTL_DEL
fd是op操作对应的文件描述符
event标识要检测的io操作,event中的events按位存储发生的事件信息:
EPOLLIN:监测读操作。
EPOLLOUT:写操作。
EPOLLRDHUP:流socket对端关闭连接或关闭写连接。
EPOLLPRI:紧急数据可读
EPOLLERR:关联的文件描述符发生错误
EPOLLHUP:发生挂断。
EPOLLET:设置该fd边缘触发模式
EPOLLONESHOT:用来保证同一SOCKET只能被一个线程处理,不会跨越多个线程。
返回值:
成功返回0,错误返回-1并设置errno。
//来获取发生的IO事件
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
参数:
epfd,epoll实例的文件描述符。
events,返回的io操作
maxevents,要监控的最大文件描述符
timeout表示超时的毫秒数,负数表示无限阻塞,0表示马上返回。
返回值:
返回发生IO事件的fd个数,没有发生返回0,发生错误返回-1.
epoll的两种工作模式 水平触发(LT,Level Trigger):默认的模式,如果发生的IO操作没有被处理,下次仍然会继续提醒。并且同时支持 Blocking 和 No-Blocking。 边缘触发(ET,Edge Trigger):高速模式,发生的IO操作只提醒一次,如果没有被处理,下次就不再提醒了。效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
使用示例:
简单地说来,select和poll适合连接数量小、活跃数量多、实时性要求高的情况。而epoll适合客户端的连接数量很大,活跃数量小的情况。