I/O多路复用
1.什么是I/O多路复用
I/O 多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;
一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;
没有文件句柄就绪就会阻塞应用程序,交出CPU。
2.为什么会有I/O多路复用机制
没有IO多路复用机制时,有BIO、NIO两种实现方式,但它们都有一些问题
1.同步阻塞
服务端采用单线程,当 accept 一个请求后,在 recv 或 send 调用阻塞时,将无法 accept 其他请求(必须等上一个请求处理 recv 或 send 完 )(无法处理并发)
服务端采用多线程,当 accept 一个请求后,开启线程进行 recv,可以完成并发处理,但随着请求数增加需要增加系统线程,大量的线程占用很大的内存空间,并且线程切换会带来很大的开销,10000个线程真正发生读写实际的线程数不会超过20%,每次accept都开一个线程也是一种资源浪费。
2.异步阻塞
服务器端当 accept 一个请求后,加入 fds 集合,每次轮询一遍 fds 集合 recv (非阻塞)数据,没有数据则立即返回错误,每次轮询所有 fd (包括没有发生读写实际的 fd)会很浪费 CPU资源
3.实现I/O多路复用的几种方式
1.select实现I/O多路复用
1.1.涉及的api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <sys/select.h>
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set exceptfds,struct timeval *timeout); int pselect(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout,sigset_t sigmask);
FD_ZERO(fd_set *set);
FD_SET(int fd,fd_set *set);
FD_CLR(int fd,fd_set *set);
|
1.2.操作实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> #include <sys/select.h> #include <arpa/inet.h> int main(int argc, char** argv) { int fd = New_Socket(argv[1], argv[2], 10); if (fd == -1) { printf("Create Socket error!\n"); exit(EXIT_FAILURE); } fd_set fdset, rset; FD_ZERO(&fdset); FD_SET(fd, &fdset); ..... int maxfd = fd + 1; struct timeval timeout = { 20, 20 }; while (1) { rset = fdset; int nfds = select(maxfd, &rset, NULL, NULL, &timeout); if (nfds < 0) { perror("select"); exit(EXIT_FAILURE); } else if (nfds == 0) { printf("select timeout\n"); continue; } else { if (FD_ISSET(fd, &rset)) { Handler(fd, &fdset); } } } }
|
2.使用poll实现I/O多路复用
2.1.涉及的api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <poll.h>
struct pollfd { int fd; short events; short revents; };
int poll(struct pollfd *fds,nfds_t nfds,int timeout); int ppoll(struct pollfd *fds,nfds_t nfds,cont struct timespec *tmo_p,const sigset_t *sigmask);
|
2.2.操作实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct pollfd { int fd; short event; short revent; } #define MAX_FD 100 int main(int argc,char **argv) { struct pollfd fd[MAX_FD]; fd[i].event = POLLIN int nfds = poll(struct pollfd fds[], nfds_t nfds, int timeout); for(int i = 0;i<MAX_FD,i++) {
if(fd[i].revent & POLLIN) ..... } }
|
3.使用epoll实现I/O多路复用
3.1.涉及的api
1 2 3 4 5 6 7 8 9 10
| 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); int epoll_pwait(int epfd, struct epoll_event * events, int maxevents, int timeout,const sigset_t *sigmask);
|
3.2.操作实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <sys/epoll.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int create_bind_listen(char *address,char *port,unsigned int backlog) { } int main(int argc,char **argv) { int fd = create_bind_listen(argv[1],argv[2],10); int epfd = epoll_create(100); struct epoll_event epoll_fd; struct epoll_event event_epoll[10]; epoll_fd.fd = fd; epoll_fd.events = EPOLLIN; epoll_fd.data.fd = fd; epoll_ctl(epfd,EPOLL_CTL_ADD,&epoll_fd); int event_count = epoll_wait(epfd,event_epoll,10); for(int i = 0;i<event_count;i++) { } }
|