1. 程式人生 > >linux I/O複用select

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時要找出值最大的,這樣比較麻煩。