一, IO多路复用(多个io复用一个进程)
1.所用函数
select, poll, epoll
2.流程
1.创建文件描述符集合
2.添加文件描述符到集合中
3.通知内核开始检测
4.根据反馈做对应的io操作
select函数
select函数是一个在多种操作系统中用于监视多个文件描述符(file descriptors)的状态变化的系统调用。它允许程序监视一个或多个文件描述符,以查看它们是否处于某种特定的状态(比如可读、可写、有异常等)。select函数通常用于实现非阻塞的I/O操作,以及多路复用I/O(multiplexing I/O),即单个进程能够处理来自多个网络连接的输入/输出。
select函数的基本用法包括:
指定要监视的文件描述符集合(通常是三个集合:读集合、写集合、异常集合)。
等待直到一个或多个文件描述符就绪(或超时)。
返回就绪的文件描述符集合,并可能返回超时信息。
select函数的原型(在POSIX标准中)大致如下:
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds:监视的文件描述符集合中最大文件描述符加1的值。
readfds:指向可读文件描述符集合的指针,如果不关心可读文件描述符,则传入NULL。
writefds:指向可写文件描述符集合的指针,如果不关心可写文件描述符,则传入NULL。
exceptfds:指向异常文件描述符集合的指针,如果不关心异常文件描述符,则传入NULL。
timeout:指定等待的最大时间,如果为NULL,则无限期等待;如果时间间隔内没有文件描述符就绪,则select返回0。
select函数返回就绪的文件描述符数量,或在出错时返回-1。
需要注意的是,select函数在处理大量文件描述符时效率较低,因为它需要遍历所有的文件描述符集合来检查哪些文件描述符已经就绪。因此,对于需要处理大量并发连接的应用,通常会使用poll或epoll等更高效的机制。
用法
select read.c
#include "head.h"int main(int argc, const char *argv[])
{int maxfd = 0;char buff[1024] = {0};mkfifo("./myfifo", 0664);int fifofd = open("./myfifo", O_RDONLY);if (-1 == fifofd){perror("fail open fifo");return -1;}//创建文件描述符集合fd_set rdfds;fd_set tmpfds;//清空集合FD_ZERO(&rdfds);//添加关注的文件描述符到集合中FD_SET(0, &rdfds);maxfd = 0 > maxfd ? 0 : maxfd;FD_SET(fifofd, &rdfds);maxfd = fifofd > maxfd ? fifofd : maxfd;while (1){tmpfds = rdfds;//开始监测集合中的ioint cnt = select(maxfd+1, &tmpfds, NULL, NULL, NULL);if (cnt < 0){perror("fail select");return -1;}//根据返回的结果区分处理不同的ioif (FD_ISSET(0,&tmpfds)){memset(buff, 0, sizeof(buff));fgets(buff, sizeof(buff), stdin);printf("STDIN : %s\n", buff);}if (FD_ISSET(fifofd, &tmpfds)){memset(buff, 0, sizeof(buff));read(fifofd, buff, sizeof(buff));printf("FIFO : %s\n", buff);}}close(fifofd);return 0;
}
select写服务器
#include "head.h"int init_tcp_ser(const char *ip ,unsigned short port)
{ int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){perror("fail socket");return -1;}struct sockaddr_in ser;ser.sin_family = AF_INET;ser.sin_port = htons(port);ser.sin_addr.s_addr = inet_addr(ip);int ret = bind(sockfd, (struct sockaddr *)&ser, sizeof(ser));if (-1 == ret){perror("fail bind");return -1;}ret = listen(sockfd, 128);if (-1 == ret){perror("fail listen");return -1;}return sockfd;
}int main(int argc, const char *argv[])
{pid_t pid = 0;int connfd = 0;pthread_t tid;int maxfd = 0;char buff[1024] = {0};int sockfd = init_tcp_ser("192.168.1.106", 50000);if (-1 == sockfd){return -1;}fd_set rdfds;fd_set tmpfds;FD_ZERO(&rdfds);FD_SET(sockfd, &rdfds);maxfd = sockfd > maxfd ? sockfd : maxfd;while (1){tmpfds = rdfds;int cnt = select(maxfd+1, &tmpfds, NULL, NULL, NULL);if (cnt < 0){perror("fail select");return -1;}if (FD_ISSET(sockfd, &tmpfds)){int connfd = accept(sockfd, NULL, NULL);if (connfd < 0){perror("fail accept");continue;}FD_SET(connfd, &rdfds);maxfd = connfd > maxfd ? connfd : maxfd;}for (int i = sockfd+1; i < maxfd; i++){if (FD_ISSET(i, &tmpfds)){memset(buff, 0, sizeof(buff));ssize_t size = recv(i, buff, sizeof(buff), 0);if (size < 0){perror("fail recv");FD_CLR(i, &rdfds);close(i);continue;}else if (0 == size){FD_CLR(i, &rdfds);close(i);continue; }printf("cli---> %s\n", buff);strcat(buff, "------ok!");size = send(i, buff, strlen(buff), 0);if (size < 0){perror("fail recv");FD_CLR(i, &rdfds);close(i);continue; }}}}return 0;
}
select缺点:
1.select监听文件描述符最大个数为1024 (数组) 时间复杂度:O(n)
2.select监听的文件描述符集合在用户层,需要应用层和内核层互相传递数据
3.select需要循环遍历一次才能找到产生的事件
4.select只能工作在水平触发模式(低速模式)无法工作在边沿触发模式(高速模式)
poll函数
poll缺点:
1.poll监测文件描述符不受上限限制 |(链表) 时间复杂度:O(n)
2.poll监听的文件描述符集合在用户层,需要内核层向用户层传递数据
3.poll需要循环遍历一次才能找到产生的事件
4.poll只能工作在水平触发模式(低速模式)无法工作在边沿触发模式(高速模式)
epoll函数
epoll是Linux特有的I/O事件通知机制,它是基于事件驱动的方式来处理多个文件描述符的I/O操作。相比于传统的select和poll方法,epoll能够更高效地处理大量的并发连接,因为它在内核中使用了更高效的数据结构和算法。
核心函数
epoll_create(int size)
创建一个新的epoll实例,并返回一个文件描述符,用于后续操作。
参数size在Linux 2.6.8及以后的版本中已被忽略,但仍需大于0。
返回值:成功时返回新的文件描述符,失败时返回-1并设置errno。
*epoll_ctl(int epfd, int op, int fd, struct epoll_event event)
向epoll实例中添加、修改或删除一个文件描述符及其事件。
epfd是epoll_create返回的epoll文件描述符。
op指定操作类型(EPOLL_CTL_ADD添加,EPOLL_CTL_MOD修改,EPOLL_CTL_DEL删除)。
select函数的原型(在POSIX标准中)大致如下:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
epfd:文件描述符集合句柄
op:
EPOLL_CTL_ADD: 向集合中添加文件描述符
EPOLL_CTL_MOD: 修改集合
EPOLL_CTL_DEL :删除文件描述符
fd :操作的文件描述符
event :文件描述符所对应的事件
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
event : EPOLLIN : 读操作
EPOLLOUT : 写事件
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能:监测IO事件
参数:
epfd : 文件描述符集合句柄
events : 保存到达事件的结合的首地址
maxevents : 监测时事件的个数
timeout:超时时间
-1 :不设置超时时间
返回值:
成功:返回到达事件的个数
失败:-1
设置超时:超时时间到达返回0
fd是要操作的文件描述符。
event指向一个epoll_event结构体,指定了感兴趣的事件和回调函数(可选)。
返回值:成功时返回0,失败时返回-1并设置errno。
*epoll_wait(int epfd, struct epoll_event events, int maxevents, int timeout)
等待并收集epoll实例上注册的文件描述符上发生的事件。
epfd是epoll文件描述符。
events是一个数组,用于存储收集到的事件。
maxevents指定了events数组的最大长度。
timeout是等待的超时时间(毫秒),-1表示无限等待,0表示立即返回。
返回值:成功时返回收集到的事件数量,0表示超时,失败时返回-1并设置errno。
epoll read.c
#include "head.h"int epoll_add_fd(int epfds, int fd, uint32_t event)
{struct epoll_event ev;ev.events = event;ev.data.fd = fd;int ret = epoll_ctl(epfds, EPOLL_CTL_ADD, fd, &ev);if (-1 == ret){perror("fail epoll_ctl add");return -1;}return 0;
}int epoll_del_fd(int epfds, int fd)
{int ret = epoll_ctl(epfds, EPOLL_CTL_DEL, fd, NULL);if (-1 == ret){perror("fail epoll_ctl del");return -1;}return 0;
}int main(int argc, const char *argv[])
{int maxfd = 0;char buff[1024] = {0};mkfifo("./myfifo", 0664);int fifofd = open("./myfifo", O_RDONLY);if (-1 == fifofd){perror("fail open fifo");return -1;}struct epoll_event evs[2];int epfds = epoll_create(2);if (-1 == epfds){perror("fail epoll_create");return -1;}epoll_add_fd(epfds, 0, EPOLLIN);epoll_add_fd(epfds, fifofd, EPOLLIN);while (1){int cnt = epoll_wait(epfds, evs, 2, -1);if (cnt < 0){perror("fail epoll_wait");return -1;}for (int i = 0; i < cnt; i++){if (0 == evs[i].data.fd){memset(buff, 0, sizeof(buff));fgets(buff, sizeof(buff), stdin);printf("STDIN: %s\n", buff);}else if (fifofd == evs[i].data.fd){memset(buff, 0, sizeof(buff));ssize_t size = read(evs[i].data.fd, buff, sizeof(buff));if (size <= 0){epoll_del_fd(epfds, evs[i].data.fd);close(evs[i].data.fd);continue;}printf("FIFO: %s\n", buff);}}}return 0;
}
epoll写服务器
优点
1.epoll创建内核事件表,不受到文件描述符上限限制(红黑树) 时间复杂度:O(logn)
2.epoll监听的事件表在内核中,直接在内核中监测事件效率高
3.epoll会直接获得产生事件的文件描述符的信息,而不需要遍历检测
4.epoll既能工作在水平触发模式,也能工作在边沿触发模式