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