IO复用

来源:互联网 发布:linux vi 跳到最后 编辑:程序博客网 时间:2024/06/06 03:10

概念:

多路网络连接复用一个IO线程。

如果用监控来自10根不同地方的水管(I/O端口)是否有水流到达(即是否可读),那么需要10个人(即10个线程或10处代码)来做这件事。如果利用某种技术(比如摄像头)把这10根水管的状态情况统一传达到某一点,那么就只需要1个人在那个点进行监控就行了。
由于I/O多路复用是在单一进程的上下文中的,因此每个逻辑流程都能访问该进程的全部地址空间,所以开销比多进程低得多;缺点是编程复杂度高

优点:开销低。

缺点:编程复杂度高。

注意:

下面3种模式,会将监听到的那一位置一,其余位置零。

select模式:

int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);

maxfd——需要监视的最大的文件描述符值+1。

rdset——需要检测的可读文件描述符的集合。

wrset——需要检测的可写文件描述符的集合。

exset——需要检测的异常文件描述符的集合。

timeout——超时时间。

struct timeval结构体:

struct timeval {               long    tv_sec;         /* seconds */               long    tv_usec;        /* microseconds */           };

返回值——=0为超时;-1为出错;>0为获取到数据。

FD_ZERO(fd_set *fdset);

清空文件描述符。

FD_SET(int fd,fd_set *fd_set);

向文件描述符集合中增加一个新的文件描述符。

FD_CLR(int fd,fd_set *fd_set);

在文件描述符集合中删除一个文件描述符。

FD_ISSET(int fd,fd_set *fd_set);

测试指定的文件描述符是否在该集合中。

FD_SETSIZE————256

select编码流程:


缺点:

1.需要修改传入的参数数组;

2.不能确切指定有数据的socket;

3.只能监视FD_SETSIZE个链接;

4.线程不安全。

代码:

服务器:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <signal.h>#include <errno.h>#include <pthread.h>#include <arpa/inet.h>#include <sys/socket.h>#include <sys/wait.h>#define max(a,b) ((a)>(b)?(a):(b))void echo(int connfd){ssize_t len;char buf[BUFSIZ];while((len = read(connfd,buf,BUFSIZ))>0){write(connfd,buf,len);}close(connfd);}int main(int argc,char* argv[]){if(2 > argc || 3 < argc){printf("usage:%s <#port> [<#backlog>]\n",argv[0]);return 1;}int listenfd = socket(AF_INET,SOCK_STREAM,0);if(-1 == listenfd){perror("listenfd open err");return 1;}struct sockaddr_in local_addr;bzero(&local_addr,sizeof(local_addr));local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = INADDR_ANY;local_addr.sin_port = htons(atoi(argv[1])); if(bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))){perror("bind err");return 1;}printf("bind ok\n");int backlog = (argc == 3)?atoi(argv[2]):10;if(listen(listenfd,backlog)){perror("listen err");return 1;}printf("listen ok\n");struct sockaddr_in remote_addr;socklen_t remote_addr_len = sizeof(remote_addr);fd_set rset;FD_ZERO(&rset);int maxfdp1 = listenfd + 1;int connfds[FD_SETSIZE-1];// listenfd已经占用一个名额memset(connfds,-1,FD_SETSIZE-1);int connfds_cnt = 0;for(;;){// 设置监视的描述符FD_SET(listenfd,&rset);int i;for(i=0;i<connfds_cnt;i++){if(-1 == connfds[i]){size_t cnt = connfds_cnt-i-1;if(cnt > 0){memcpy(connfds+i,connfds+i+1,cnt);FD_SET(connfds[i],&rset);maxfdp1 = max(maxfdp1,connfds[i]) + 1 ;}connfds[connfds_cnt-1] = -1;connfds_cnt--;}else{FD_SET(connfds[i],&rset);maxfdp1 = max(maxfdp1,connfds[i]) + 1 ;}}// 阻塞监视if(select(maxfdp1,&rset,NULL,NULL,NULL) > 0){// 判断是否有新的连接if(FD_ISSET(listenfd,&rset)){int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);if(-1 == connfd){
//慢系统调用accept中断重启兼容性处理
if(EINTR == errno){continue;}perror("accept err");return 1;}if(connfds_cnt+1 == FD_SETSIZE -1){perror("over fdset size");shutdown(connfd,SHUT_RDWR);}else{connfds[connfds_cnt] = connfd;connfds_cnt++;}}// 判断是否有请求数据int i;for(i=0;i<connfds_cnt;i++){if(-1 == connfds[i]){continue;}if(FD_ISSET(connfds[i],&rset)){ssize_t len;char buf[BUFSIZ];if((len = read(connfds[i],buf,BUFSIZ))>0){write(connfds[i],buf,len);}else{close(connfds[i]);FD_CLR(connfds[i],&rset);connfds[i] = -1;}}}}}close(listenfd);}

客户机:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <arpa/inet.h>#include <sys/socket.h>#include <sys/select.h>#define max(a,b) ((a)>(b)?(a):(b))int main(int argc,char* argv[]){if(3 != argc){printf("usage:%s <ip> <#port>\n",argv[0]);return 1;}int connfd = socket(AF_INET,SOCK_STREAM,0);if(-1 == connfd){perror("connfd open err");return 1;}struct sockaddr_in remote_addr;bzero(&remote_addr,sizeof(remote_addr));remote_addr.sin_family = AF_INET;remote_addr.sin_addr.s_addr = inet_addr(argv[1]);remote_addr.sin_port = htons(atoi(argv[2]));if(-1 == connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){perror("connect err");return 1;}char buf[BUFSIZ];bzero(buf,BUFSIZ);fd_set rset;FD_ZERO(&rset);for(;;){FD_SET(STDIN_FILENO,&rset);FD_SET(connfd,&rset);int maxfdp1 = max(STDIN_FILENO,connfd) + 1;if(select(maxfdp1,&rset,NULL,NULL,NULL) > 0){if(FD_ISSET(connfd,&rset)){ssize_t len = 0;bzero(buf,BUFSIZ);if((len = read(connfd,buf,BUFSIZ))>0){buf[len] = '\0';fputs(buf,stdout);}else{printf("exit %s\n",argv[0]);return 0;}}if(FD_ISSET(STDIN_FILENO,&rset)){bzero(buf,BUFSIZ);if(fgets(buf,BUFSIZ,stdin)){if(strcmp(buf,"q\n") == 0){shutdown(connfd,SHUT_WR);FD_CLR(STDIN_FILENO,&rset);}else{write(connfd,buf,strlen(buf));}}}}}}

poll模式:

int poll(struct pollfd *fdarray,unsigned long nfds,int timeout);

fdarray:

struct pollfd {               int   fd;         /* file descriptor */               short events;     /* requested events */               short revents;    /* returned events */           };
fd——文件描述符。


events(监视fd事件):

输入:

POLLRDNORM——普通数据。

POLLRDBAND——优先级带数据。

POLLIN——普通或者优先级带数据。

输出:

POLLWRNORM——普通数据。

POLLWRBAND——优先级带数据。

POLLOUT——普通或者优先级带数据。


revent(fd实际发生的事件):

输入:

POLLRDNORM——普通数据。

POLLRDBAND——优先级带数据。

POLLIN——普通或者优先级带数据。

输出:

POLLWRNORM——普通数据。

POLLWRBAND——优先级带数据。

POLLOUT——普通或者优先级带数据。

出错:

POLLERR——发生错误。

POLLHUP——发生挂起。

POLLNVAL——描述符非法。


nfds——数组元素个数。


timeout(等待时间):

INFTIM——永久等待。

0—————立即返回。

>0————等待秒数。


返回值:

0————超时;

-1————出错;

正数———就绪描述符个数。


概念:

普通数据——>正规的TCP数据;所有的UDP数据。

优先级带数据——>TCP带外数据。

优点:

不需要修改传入的参数数组;

可以监视任意个链接——cat  /proc/sys/fs/file-max

缺点:

不能确切指定有数据的socket;

线程不安全。

代码:

服务器:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <signal.h>#include <errno.h>#include <pthread.h>#include <poll.h>#include <linux/fs.h>#include <limits.h>#include <arpa/inet.h>#include <sys/socket.h>#include <sys/wait.h>#ifndef INFTIM#define INFTIM -1#endifvoid echo(int connfd){ssize_t len;char buf[BUFSIZ];while((len = read(connfd,buf,BUFSIZ))>0){write(connfd,buf,len);}close(connfd);}int main(int argc,char* argv[]){if(2 > argc || 3 < argc){printf("usage:%s <#port> [<#backlog>]\n",argv[0]);return 1;}int listenfd = socket(AF_INET,SOCK_STREAM,0);if(-1 == listenfd){perror("listenfd open err");return 1;}struct sockaddr_in local_addr;bzero(&local_addr,sizeof(local_addr));local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = INADDR_ANY;local_addr.sin_port = htons(atoi(argv[1])); if(bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))){perror("bind err");return 1;}printf("bind ok\n");int backlog = (argc == 3)?atoi(argv[2]):10;if(listen(listenfd,backlog)){perror("listen err");return 1;}printf("listen ok\n");struct sockaddr_in remote_addr;socklen_t remote_addr_len = sizeof(remote_addr);struct pollfd pollfds[INR_OPEN_MAX];int i;for(i=0;i<INR_OPEN_MAX;i++){pollfds[i].fd = -1;}pollfds[0].fd = listenfd;pollfds[0].events = POLLRDNORM;int pollfds_cnt = 1;for(;;){if(poll(pollfds,pollfds_cnt,INFTIM) > 0){if(pollfds[0].revents & POLLRDNORM){int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);if(-1 == connfd){if(EINTR == errno){continue;}perror("accept err");return 1;}if(pollfds_cnt+1 == INR_OPEN_MAX){perror("over open size");shutdown(connfd,SHUT_RDWR);}else{int i;for(i=0;i<INR_OPEN_MAX;i++){if(pollfds[i].fd == -1){break;}}pollfds[i].fd = connfd;pollfds[i].events = POLLRDNORM;pollfds_cnt++;}}int i;for(i=1;i<INR_OPEN_MAX;i++){if(pollfds[i].fd == -1){continue;}if(pollfds[i].events & POLLRDNORM){ssize_t len;char buf[BUFSIZ];if((len = read(pollfds[i].fd,buf,BUFSIZ))>0){write(pollfds[i].fd,buf,len);}else{shutdown(pollfds[i].fd,SHUT_RDWR);close(pollfds[i].fd);pollfds[i].fd = -1;pollfds_cnt--;}}}}}close(listenfd);}

客户机:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <arpa/inet.h>#include <sys/socket.h>#include <poll.h>#ifndef INFTIM#define INFTIM -1#endifint main(int argc,char* argv[]){if(3 != argc){printf("usage:%s <ip> <#port>\n",argv[0]);return 1;}int connfd = socket(AF_INET,SOCK_STREAM,0);if(-1 == connfd){perror("connfd open err");return 1;}struct sockaddr_in remote_addr;bzero(&remote_addr,sizeof(remote_addr));remote_addr.sin_family = AF_INET;remote_addr.sin_addr.s_addr = inet_addr(argv[1]);remote_addr.sin_port = htons(atoi(argv[2]));if(-1 == connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){perror("connect err");return 1;}char buf[BUFSIZ];bzero(buf,BUFSIZ);struct pollfd pollfds[] = {{STDIN_FILENO,POLLRDNORM},{connfd,POLLRDNORM}};int pollfds_cnt = sizeof(pollfds)/sizeof(pollfds[0]);for(;;){if(poll(pollfds,pollfds_cnt,INFTIM) > 0){if(pollfds[1].revents & POLLRDNORM){ssize_t len = 0;bzero(buf,BUFSIZ);if((len = read(connfd,buf,BUFSIZ))>0){buf[len] = '\0';fputs(buf,stdout);}else{printf("exit %s\n",argv[0]);return 0;}}if(pollfds[0].revents & POLLRDNORM){bzero(buf,BUFSIZ);if(fgets(buf,BUFSIZ,stdin)){if(strcmp(buf,"q\n") == 0){shutdown(connfd,SHUT_WR);}else{write(connfd,buf,strlen(buf));}}}}}}

epoll模式:

优点:

能确切指定有数据的socket;

线程安全。

创建:int  epoll_create(int size);

size——监听的数目。

返回值——文件描述符;/proc/进程id/fd/


控制:int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

epfd——epoll文件描述符。

op——操作:

EPOLL_CTL_ADD——注册

EPOLL_CTL_MOD——修改(modify)

EPOLL_CTL_DEL——删除

fd——关联的文件描述符。

event——指向epoll_event的指针

Epoll events说明EPOLLOUT表示对应的文件描述符可以写EPOLLPRI表示对应的文件描述符有紧急的数据可读EPOLLERR表示对应的文件描述符发生错误EPOLLHUP表示对应的文件描述符被挂断EPOLLET        表示对应的文件描述符设定为edge模式
返回值——0为成功;-1为失败。

轮询I/O事件:int epoll_wait(int epfd,struct epoll_event events,int maxevents,int timeout);

epfd——epoll文件描述符。

epoll_event——用于回传待处理事件的数组。

maxevents——买次能处理的事件数。

timeout——等待I/O事件发生的超时值ms——0为立即返回;-1为永不超时。

返回值——正数为发生事件数;-1为错误。

模式:

ET(edge triggered)模式

LT(level triggered)模式

1. 标示管道读者的文件句柄注册到epoll中;
2. 管道写者向管道中写入2KB的数据;
3. 调用epoll_wait可以获得管道读者为已就绪的文件句柄;
4. 管道读者读取1KB的数据
5. 一次epoll_wait调用完成
如果是ET模式,管道中剩余的1KB被挂起,再次调用epoll_wait,得不到管道读者的文件句柄,除非有新的数据写入管道。如果是LT模式,只要管道中有数据可读,每次调用epoll_wait都会触发。
另一点区别就是设为ET模式的文件句柄必须是非阻塞的。

代码:

服务器:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <signal.h>#include <errno.h>#include <pthread.h>#include <sys/epoll.h>#include <limits.h>#include <arpa/inet.h>#include <sys/socket.h>#include <sys/wait.h>#include <linux/fs.h>#include <fcntl.h>#define max(a,b) ((a)>(b)?(a):(b))#ifndef INFTIM#define INFTIM -1#endifint main(int argc,char* argv[]){if(2 > argc || 3 < argc){printf("usage:%s <#port> [<#backlog>]\n",argv[0]);return 1;}int listenfd = socket(AF_INET,SOCK_STREAM,0);if(-1 == listenfd){perror("listenfd open err");return 1;}struct sockaddr_in local_addr;bzero(&local_addr,sizeof(local_addr));local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = INADDR_ANY;local_addr.sin_port = htons(atoi(argv[1])); int flag = 1;if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag))){perror("setsockopt err");return 1;}if(bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))){perror("bind err");return 1;}printf("bind ok\n");int backlog = (argc == 3)?atoi(argv[2]):10;if(listen(listenfd,backlog)){perror("listen err");return 1;}printf("listen ok\n");struct sockaddr_in remote_addr;socklen_t remote_addr_len = sizeof(remote_addr);int epoll_fd = epoll_create(INR_OPEN_MAX);struct epoll_event listenfd_event;//fcntl(listenfd,F_SETFL,O_NONBLOCK);listenfd_event.data.fd = listenfd;listenfd_event.events = EPOLLIN;//|EPOLLET;epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listenfd,&listenfd_event);int evts_cnt = 1;for(;;){struct epoll_event evts[evts_cnt];int fd_cnt = epoll_wait(epoll_fd,evts,evts_cnt,-1);int i;for(i = 0;i < fd_cnt; i++){if(evts[i].data.fd == listenfd){printf("accept connect\n");int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);if(-1 == connfd){if(EINTR == errno){continue;}perror("accept err");return 1;}if(evts_cnt+1 == INR_OPEN_MAX){perror("over open size");close(connfd);}else{struct epoll_event connfd_evt;connfd_evt.data.fd = connfd;//fcntl(listenfd,F_SETFL,O_NONBLOCK);connfd_evt.events = EPOLLIN;//|EPOLLET;epoll_ctl(epoll_fd,EPOLL_CTL_ADD,connfd,&connfd_evt);evts_cnt++;printf("server new connfd:%d\n",connfd);}}else{if(evts[i].events & EPOLLIN){ssize_t len;char buf[BUFSIZ];if((len = read(evts[i].data.fd,buf,BUFSIZ))>0){write(evts[i].data.fd,buf,len);}else{close(evts[i].data.fd);epoll_ctl(epoll_fd,EPOLL_CTL_DEL,evts[i].data.fd,&evts[i]);evts_cnt--;}}}}}close(epoll_fd);close(listenfd);}

客户机:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <linux/fs.h>#include <arpa/inet.h>#include <sys/socket.h>#include <sys/epoll.h>#ifndef INFTIM#define INFTIM -1#endifint main(int argc,char* argv[]){if(3 != argc){printf("usage:%s <ip> <#port>\n",argv[0]);return 1;}int connfd = socket(AF_INET,SOCK_STREAM,0);if(-1 == connfd){perror("connfd open err");return 1;}struct sockaddr_in remote_addr;bzero(&remote_addr,sizeof(remote_addr));remote_addr.sin_family = AF_INET;remote_addr.sin_addr.s_addr = inet_addr(argv[1]);remote_addr.sin_port = htons(atoi(argv[2]));if(-1 == connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){perror("connect err");return 1;}char buf[BUFSIZ];bzero(buf,BUFSIZ);int epollfd = epoll_create(INR_OPEN_MAX);struct epoll_event in_evts[2];in_evts[0].data.fd = STDIN_FILENO;in_evts[0].events = EPOLLIN;in_evts[1].data.fd = connfd;in_evts[1].events = EPOLLIN;epoll_ctl(epollfd,EPOLL_CTL_ADD,STDIN_FILENO,in_evts);epoll_ctl(epollfd,EPOLL_CTL_ADD,connfd,in_evts+1);int out_evts_cnt = 2;for(;;){struct epoll_event out_evts[out_evts_cnt];int fd_cnt = epoll_wait(epollfd,out_evts,out_evts_cnt,INFTIM);int i;for(i=0;i<fd_cnt;i++){if(out_evts[i].data.fd == STDIN_FILENO && (out_evts[i].events & EPOLLIN)){bzero(buf,BUFSIZ);if(fgets(buf,BUFSIZ,stdin)){if(strcmp(buf,"q\n") == 0){shutdown(connfd,SHUT_WR);}else{write(connfd,buf,strlen(buf));}}}else if(out_evts[i].data.fd == connfd && (out_evts[i].events & EPOLLIN)){ssize_t len = 0;bzero(buf,BUFSIZ);if((len = read(connfd,buf,BUFSIZ))>0){buf[len] = '\0';fputs(buf,stdout);}else{printf("exit %s\n",argv[0]);return 0;}}}}}

原创粉丝点击