I/O函式複用 -- select
阿新 • • 發佈:2018-11-25
select系統呼叫的用途是:在一段指定時間內,監聽使用者感興趣的檔案描述符上的可讀、可寫和異常事件。
select API
select函式原型如下:
# include<stdio.h> int select(int nfds, fd_set* readfds, fd_set* wtitefds, fd_set* exceptfds, struct timeval* timeout); //nfd引數指定被監聽的檔案描述符的總數。它通常被設定為select監聽的所有檔案描述符中最大的值加一,因為檔案描述符是從0開始的。 //reafds、writefds和exceptfds引數分別指向可讀、可寫和異常等事件對應的檔案描述符結合。 //應用程式呼叫select時,通過這3個引數傳入自己感興趣的檔案描述符。select呼叫返回時,核心將修改它們來通知應用程式哪些檔案描述符已經就緒。 //這3個引數時fd_set結構體指標型別。fd_set結構體僅包含一個數組,該陣列的每個元素的每一位標記一個檔案描述符。 //fd_set能容納的檔案描述符數量由FD_SETSIZE指定,這就限制了select能同時處理的檔案描述符的總量。 //timeout引數用來設定select函式的超時時間。
由於位操作過於繁瑣,我們通常使用下面的一系列巨集來訪問fd_set結構體中的位:
# include<sys/select.h>
FD_ZERO(fd_set* fdset);//清楚fd_set中所有位
FD_SET(int fd, fd_set *fdset);//設定fdset的位fd
FD_CLR(int fd, fd_set *fdset);//清楚fdset的位fd
int FD_ISSET(int fd, fd_set *fdset);//測試fdset的位fd是否被設定
檔案描述符就緒的條件
哪些情況下檔案描述符可以被認為是可讀、可寫或出現異常,對於select的使用非常關鍵。在網路程式設計中:
下列情況下socket可讀:
- socket核心接收緩衝區中的位元組數大於或等於其低水位標記SO_RCVLOWAT。此時我們可以無阻塞地讀該socket,並且讀操作返回的位元組數大於0。
- socket通訊的對方關閉連線。此時隊該socket的讀操作將返回0。
- 監聽套接字上有新的連線請求。
- socket上有未處理的錯誤。此時我們可以使用getsockopt來讀取和清除該錯誤。
下列情況下socket可寫:
- socke核心傳送快取區中的可用位元組數大於或等於其低水位標記SO_SNDLOWAT。此時我們可以無阻塞地寫該socket,並且寫操作返回地位元組數大於0。
- socket的寫操作被關閉。對寫操作被關閉的soacket執行寫操作將觸發一個SIGPIPE訊號。
- socket使用非阻塞connect連線成功或者失敗之後。
- socket上有未處理的錯誤。此時我們可以使用getsockopt來讀取和清楚該錯誤。
程式碼清單
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include<unistd.h>
# include<arpa/inet.h>
# include<sys/socket.h>
# include<string.h>
# include<arpa/inet.h>
# include<sys/select.h>
# define MAXFD 10
//初始化fds陣列
void fds_init(int *fds)
{
int i = 0;
for(; i < MAXFD; ++i)
{
fds[i] = -1;
}
}
//新增套接字描述符到fds陣列
void fds_add(int *fds, int fd)
{
int i = 0;
for(; i < MAXFD; ++i)
{
if(fds[i] == -1)
{
fds[i] = fd;
break;
}
}
}
//刪除指定套接字描述符
void fds_del(int *fds, int fd)
{
int i = 0;
for(; i < MAXFD; ++i)
{
if(fds[i] == fd)
{
fds[i] = -1;
break;
}
}
}
int main()
{
//建立套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(sockfd != -1);
struct sockaddr_in saddr, caddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//命名套接字
int res = bind(sockfd, (struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
//監聽套接字
listen(sockfd, 5);
//定義陣列
int fds[MAXFD];
fds_init(fds);
//將定義好的套接字描述符新增到陣列中
fds_add(fds, sockfd);
while(1)
{
fd_set fdset;
FD_ZERO(&fdset);
int maxfd = -1;
int i = 0;
for(; i < MAXFD; i++)
{
if(fds[i] == -1)
{
continue;
}
FD_SET(fds[i], &fdset);
if(fds[i] > maxfd)
{
maxfd = fds[i];
}
}
struct timeval tv = {5, 0};
int n = select(maxfd + 1, &fdset, NULL, NULL, &tv);
if(n == -1)
{
printf("select error\n");
continue;
}
else if(n == 0)
{
printf("time out\n");
continue;
}
else
{
int i = 0;
for(; i < MAXFD; i++)
{
if(fds[i] == -1)
{
continue;
}
if(FD_ISSET(fds[i], &fdset))
{
if(fds[i] == sockfd)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr, &len);
if(c < 0)
{
continue;
}
printf("accept c = %d\n", c);
fds_add(fds, c);
}
else
{
char buff[128] = {0};
int num = recv(fds[i], buff, 127, 0);
if(num <= 0)
{
close(fds[i]);
fds_del(fds,fds[i]);
printf("one client close\n");
}
else
{
printf("recv(%d)=%s\n", fds[i], buff);
send(fds[i], "ok", 2, 0);
}
}
}
}
}
}
}