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了。