- 摘要:io多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般都是读就绪或者写就绪),就能通知应用程序进行相应的读写操作。select函数作为io多路复用的机制,第一个参数nfds是fd_set集合中最大描述符值+1,fdset是一个位数组,每一位代表其对应的描述符是否需要被检查。第二三四参数表示需要关注读、写、错误时间的文件描述符位数组,这些参数既是输入型参数也是输出型参数,可能会被内核修改用于标识哪些描述符上发生了关注的事件,所以每次调用select前都需要重新
-
io多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般都是读就绪或者写就绪),就能通知应用程序进行相应的读写操作。select函数作为io多路复用的机制,第一个参数nfds是fd_set集合中最大描述符值+1,fdset是一个位数组,每一位代表其对应的描述符是否需要被检查。第二三四参数表示需要关注读、写、错误时间的文件描述符位数组,这些参数既是输入型参数也是输出型参数,可能会被内核修改用于标识哪些描述符上发生了关注的事件,所以每次调用select前都需要重新初始化fdset。timeout参数为超时时间,该结构会被内核修改,其值为超时剩余时间。
select函数返回值有三种,返回-1时表示select失败;返回0表示超时;其他返回值表示select成功。
2018-12-02 18:56:42
#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[];
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 > );
int sock = socket(AF_INET,SOCK_STREAM,);
if(sock < )
{
perror("socket");
exit();
}
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)) < )
{
perror("bind");
exit();
} if(listen(sock,) < )
{
perror("listen");
exit();
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc != )
{
usage(argv[]);
return ;
}
int listen_sock = start_up(argv[],atoi(argv[]));
for(int i= ;i < ; i++)
{
rfds[i] = -;
}
fd_set rset;
int max_fd = ;
while()
{
struct timeval timeout = {,};
FD_ZERO(&;rset);
rfds[] = listen_sock;
max_fd = listen_sock;
for(int i= ;i < ; i++)
{
if(rfds[i] >= )
{
FD_SET(rfds[i],&;rset);
if(max_fd < rfds[i])
max_fd = rfds[i];
}
}
switch(select(max_fd + ,&;rset,NULL,NULL,NULL))
{
case -:
perror("select");
break;
case :
printf("timeout");
break;
default:
{
int j = ;
for(;j < ;j++)
{
if(rfds[j] < )
continue;
if(j == &;&;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 < )
{
perror("accept");
}
else
{
printf("get a client:socket :%s:%d/n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
int k = ;
for(;k < ;k++)
{
if(rfds[k] == -)
{
rfds[k] = new_fd; break;
}
}
if( k == )
{
close(new_fd);
}
}
}
else if(FD_ISSET(rfds[j],&;rset))
{
char buf[];
ssize_t s = read(rfds[j],buf,sizeof(buf)-);
if(s > )
{
buf[s] = ;
printf("client#%s/n",buf);
}
else if(s == )
{
printf("client close.../n");
close(rfds[j]); rfds[j] = -;
}
else
{
perror("read");
}
}
}
}
break;
}
}
}
select_server.c
使用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了。