一、select()函數功能
The select system call allows a program to wait for input to arrive (or output to complete) on a number
of low-level file descriptors at once.
select系統調用允許程序同時在多個底層文件表述符上,等待輸入的到達或輸出的完成。
二、函數意義
A server can deal with multiple clients by waiting for a request on
many open sockets at the same time.
一個服務器可以同時在多個打開的套接字等待請求到來的方法而處理多個客戶。(只是具體應用的其中之一)
自己的理解,但凡是程序在執行過程中會遇到阻塞(不到條件發生就不往下執行)的情況,都是很浪費CPU資源的。因爲程序佔用了CPU的時間,卻在等待一個事件的發生,乾等着不幹活,佔着茅坑不拉屎……但是有了select()函數,就可以用一個進程同時監視很多個文件描述符到輸入、輸出、錯誤。select函數返回後,通過FD_ISSET()函數檢測是哪個文件描述符狀態發生了變化,在進行相應的操作,從而省去了對這麼文件描述符的等待操作,提高了CPU的利用率。
三、相關的函數原型
#include <sys/types.h>
#include <sys/time.h>
void FD_ZERO(fd_set *fdset); 將fdset集合清零(初始化)
void FD_CLR(int fd, fd_set *fdset); 清除fd文件描述符
void FD_SET(int fd, fd_set *fdset); 添加fd文件描述符
int FD_ISSET(int fd, fd_set *fdset); 檢測fd是否爲fdset文件描述符集合中的元素,是返回非0值,否返回0;
其中:數據結構“fd_set”是由打開的文件描述符構成的集合。上面的四個函數用來控制這個集合。
#include <sys/types.h>
#include <sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
參數:
nfds:要測試的描述符範圍,0~(nfds-1)
返回值:
1.readfds集合中有描述符可讀,writefds集合中有描述符可寫,errorfds集合中有描述符遇到錯誤條件時select將返回1,失敗返回-1;
2.如果三種情況都沒有發生,select將在到達timeout超時時間後,返回0;
3.如果timeout爲0,則select將一直阻塞,直到有情況發生。(之前說但凡是阻塞就是浪費CPU資源,但是此處的阻塞,也算是犧牲小我,成就大我了,因爲一個阻塞同時監控着所有文件描述符的變化,跟每個描述符都阻塞相比,效率還是高高的……)
四、實例
例程1.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <fcntl.h>
#include <sys/ioctl.h>
int main(void)
{
char buffer[128];
int result, nread;
fd_set inputs, testfds;
struct timeval timeout;
FD_ZERO(&inputs);
FD_SET(0,&inputs);
while(1)
{
testfds = inputs;
timeout.tv_sec = 2;
timeout.tv_usec = 500000;
result = select(FD_SETSIZE, &inputs, (fd_set *)NULL, (fd_set *)NULL, &timeout);
switch(result)
{
case 0:printf("timeout\n");break;
case -1:perror("select");exit(1);
default:
if(FD_ISSET(0,&inputs))
{
ioctl(0,FIONREAD,&nread);
if(nread == 0)
{
printf("keyboard done\n");
exit(0);
}
nread = read(0,buffer,nread);
buffer[nread] = 0;
printf("read %d from keyboard: %s", nread, buffer);
}
break;
}
}
}
這個程序讀取鍵盤輸入(標準輸入-文件描述符爲0),超時時間設爲2.5s例程2:
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/times.h>
#include <sys/ioctl.h>
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int res;
fd_set readfds, testfds;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr("192.168.0.21");
server_address.sin_port = htons(8080);
server_len = sizeof(server_address);
int on=1;
if((setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)
{
perror("set sock opt");
exit(1);
}
bind(server_sockfd, (struct sockaddr*)&server_address, server_len);
listen(server_sockfd, 5);
FD_ZERO(&readfds);
FD_SET(server_sockfd, &readfds);
while(1)
{
char ch;
int fd;
int nread;
testfds = readfds;
printf("server is waiting!\n");
res = select(FD_SETSIZE, &testfds, (fd_set*)0, (fd_set*)0, (struct timeval*)0);
printf("res=%d\n",res);
if(res < 0)
{
perror("server");
exit(1);
}
for(fd = 0; fd<FD_SETSIZE;fd++)
{
if(FD_ISSET(fd, &testfds))
{
if(fd == server_sockfd)
{
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_address, &client_len);
FD_SET(client_sockfd, &readfds);
printf("adding client on fd %d\n",client_sockfd);
}
else
{
ioctl(fd, FIONREAD, &nread);
if(nread == 0)
{
close(fd);
FD_CLR(fd, &readfds);
printf("removing client on fd %d\n", fd);
}
else
{
read(fd, &ch ,1);
// sleep(5);
printf("serving client on fd %d\n", fd);
ch++;
write(fd, &ch, 1);
}
}
}
}
}
}
client.c/* Make the necessary includes and set up the variables. */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int sockfd;
int len;
struct sockaddr_in address;
int result;
char ch = 'A';
/* Create a socket for the client. */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
/* Name the socket, as agreed with the server. */
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("192.168.0.21");
address.sin_port = htons(8080);
len = sizeof(address);
/* Now connect our socket to the server's socket. */
result = connect(sockfd, (struct sockaddr *)&address, len);
if(result == -1) {
perror("oops: client3");
exit(1);
}
/* We can now read/write via sockfd. */
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(sockfd);
exit(0);
}
此程序用select系統調用實現了服務器服務於多客戶,沒有采用多進程。服務器可以讓select同時檢查 監聽套接字 和 客戶端的連接套接字。一旦select指示有活動發生,我們便可以用FD_ISSET()函數來遍歷所有可能的文件描述符,以檢查是哪個發生了活動。
如果是監聽套接字可讀,說明正有一個客戶嘗試連接,此時可以用accept接受連接請求,而不會阻塞;如果是某個客戶連接套接字準備好,說明這個套接字的客戶有情況需要我們處理,這時調用ioctl(),如果返回0,則客戶進程已經結束,關閉該套接字並把它從描述符集合中刪除即可。