I/O多路复用-创新互联

I/O模型

在UNIX/Linux下主要有4种I/O 模型:
阻塞I/O:最常用
非阻塞I/O:可防止进程阻塞在I/O操作上,需要轮询
I/O 多路复用:允许同时对多个I/O进行控制
信号驱动I/O:一种异步通信模型
阻塞I/O
阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
缺省情况下,套接字建立后所处于的模式就是阻塞I/O 模式。
很多读写函数在调用过程中会发生阻塞。
读操作中的read、recv、recvfrom
写操作中的write、send
其他操作:accept、connect
非阻塞I/O
当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”
当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。
应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
这种模式使用中不普遍。
非阻塞I/O实现:
fcntl()函数
当你一开始建立一个套接字描述符的时候,系统内核将其设置为阻塞IO模式。
可以使用函数fcntl()设置一个套接字的标志为O_NONBLOCK 来实现非阻塞。
代码实现;
1.fcntl( )函数int fcntl(int fd, int cmd, long arg);
int flag;
flag = fcntl(sockfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(sockfd, F_SETFL, flag);
2.ioctl() 函数
int b_on =1;
ioctl(sock_fd, FIONBIO, &b_on);
多路复用I/O
应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
比较好的方法是使用I/O多路复用。其基本思想是:
先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。

沙县网站建设公司创新互联,沙县网站设计制作,有大型网站制作公司丰富经验。已为沙县上千家提供企业网站建设服务。企业网站搭建\成都外贸网站建设要多少钱,请找那个售后服务好的沙县做网站的公司定做!select()实现多路复用

int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
select()参数
maxfd
所有监控的文件描述符中大的那一个加1
read_fds
所有要读的文件文件描述符的集合
write_fds
所有要的写文件文件描述符的集合
except_fds
其他要向我们通知的文件描述符 (异常集合)
timeout
超时设置.
Null:一直阻塞,直到有文件描述符就绪或出错
时间值为0:仅仅检测文件描述符集的状态,然后立即返回
时间值不为0:在指定时间内,如果没有事件发生,则超时返回

宏的形式:
void FD_ZERO(fd_set *fdset) //集合清零
void FD_SET(int fd,fd_set *fdset) //把fd加入集合
void FD_CLR(int fd,fd_set *fdset) //把fd删除
int FD_ISSET(int fd,fd_set *fdset) //判断fd是否在集合中。
在这里插入图片描述
select使用步骤:
首先,建立相关读集合、写集合或异常集合,集合清零。
其次,把关心的集合加入到相关集合中,并更新maxfd
接着,调用select去监控集合中的状态,当有数据时,退出select阻塞。
然后,依次判断那个文件描述符是否有效,然后处理,若是客户退出,就用FD_CLR清掉FD,并更新maxfd
如果,accept了一个新描述符,就加入到集合中,并更新maxfd。
最后,回到开始,继续循环。

练习:
基于tcp模型的IO多路复用(select)程序,在服务器端采用select来实现客户端的多路并发
代码如下:
客户端代码:

#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__
#include#include#include#include#include#include#include#include#include#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.203.130"
#define BACKLOG 5
#define QUIT_STR "quit"
#include#define SERV_RESP_STR "Server:"
#include#endif
void usage(char *s){printf("\n%s serv_ip serv_port\n",s);
	printf("\n\t serv_ip:server ip address");
	printf("\n\t serv_port:server port(>5000)\n\n");
}

int main(int argc, char *argv[])
{int fd = -1;
	int port;
	if(argc != 3){usage(argv[0]);
		exit(1);
	}
	struct sockaddr_in sin;
	//1.创建socket fd
	if((fd = socket(AF_INET,SOCK_STREAM,0))< 0){perror("socket");
		exit(1);
	}
	port = atoi(argv[2]);
	if(port< 5000){usage(argv[0]);
		exit(1);

	}
	//2.连接服务器
	bzero(&sin,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);  //网络字节序端口号
	if(inet_pton(AF_INET,argv[1],(void *)&sin.sin_addr)!=1){perror("inet_pton");
		exit(1);
	}
	if(connect(fd,(struct sockaddr *)&sin,sizeof(sin))< 0){perror("connect");
		exit(1);
	}


	fd_set rset;
	int maxfd = -1;
	int ret = -1;
	struct timeval tout;
	char buf[BUFSIZ];
	while(1){FD_ZERO(&rset);
		FD_SET(0,&rset);
		FD_SET(fd,&rset);
		maxfd = fd;
		
		tout.tv_sec = 5;
		tout.tv_usec = 0;

		select(maxfd+1,&rset,NULL,NULL,&tout);
		if(FD_ISSET(0,&rset)){//标准键盘上有输入
			//读取键盘输入
			bzero(buf,BUFSIZ);
			do{		ret = read(0,(void *)buf,BUFSIZ-1);
			}while((ret< 0) && (EINTR == errno));
			if(ret< 0){		perror("read");
				continue;
			}
			if(ret == 0){		continue;
			}
			if(write(fd,buf,strlen(buf))< 0){		perror("write () to socket");
				continue;
			}
			if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){		printf("client is exiting!\n");
				break;
			}
		}	
		if(FD_ISSET(fd,&rset)){//服务器给发过来数据
			//读取套接字数据	
			bzero(buf,BUFSIZ);
			do{		ret = read(fd,(void *)buf,BUFSIZ-1);
			}while((ret< 0) && (EINTR == errno));
			if(ret< 0){		perror("read from socket");
				continue;
			}
			if(ret == 0){//服务器关闭
				break;
			}
			printf("server said:%s\n",buf);
			//there is a bug FIXME
			if((strlen(buf) >strlen(SERV_RESP_STR))
					&& (!strncasecmp(buf+strlen(SERV_RESP_STR),QUIT_STR,strlen(QUIT_STR)))){		printf("server client is exiting!\n");
				break;
			}
		}

	}
	close(fd);
	return 0;
}

服务器代码:

#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__
#include#include#include#include#include#include#include#include#include#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.203.130"
#define BACKLOG 5
#define QUIT_STR "quit"
#include#define SERV_RESP_STR "Server:"

#include#endif
#include "net.h"
void * cli_data_handle(void * arg);
int creatFd();
int main(int argc, char *argv[])
{fd_set rset,clientfds;
	int maxfd = -1;
	int i;
	int fd,newfd;
	char buf[BUFSIZ];
	struct timeval tout;
	int ret = 0;

	fd = creatFd();

	maxfd = fd+1;

	FD_ZERO(&clientfds);

	while(1){FD_ZERO(&rset);
		FD_SET(fd,&rset);

		i = fd;
		while(++i< maxfd){	if(FD_ISSET(i,&clientfds)){		FD_SET(i,&rset);
			}
		}
		tout.tv_sec = 5;
		tout.tv_usec = 0;

		ret = select(maxfd,&rset,NULL,NULL,&tout);
		i = fd;
		while(++i< maxfd){	if(FD_ISSET(i,&rset)){		bzero(buf,BUFSIZ);
				read(i,buf,BUFSIZ);
				printf("buf=%s\n",buf);
				if(!strncasecmp(buf,"quit",4)){close(i);
					FD_CLR(i,&clientfds);
					printf("\tclientfd %d exit\n",i);
				}
			}
		}
		if(FD_ISSET(fd,&rset)){	struct sockaddr_in cin;
			socklen_t addrlen = sizeof(cin);	
			if((newfd = accept(fd,(struct sockaddr *)&cin,&addrlen))< 0){perror("accept");exit(1);}
			FD_SET(newfd,&clientfds);
			maxfd = (maxfd >newfd) ? maxfd : newfd+1;
		}

	}

	return 0;

}
int creatFd(){//封装,fd(由socket创建,并由bind绑定,listen倾听的fd)
	int fd = socket(AF_INET,SOCK_STREAM,0);
	if(fd< 0){perror("socket");exit(1);}
	struct sockaddr_in sin;
	socklen_t addrlen = sizeof(sin);
	memset(&sin,0,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	if(bind(fd,(struct sockaddr *)&sin,addrlen)< 0){perror("bind");exit(1);}
	if(listen(fd,BACKLOG)< 0){perror("listen");exit(1);}
	return fd;
}

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


文章名称:I/O多路复用-创新互联
文章网址:http://myzitong.com/article/dsspgd.html