前言
IO多路复用的基本用法优势在于:可以等待多个描述符就绪。在网络服务器编程中,IO多路复用目前是最常用的手段。
1 select模型
1.1 基本语法
select是一种典型的bit map管理模式,利用bit位来判断当前集合中IO是否就绪。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);
参数描述
1 timeout三种情况:
- 设置为空指针NULL:永远等待下去,直到有I/O准备是才返回;
- 设置时间设置为0:立即返回;
- 设置为具体时间:超时返回;(这种情况一种用法:用来作为一个粗略的定时器)
2 中间三个参数比较好理解:
readset:读fd集合
writeset:写fd集合
exceptset:异常fd集合
3 第一个参数maxfd
最大描述符加1 ,这个主要是用来进行描述符集合的遍历;
另外就是进行集合操作函数,用于集合的增删改查。
void FD_SET(fd_set *fdset);//监听集合某一位bit置一
void FD_ZERO(int fd, fd_set *fdset);//清空集合,所有bit位置零
void FD_CLR(int fd, fd_set *fdset);//集合某一位bit置零
int FD_ISSET(int fd, fd_set *fdset);//检查集合某一位bit是否置一
1.2 实战用法
fd_set rfds, rset, wfds, wset;
FD_SET(listenfd, &rfds);
FD_ZERO(&rfds);
FD_ZERO(&wfds);
int max_fd = listenfd;
while (1) {
rset = rfds;
wset = wfds;
int nready = select(max_fd+1, &rset, &wset, NULL, NULL);//select阻塞等待
if (FD_ISSET(listenfd, &rset)) { //服务器监听服务器socket事件
struct sockaddr_in client;
socklen_t len = sizeof(client);
//接收客户连接
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
//将客户连接加入读集合
FD_SET(connfd, &rfds);
if (connfd > max_fd) max_fd = connfd;
if (--nready == 0) continue;
}
int i = 0;
for (i = listenfd+1;i <= max_fd;i ++) {
//客户端读集合触发,读取去客户数据
if (FD_ISSET(i, &rset)) { //
n = recv(i, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
//客户fd加入写集合
FD_SET(i, &wfds);
} else if (n == 0) {
FD_CLR(i, &rfds);//当客户端断开需要移除集合
close(i);
}
if (--nready == 0) break;
} else if (FD_ISSET(i, &wset)) {//触发写集合向客户写数据
send(i, buff, n, 0);
FD_SET(i, &rfds);
}
}
}
2 poll模型
poll模型和select模型实现原理基本一致,效率上也相差不大。
1.1 基本语法
#include <sys/poll.h>
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);
第一个参数fds为一个pollfd结构数组,用来保存文件描述符
第二个参数nfds为pollfd结构体数组+1
第三个参数timeout为poll等待时间
1.2 实战用法
#define POLL_SIZE 1024
//初始化poll集合
struct pollfd fds[POLL_SIZE] = {0};
fds[0].fd = listenfd;
fds[0].events = POLLIN;
int max_fd = listenfd;
int i = 0;
for (i = 1;i < POLL_SIZE;i ++) {
fds[i].fd = -1;//fd为-1表示为空闲
}
while (1) {
int nready = poll(fds, max_fd+1, -1);//poll
if (fds[0].revents & POLLIN) {//接收客户端连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
//加入poll队列
fds[connfd].fd = connfd;
fds[connfd].events = POLLIN;
if (connfd > max_fd) max_fd = connfd;
if (--nready == 0) continue;
}
//int i = 0;
for (i = listenfd+1;i <= max_fd;i ++) {
if (fds[i].revents & POLLIN) {//客户端事件检查
//接收回写
n = recv(i, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
send(i, buff, n, 0);
} else if (n == 0) { //客户端退出
fds[i].fd = -1;
close(i);
}
if (--nready == 0) break;
}
}
}
3 epoll模型
epoll模型可以说是神一样的存在,从效率和速度都是远超过其他io复用模型,目前大部分的linux网络组件都是基于epoll模型构建,使得服务突破百万连接的瓶颈。
1.1 基本语法
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
1 epoll_create
size没有实际意义大于零即可,主要是兼容旧版本
建立一个epoll对象(在epoll文件系统中给这个句柄分配资源);
2 epoll_ctl
实现套接字的增删改
EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD
3 epoll_wait
收集发生事件的连接。
1.2 实战用法
#define EPOLL_SIZE 1024
//创建epoll模型
int epfd = epoll_create(1); //int size
//初始化epoll事件
struct epoll_event events[EPOLL_SIZE] = {0};
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = listenfd;
//服务器socket加入epoll
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
while (1) {
//epoll等待io事件触发
int nready = epoll_wait(epfd, events, EPOLL_SIZE, 50);//size对性能影响不大,主要是影响单次通知的数量
if (nready == -1) {
continue;
}
int i = 0;
for (i = 0;i < nready;i ++) {
//遍历所有io事件
int clientfd = events[i].data.fd;
if (clientfd == listenfd) {//服务器接收客户端
struct sockaddr_in client;
socklen_t len = sizeof(client);
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
printf("accept\n");
ev.events = EPOLLIN;
ev.data.fd = connfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
} else if (events[i].events & EPOLLIN) {//客户端读事件
//接收和回写
printf("recv\n");
n = recv(clientfd, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
send(clientfd, buff, n, 0);
} else if (n == 0) { //关闭客户端连接
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
close(clientfd);
}
}
}
}