http://blog.csdn.net/y396397735/article/details/55004775
select函數的功能和調用順序
使用select函數時統一監視多個文件描述符的:?
1、 是否存在套接字接收數據??
2、 無需阻塞傳輸數據的套接字有哪些??
3、 哪些套接字發生了異常?
select函數調用過程:?
?
由上圖知,調用select函數需要一些準備工作,調用后還需要查看結果。
設置文件描述符
select可以同時監視多個文件描述符(套接字)。?
此時需要先將文件描述符集中到一起。集中時也要按照監視項(接收,傳輸,異常)進行區分,即按照上述3種監視項分成三類。?
使用fd_set數組變量執行此項操作,該數組是存有0和1的位數組。
最左端的位表示文件描述符0(位置)。如果該位值為1,則表示該文件描述符是監視對象。?
圖上顯然監視對象為fd1和fd3。
“是否應當通過文件描述符的數字直接將值注冊到fd_set變量?”?
當然不是!操作fd_set的值由如下宏來完成:
FD_ZERO(fd_set* fdset)
: 將fd_set變量的所有位初始化為0。?
FD_SET(int fd, fd_set* fdset):
在參數fdset指向的變量中注冊文件描述符fd的信息。?
FD_CLR(int fd, fd_set* fdset):
參數fdset指向的變量中清除文件描述符fd的信息。?
FD_ISSET(int fd, fd_set* fdset):
?若參數fdset指向的變量中包含文件描述符fd的信息,則返回真。
畫圖解釋:?
設置監視范圍及超時
select函數:
#include <sys/select.h>
#include <sys/time.h>int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
其中參數和返回值:?
maxfd:監視對象文件描述符數量。?
readset: 將所有關注“是否存在待讀取數據”的文件描述符注冊到fd_set變量,并傳遞其地址值。?
writeset: 將所有關注“是否可傳輸無阻塞數據”的文件描述符注冊到fd_set變量,并傳遞其地址值。?
exceptset: 將所有關注“是否發生異常”的文件描述符注冊到fd_set變量,并傳遞其地址值。?
timeout: 調用select后,為防止陷入無限阻塞狀態,傳遞超時信息。?
返回值:錯誤返回-1,超時返回0。因關注的事件返回時,返回大于0的值,該值是發生事件的文件描述符數。
select函數用來驗證3種監視項的變化情況。根據監視項聲明3個fd_set變量,分別向其注冊文件描述符信息,并把變量的地址傳遞到函數的第二到第四個參數。但是,在調用select函數前需要決定2件事:?
“文件描述符的監視范圍是?”?
“如何設定select函數的超時時間?”
第一,文件描述符的監視范圍與第一個參數有關,實際上,select函數要求通過第一個參數傳遞監視對象文件描述符的數量。因此,需要得到注冊在fd_set變量中的文件描述符數。但每次新建文件描述符時,其值都會增1,故只需將最大的文件描述符值加1再傳遞到select函數即可。(加1是因為文件描述符的值從0開始)?
第二,超時時間與 最后一個參數有關,其中timeval結構體如下:
struct timeval
{long tv_sec;long tv_usec;
};
- 1
- 2
- 3
- 4
- 5
本來select函數只有在監視文件描述符發生變化時才返回,未發生變化會進入阻塞狀態。指定超時時間就是為了防止這種情況發生。?
將上述結構體填入時間值,然后將結構體地址值傳給select函數的最后一個參數,此時,即使文件描述符中未發生變化,只要過了指定時間,也可以從函數返回。不過這種情況下,select函數返回0。?
不想設置超時最后一個參數只需要傳遞NULL。
調用select函數后查看結果
如果select返回值大于0,說明文件描述符發生了變化。
關于文件描述符變化:文件描述符變化是指監視的文件描述符中發生了相應的監視事件。例如通過select的第二個參數傳遞的集合中存在需要讀取數據的描述符時,就意味著文件描述符發生變化。
- 1
- 2
- 3
怎樣獲知哪些文件描述符發生了變化?向select函數的第二到第四個參數傳遞的fd_set變量中將產生變化,如下圖:
select函數調用完成后,向其傳遞的fd_set變量中將發生變化。原來為1的所有位均變為0,但發生變化的文件描述符對應位除外。因此,可以認為值為1的位置上的文件描述符發生了變化。
select函數調用實例
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>#define BUF_SIZE 30int main(int argc, char* argv[])
{fd_set reads, temps;int result, str_len;char buf[BUF_SIZE];struct timeval timeout;FD_ZERO(&reads);FD_SET(0, &reads);//監視文件描述符0的變化, 即標準輸入的變化/*超時不能在此設置!因為調用select后,結構體timeval的成員tv_sec和tv_usec的值將被替換為超時前剩余時間.調用select函數前,每次都需要初始化timeval結構體變量.timeout.tv_sec = 5;timeout.tv_usec = 5000;*/while(1){/*將準備好的fd_set變量reads的內容復制到temps變量,因為調用select函數后,除了發生變化的fd對應位外,剩下的所有位都將初始化為0,為了記住初始值,必須經過這種復制過程。*/temps = reads;//設置超時timeout.tv_sec = 5;timeout.tv_usec = 0;//調用select函數. 若有控制臺輸入數據,則返回大于0的整數,如果沒有輸入數據而引發超時,返回0.result = select(1, &temps, 0, 0, &timeout);if(result == -1){perror("select() error");break;}else if(result == 0){puts("timeout");}else{//讀取數據并輸出if(FD_ISSET(0, &temps)){str_len = read(0, buf, BUF_SIZE);buf[str_len] = 0;printf("message from console: %s", buf);}}}return 0;
}程序運行結果:
/*
nihao
message from console: nihao
goodbye
message from console: goodbye
timeout
timeout
*/
select函數實現I/O復用服務端
服務端代碼:
//server.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/select.h>#define BUF_SIZE 100void error_handing(char* buf);int main(int argc, char* argv[])
{int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;struct timeval timeout;fd_set reads, cpy_reads;socklen_t adr_sz;int fd_max, str_len, fd_num, i;char buf[BUF_SIZE];if(argc != 2){printf("Usage: %s <port> \n", argv[0]);exit(1);}serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)error_handing("bind() error");if(listen(serv_sock, 5) == -1)error_handing("listen() error");FD_ZERO(&reads);FD_SET(serv_sock, &reads); //將服務端套接字注冊入fd_set,即添加了服務器端套接字為監視對象fd_max = serv_sock;while(1){cpy_reads = reads;timeout.tv_sec = 5;timeout.tv_usec = 5000;//無限循環調用select 監視可讀事件if((fd_num = select(fd_max+1, &cpy_reads, 0, 0, &timeout)) == -1){perror("select error");break;}if(fd_num == 0)continue;for(i = 0; i < fd_max + 1; i++){if(FD_ISSET(i, &cpy_reads)){/*發生狀態變化時,首先驗證服務器端套接字中是否有變化.①若是服務端套接字變化,接受連接請求。②若是新客戶端連接,注冊與客戶端連接的套接字文件描述符.*/if(i == serv_sock){adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);FD_SET(clnt_sock, &reads);if(fd_max < clnt_sock)fd_max = clnt_sock;printf("connected client: %d \n", clnt_sock);}else {str_len = read(i, buf, BUF_SIZE);if(str_len == 0) //讀取數據完畢關閉套接字{FD_CLR(i, &reads);//從reads中刪除相關信息close(i);printf("closed client: %d \n", i);}else{write(i, buf, str_len);//執行回聲服務 即echo}}}}}close(serv_sock);return 0;
}void error_handing(char* buf)
{fputs(buf, stderr);fputc('\n', stderr);exit(1);
}
客戶端代碼:
//client.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#define BUF_SIZE 1024void error_handling(char* message);int main(int argc, char* argv[])
{int sock;char message[BUF_SIZE];int str_len;struct sockaddr_in serv_adr;if(argc != 3){printf("Usage: %s <IP> <port> \n", argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);if(sock==-1)error_handling("socket error");memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(argv[1]);serv_adr.sin_port = htons(atoi(argv[2]));if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)error_handling("connect() error!");elseputs("connected....");while(1){fputs("Input message:(輸入Q退出):", stdout);fgets(message, BUF_SIZE, stdin);if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))break;write(sock, message, strlen(message));str_len = read(sock, message, BUF_SIZE-1);message[str_len] = 0;printf("Message from server: %s", message);}close(sock);return 0;
}void error_handling(char* message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
編譯執行測試:
編譯程序:
gcc server.c –o server
gcc client.c –o client啟動服務端:
yu@ubuntu:~/qtProjects/echo_selectserv$ ./server 8899啟動客戶端1:
yu@ubuntu:~/qtProjects/echo_selectserv$ ./client 127.0.0.1 8899
connected....
Input message:(輸入Q退出):你好哇2017年2月12日20:25:43
Message from server: 你好哇2017年2月12日20:25:43
Input message:(輸入Q退出):你好哇2017-02-12 20:25:52
Message from server: 你好哇2017-02-12 20:25:52
Input message:(輸入Q退出):q啟動客戶端2:
yu@ubuntu:~/qtProjects/echo_selectserv$ ./client 127.0.0.1 8899
connected....
Input message:(輸入Q退出):你好2017年2月12日20:25:11
Message from server: 你好2017年2月12日20:25:11
Input message:(輸入Q退出):你好2017年2月12日20:25:24
Message from server: 你好2017年2月12日20:25:24
Input message:(輸入Q退出):q服務端情況:
yu@ubuntu:~/qtProjects/echo_selectserv$ ./server 8899
connected client: 4
connected client: 5
closed client: 5
closed client: 4
學習自《TCP/IP網絡編程》