linux I/O复用select

目录

1、select简介

2、select使用

3、select缺点


1、select简介

在linux网络编程I/O复用使用的函数之一就是select,select函数是一个古老的接口

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

select函数主要有5个参数:

nfds:被监听所有文件描述最大加1,因为文件描述是从0开始的。

readfds:监听文件描述的可读事件。

writefds:监听文件描述的可写事件。

exceptfds:监听文件描述的异常事件。

timeout:超时时间。

select的返回值:

大于0:返回准备号的文件描述数量。

等于0:监听超时。

等于-1:监听错误。

struct timeval
{      
    long tv_sec;   /*秒 */
    long tv_usec;  /*微秒 */   
};

timeout有三种情况

(1) timeout == NULL

等待无限长的时间。等待可以被一个信号中断。当有一个描述符做好准备或者是捕获到一个信号时函数会返回。如果捕获到一个信号, select函数将返回-1,并将变量 erro设为 EINTR

(2)timeout->tv_sec == 0 &&timeout->tv_usec == 0

这种情况不等待,直接返回。加入描述符集的描述符都会被测试,并且返回满足要求的描述符的个数。这种方法通过轮询,无阻塞地获得了多个文件描述符状态

(3)timeout->tv_sec != 0  || timeout->tv_usec != 0

等待指定的时间。当有描述符符合条件或者超过超时时间的话,函数返回。在超时时间即将用完但又没有描述符合条件的话,返回 0。对于第一种情况,等待也会被信号所中断。

select的操作主要有以下四个宏:

int FD_ZERO(int fd, fd_set *fdset); //一个 fd_set类型变量的所有位都设为 0

int FD_CLR(int fd, fd_set *fdset); //清除某个位时可以使用 i

nt FD_SET(int fd, fd_set *fd_set); //设置变量的某个位置位

int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位

2、select使用

下面介绍一个select的使用例子

tcp_server:

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

int main(int argc, char *argv[]){

	int server_fd = 0;
	int port = 0;
	char *ip = NULL;

	int ret = 0;	
	struct sockaddr_in address;
	fd_set read_fds;
	char buf[512] = {0};
	struct timeval timeout;
	
	
	struct sockaddr_in client_address;
	int socket_len = sizeof(client_address);
	int client_fd = 0;
	int max_fds = 0;
	
	if(argc < 3){
		printf("error argv!\n");
		return -1;
	}

	port = atoi(argv[2]);
	ip = argv[1];
	
	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	inet_pton(AF_INET, ip, &address.sin_addr);
	
	server_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(server_fd < 0){
		printf("socket error!\n");
		return -1;
	}

	ret = bind(server_fd, (struct sockaddr*)&address, sizeof(address));
	if(ret == -1){
		printf("bind error!\n");
		return -1;
	}	
	
	ret = listen(server_fd, 5);
	if(ret == -1){
		printf("listen error!\n");
		return -1;
	}
	
	timeout.tv_sec = 5;
	timeout.tv_usec = 0;
	
	FD_ZERO(&read_fds);
	FD_SET(server_fd, &read_fds);
	FD_SET(STDIN_FILENO, &read_fds);
	
	
	if(server_fd > STDIN_FILENO){
		max_fds = server_fd +1;
	}else{
		max_fds = STDIN_FILENO + 1;
	}
		
	while(1){

		FD_ZERO(&read_fds);
		FD_SET(server_fd, &read_fds);
		FD_SET(STDIN_FILENO, &read_fds);
		FD_SET(client_fd, &read_fds);
		
		if(client_fd + 1 > max_fds)
			max_fds = client_fd + 1;
			
		ret = select(max_fds, &read_fds, NULL, NULL, NULL);
		if(ret < 0){
			printf("select error!\n");
			break;
		}else if(ret == 0){
			printf("select timeout!\n");
		}	

		if(FD_ISSET(server_fd, &read_fds)){

			client_fd = accept(server_fd, (struct sockaddr*)&client_address, &socket_len);
			if(client_fd < 0){
				printf("accept error!\n");				
			}
			printf("new client:%d\n", client_fd);
		}
		
		if(FD_ISSET(client_fd, &read_fds)){
			bzero(buf, 512);
			ret = recv(client_fd, buf, sizeof(buf), 0);
			printf("server rcv:%s", buf);
		}

		if(FD_ISSET(STDIN_FILENO, &read_fds)){
			bzero(buf, 512);
			fgets(buf, 512, stdin);
			ret = send(client_fd, buf, sizeof(buf), 0);
		}
	}
	close(server_fd);
	close(client_fd);
}

tcp_client:

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

int main(int argc, char *argv[]){

	int client_fd = 0;
	int port = 0;
	char *ip = NULL;
	int ret = 0;
	int max_fds = 0;
	
	struct sockaddr_in server_addr;
	int sock_len = sizeof(server_addr);
	fd_set read_fds;
	char buf[512] = {0};
	
	if(argc < 3){
		printf("argc error!\n");
		return - 1;
	}	
	
	port = atoi(argv[2]);
	ip = argv[1];
	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	inet_pton(AF_INET, ip, &server_addr.sin_addr);
	
	client_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(client_fd < 0){
		printf("socket error!\n");
		return -1;
	}
	
	
	ret = connect(client_fd, (struct sockaddr*)&server_addr, sock_len);
	if(ret == -1){
		printf("connect error!\n");
		return -1;
	}
	
	
	
	if(client_fd > STDIN_FILENO){
		max_fds = client_fd +1;
	}else{
		max_fds = STDIN_FILENO + 1;
	}
	while(1){
		
		FD_ZERO(&read_fds);
		FD_SET(client_fd, &read_fds);
		FD_SET(STDIN_FILENO, &read_fds);
	
		ret = select(max_fds, &read_fds, NULL, NULL, NULL);
		if(ret < 0){
			printf("select error!\n");
			break;
		}
		if(FD_ISSET(client_fd, &read_fds)){
			
			bzero(buf, 512);
			ret = recv(client_fd, buf, sizeof(buf), 0);
			
			if(ret < 0){				
				if(errno == EINTR){
					continue;
				}
				printf("read error!\n");
				break;
			}else if(ret == 0){
				printf("server is close!\n");
				break;
			}
			printf("client rcv:%s", buf);					
		}else if(FD_ISSET(STDIN_FILENO, &read_fds)){
			bzero(buf, 512);
			fgets(buf, 512, stdin);
			ret = send(client_fd, buf, sizeof(buf), 0);	
		}
	}
	
	close(client_fd);
}

3、select缺点

select函数有以下缺点:

1、select会改变传进来的fd_sets所以每次在select之前多要重新调用FD_SET把要监听的文件描述加进来,即使你监听的文件描述没有改变。

2、FD_ISSET检查监听的文件描述是采用轮询的方式,这样比较耗CPU性能,效率不高。

3、监听的文件描述数量最大是1024,除非修改linux源码重新编译可以加大监听的文件描述。

4、当描述符在select中被监听时其他的线程不能修改它。假设你有一个管理线程检测到sock1等待输入数据的时间太长需要关闭它,以便重新利用sock1来服务其他工作线程。但是它还在select的监听集合中。如果此时这个套接字被关闭会发生什么?select的man手册中有解释:如果select正在监听的套接字被其他线程关闭,结果是未定义的。

5、填充文件描述集合fd_sets时要找出值最大的,这样比较麻烦。

猜你喜欢

转载自blog.csdn.net/City_of_skey/article/details/85223592