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;}}}}}
- IO复用
- IO 复用
- IO复用
- IO复用
- IO 复用
- IO复用
- IO复用
- IO复用
- IO复用
- IO复用\阻塞IO\非阻塞IO\同步IO\异步IO
- IO复用\阻塞IO\非阻塞IO\同步IO\异步IO .
- 网络模型:阻塞IO,非阻塞IO,IO复用,信号驱动IO,异步IO
- linux下五种IO模型小结(阻塞IO、非阻塞IO、IO复用、信号驱动式IO、异步IO)
- 【UNIX】IO复用
- IO复用,linux poll
- IO复用,linux poll
- Linux的IO复用
- Linux IO 复用
- 快速排序方法
- 模块化的进一步
- 解决intel/caffe配置时候,mklml文件无法下载问题
- Productivity Power Tools工具
- vc++快速使用richedit控件
- IO复用
- shell批量处理
- ubuntu 16.04 测试Kinect 1 保存深度图像
- Redis常用命令
- freemarker常用内置函数
- leetcode_112. Path Sum
- 使用HttpServlet的三种方式
- 表单判断(正则)
- jmeter个人学习之路--常见问题及解决方案