io多路複用是通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般都是讀就緒或者寫就緒),就能通知應用程式進行相應的讀寫操作。select函數作為io多路複用的機制,第一個參數nfds是fd_set集合中最大描述符值+1,fdset是一個位數組,每一位代表其對應的描述符是否需要被檢查。第二三四參數表示需要關注讀、寫、錯誤時間的檔案描述符位數組,這些參數既是輸入型參數也是輸出型參數,可能會被核心修改用于辨別哪些描述符上發生了關注的事件,是以每次調用select前都需要重新初始化fdset。timeout參數為逾時時間,該結構會被核心修改,其值為逾時剩餘時間。
select函數傳回值有三種,傳回-1時表示select失敗;傳回0表示逾時;其他傳回值表示select成功。
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
int rfds[128];
void usage(const char* proc)
{
assert(proc);
printf("usage:%s: [ip] [port]\n",proc);
}
int start_up(const char* ip,int port)
{
assert(ip);
assert(port > 0);
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
perror("bind");
exit(3);
}
if(listen(sock,5) < 0)
{
perror("listen");
exit(4);
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
usage(argv[0]);
return 1;
}
int listen_sock = start_up(argv[1],atoi(argv[2]));
int i = 0;
for(;i < 128; i++)
{
rfds[i] = -1;
}
fd_set rset;
int max_fd = 0;
while(1)
{
struct timeval timeout = {0,0};
FD_ZERO(&rset);
rfds[0] = listen_sock;
max_fd = listen_sock;
for(i = 0;i < 128; i++)
{
if(rfds[i] >= 0)
{
FD_SET(rfds[i],&rset);
if(max_fd < rfds[i])
max_fd = rfds[i];
}
}
switch(select(max_fd + 1,&rset,NULL,NULL,NULL))
{
case -1:
perror("select");
break;
case 0:
printf("timeout");
break;
default:
{
int j = 0;
for(;j < 128;j++)
{
if(rfds[j] < 0)
continue;
if(j == 0&&FD_ISSET(rfds[j],&rset))
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_fd = accept(listen_sock,(struct sockaddr*)&client,&len);
if(new_fd < 0)
{
perror("accept");
}
else
{
printf("get a client:socket :%s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
int k = 0;
for(;k < 128;k++)
{
if(rfds[k] == -1)
{
rfds[k] = new_fd;
break;
}
}
if( k == 128)
{
close(new_fd);
}
}
}
else if(FD_ISSET(rfds[j],&rset))
{
char buf[1024];
ssize_t s = read(rfds[j],buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("client#%s\n",buf);
}
else if(s == 0)
{
printf("client close...\n");
close(rfds[j]);
rfds[j] = -1;
}
else
{
perror("read");
}
}
}
}
break;
}
}
}
使用telnet為一個用戶端通路:

當把伺服器終止後,再次打開伺服器會出現這種情況:
這是因為,雖然server的應用程式終止了,但tcp協定層的連接配接沒有完全斷開,是以不能再次監聽同樣的server端口。
client終止時自動關閉socket描述符,server的TCP連接配接收到client發的FIN段後處于TIME_WAIT狀态。TCP協定規定,主動關閉連接配接的一方要處于TIME_WAIT狀态,等待兩個MSL(maximum segment lifetime)的時間後才能回到CLOSED狀态,因為我們先Ctrl-C終止了server,是以server是主動關閉連接配接的一方,在TIME_WAIT期間仍然不能再次監聽同樣的server端口。MSL在RFC1122中規定為兩分鐘,但是各作業系統的實作不同,在Linux上一般經過半分鐘後 就可以再次啟動server了。