IO多路转接之select

一、初识select
系统提供select函数来实现多路复用输入输出模型。Select系统调用是用来让我们的程序监视多个文件描述符的状态变化的,程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变。

二、select函数解释
1、select的函数原型

#include<sys/select.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

(1)nfds:参数nfds是需要监视的最大的文件描述符+1;
(2)readfd、writefds、exceptfds:分别对应于需要检测的可读、可写和异常文件描述符的集合。
(3)timeout:表示延时。
1)NULL:表示select函数没有timeout,select将一直阻塞,直到某个文件描述符上发生了事件;
2)0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
3)特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

2、与select函数相关的结构
(1)fd_set结构
这个结构就是一个整数数组,更严格的说是一个“位图”,使用位图中对应的位来表示要监视的文件描述符。为了方便操作位图,系统提供了一组操作fd_set的接口:

1)void FD_CLR(int fd, fd_set* set);//用来清除描述词组set中相关的fd的位。
2)int FD_ISSET(int fd, fd_set* set);//用来测试描述词组set中相关的fd的位。
3)void FD_SET(int fd, fd_set* set);//用来设置描述词组set中相关的fd的位。
4)void FD_ZERO(fd_set* set);//用来清除描述词组set的全部的位。

(2)timeval结构
timeval结构用于描述一段时间长度需要监视的描述符,而返回不同的函数值:
1)执行成功则返回文件描述词状态已改变的个数;
2)如果返回0代表在描述词改变前已超过timeout时间,没有返回;
3)当有错误发生时则返回-1,错误原因存于errno,此时参数readfds、writefds、exceptfds和timeout的值不可预测。
注意:错误值可能是*EBAFD文件描述词为无效的或该文件已关闭;*EINTR此调用被信号所中断;*EINVAL参数n为负值;ENOMEM核心内存不足。

3、常见的程序片段如下:

fs_set readset;
FD_SET(fd, &readset);
select(fd+1, &readset, NULL, NULL, NULL);
if(FD_ISSET(fd, readset)){......}

4、select的执行过程
理解select模型的关键在于理解fd_set,为说明方便,取fd_set的长度为1字节,fd_set中的每一位可以对应一个文件描述符fd,则1字节长的fd_set最大可以对应8个fd。
(1)执行“fd_set set;FD_ZERO(&set);”如果用位表示是0000 0000。
(2)若fd=5,执行“FD_SET(fd, &set);”后set变为0001 0000(第五位置1)。
(3)若再加入fd=2,fd=1,则set变为0001 0011。
(4)执行select(6, &set, 0, 0, 0)阻塞等待。
(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000 0011。
注意:没有事件发生的fd=5被清空。

5、socket就绪条件
(1)读就绪
1)socket内核中,接收缓冲区中字节数大于等于低水位标记SO_RCVLOWAT,此时可以无阻塞的读该文件描述符,并且返回值大于0。
2)socket TCP通信中,对端关闭连接,此时对该socket读,则返回0。
3)监听的socket上有新的连接请求。
4)socket上有未处理的错误。
(2)写就绪
1)socket内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小),大于等于低水位标记SO_SNDLOWAT,此时可以无阻塞的写,并且返回值大于0。
2)socket的写操作被关闭(close或者shutdown),对一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号。
3)socket使用非阻塞connect连接成功或失败之后。
4)socket上有未读取的错误。
6、select的特点
(1)可监控的文件描述符个数取决于sizeof(fd_set)的值,服务器上sizeof(fd_set)=512byte,每bit表示一个文件描述符,则服务器上支持的最大文件描述符是512*8=4096个。
(2)将fd加入select监控集的同时,还要使用一个数据结构array保存放到select监控集中的fd。
1)第一是用于在select返回后array作为源数据和fd_set进行fd_isset判断。
2)第二是select返回后把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

7、select的缺点
(1)每次调用select,都需要手动设置fd集合,从接口使用角度来说非常不便。
(2)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
(3)每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
(4)select支持的文件描述符数量太小。

8、使用示例
【例1】只检测标准输入

#include<stdio.h>
#include<stdlib.h>

int main()
{
	fd_set read_fds;
	FD_ZERO(&read_fds);
	FD_SET(0, &read_fds);

	for(; ;){
		printf(">");
		fflush(stdout);
		int ret = select(1, &read_fds, NULL, NULL, NULL);
		if(ret < 0){
			perror("select");
			continue;
		}
		if(FD_ISSET(0, &read_fds)){
			char buf[1024] = {0};
			read(0, buf, sizeof(buf)-1);
			printf("input:%s", buf);
		}else{
			printf("error!invaild fd\n");
			continue;
		}
		FD_ZERO(&read_fds);
		FD_SET(0, &read_fds);
	}
	return 0;
}

运行结果:
在这里插入图片描述
【例2】使用select编写网络服务器

//client
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>

void Usage()
{
	printf("usage:./client [ip] [port]\n");
}

int main(int argc, char *argv[])
{
	if(argc != 3){
		Usage();
		return 1;
	}
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(argv[1]);
	addr.sin_port = htons(atoi(argv[2]));

	int fd = socket(AF_INET, SOCK_STREAM, NULL);
	if(fd < 0){
		perror("socket");
		return 1;
	}
	int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
	if(ret < 0){
		perror("connect");
		return 1;
	}
	
	for(; ;){
		printf(">");
		fflush(stdout);
		
		char buf[1024] = {0};
		read(0, buf, sizeof(buf) - 1);
		ssize_t write_size = write(fd, buf, strlen(buf));
		if(write_size < 0){
			perror("write");
			continue;
		}

		ssize_t read_size = read(fd, buf, sizeof(buf) -1);
		if(read_size == 0){
			printf("server close\n");
			break;
		}
		printf("server say:%s", buf);
	}
	close(fd);
	return 0;
}


//client
#include<sys/socket.h>
#include<sys/select.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>

void Init(int* fd_list, int fd_list_size)
{
	int i = 0;
	for(i = 0; i < fd_list_size; ++i){
		fd_list[i] = -1;
	}
}

void Reload(int listen_fd, int* connect_list, int connect_list_size, 
			fd_set* read_fds, int* max_fd){
	FD_ZERO(read_fds);
	FD_SET(listen_fd, read_fds);
	int max = listen_fd;
	int i = 0;
	for(i = 0; i < connect_list_size; ++i){
		if(connect_list[i] != -1){
			FD_SET(connect_list[i], read_fds);
			if(connect_list[i] > max){
				max = connect_list[i];
			}
		}
	}
	*max_fd = max;
}

void Add(int fd, int* connect_list, int* connect_list_size){
	int i = 0;
	for(i = 0; i < connect_list_size; ++i){
		if(connect_list[i] == -1){
			connect_list[i] = fd;
			break;
		}
	}
	return;
}

int main(int argc, char* argv[])
{
	if(argc != 3){
		printf("usage:./server [ip] [port]\n");
		return 1;
	}
	
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(argv[1]);
	addr.sin_port = htons(atoi(argv[2]));

	int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(listen_fd < 0){
		perror("socket");
		return 1;
	}
	int ret = bind(listen_fd, (struct sockeaddr*)&addr, sizeof(addr));
	if(ret < 0){
		perror("listen");
		return 1;
	}
	fd_set read_fds;
	int fd_list[1024];
	Init(fd_list, sizeof(fd_list)/sizeof(int));
	for(; ;){
		int max_fd = listen_fd;
		Reload(listen_fd, fd_list, sizeof(fd_list)/sizeof(int), &read_fds, &max_fd);
		printf("after select:%d\n", FD_ISSET(listen_fd, &read_fds));
		if(ret == 0){
			printf("select timeout\n");
			continue;
		}
		//处理listen_fd
		if(FD_ISSET(listen_fd, &read_fds)){
			struct sockaddr_in client_addr;
			socklen_t len = sizeof(client_addr);
			int connect_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);
			if(connect_fd < 0){
				perror("accept");
				continue;
			}
			printf("client %s:%d connect\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
			Add(connect_fd, fd_list, sizeof(fd_list)/sizeof(int));
		}
		//处理connect_fd
		size_t i = 0;
		for(i = 0; i < sizeof(fd_list)/sizeof(int); ++i){
			if(fd_list[i] == -1){
				continue;
			}
			if(!FD_ISSET(fd_list[i], &read_fds)){
				continue;
			}
			char buf[1024] = {0};
			ssize_t read_size = read(fd_list[i], buf, sizeof(buf) - 1);
			if(read_size < 0){
				perror("read");
				continue;
			}
			if(read_size == 0){
				printf("client say:goodbye\n");
				close(fd_list[i]);
				fd_list[i] = -1;
			}
			printf("client say: %s", buf);
			write(fd_list[i], buf, strlen(buf));
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值