IO复用

概念:

多路网络连接复用一个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
#endif

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);

	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
#endif

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);

	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
#endif

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])); 

	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
#endif

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);

	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;
				}
			}
		}
	}
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值