1、select函数原型
int select(int maxfdp
,fd_set
*readfds
,fd_set
*writefds
,fd_set
*errorfds
,struct timeval
*timeout
);
参数解释:
maxfdp——传入参数,集合中所有文件描述符的范围,即最大文件描述符值+1
readfds——传入传出参数,select调用时传入要监听的可读文件描述符集合,select返回时传出发生可读事件的文件描述符集合
writefds——传入传出参数,select调用时传入要监听的可写文件描述符集合,select返回时传出发生可写事件的文件描述符集合
errorfds——传出参数,select返回时传出发生事件(包括可读和可写)中异常事件的文件描述符集合
timeout——传入参数,设置select阻塞的时间。若设置为NULL,则select一直阻塞直到有事件发生;
若设置为0,则select为非阻塞模式,执行后立即返回;
若设置为一个大于0的数,即select的阻塞时间,若阻塞时间内有事件发生就返回,否则时间到了立即返回
fd_set是自定义的一个数据结构,可看作一个集合,存放可读、可写或异常事件的文件描述符。fd_set集合通常有以下四个宏来操作:
void FD_ZERO(fd_set *fdset); //清空fdset中所有文件描述符
void FD_SET(int fd,fd_set *fdset); //添加文件描述符fd到集合fdset中
void FD_CLR(int fd,fd_set *fdset); //将文件描述符fd从集合fdset中去除
int FD_ISSET(int fd,fd_set *fdset); //判断文件描述符fd是否在集合fdset中
select工作原理:传入要监听的文件描述符集合(可读、可写或异常)开始监听,select处于阻塞状态,当有事件发生或设置的等待时间timeout到了就会返回,返回之前自动去除集合中无事件发生的文件描述符,返回时传出有事件发生的文件描述符集合。但select传出的集合并没有告诉用户集合中包括哪几个就绪的文件描述符,需要用户后续进行遍历操作。
2、select优缺点
优点:
(1)select的可移植性较好,可以跨平台; (2)select可设置的监听时间timeout精度更好,可精确到微秒,而poll为毫秒。
缺点:
(1)select支持的文件描述符数量上限为1024,不能根据用户需求进行更改; (2)select每次调用时都要将文件描述符集合从用户态拷贝到内核态,开销较大; (3)select返回的就绪文件描述符集合,需要用户循环遍历所监听的所有文件描述符是否在该集合中,当监听描述符数量很大时效率较低。
3、select使用经典案例
用select函数编写一个简单的高并发服务器,且假设服务器启动时处于无连接状态,满足以下功能: a)可处理来自一个新客户端的连接请求; b)监听可读事件,若已连接客户端的已连接描述符发生可读事件,服务器从客户端读取数据并处理;
服务器端代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>
#define SERV_PORT 6666
int main()
{
int i
,j
,n
,maxi
;
int maxfd
,listenfd
,connfd
,sockfd
;
int nready
,client
[FD_SETSIZE
-1];
char buf
[BUFSIZ
], str
;
struct sockaddr_in clie_addr
,serv_addr
;
socklen_t clie_addr_len
;
fd_set allset
,readset
;
bzero(&serv_addr
,sizeof(serv_addr
));
serv_addr
.sin_family
=AF_INET
;
serv_addr
.sin_port
=htons(SERV_PORT
):
serv_addr
.sin_addr
.s_addr
=htonl(INADDR_ANY
);
listenfd
=socket(AF_INET
,SOCK_STREAM
,0);
bind(listenfd
,(struct sockaddr
*)&serv_addr
,sizeof(serv_addr
));
listen(listenfd
,1024);
maxfd
=listenfd
;
int maxi
=-1;
for (i
=0;i
<FD_SETSIZE
;i
++)
client
[i
]=-1
FD_ZERO(&allset
);
FD_SET(listenfd
,&allset
);
while(1)
{
readset
=allset
;
nready
=select(maxfd
+1,&readset
,NULL,NULL,NULL);
if (nready
<0)
perr_exit("select error");
if (FD_ISSET(listenfd
,&readset
))
{
clie_addr_len
=sizeof(clie_addr
);
connfd
=accept(listenfd
,(struct sockaddr
*)&clie_addr
,&clie_addr_len
);
printf(“received from
%s at port
%d\n”
,
inet_ntop(AF_INET
,&clie_addr
.sin_addr
.s_addr
,&str
,sizeof(str
)),
ntohs(clie_add
.sin_port
));
for (i
=0;i
<FD_SETSIZE
;i
++)
{
if (client
[i
]<0)
{
client
[i
]=connfd
;
break;
}
}
if (i
== FD_SIZE
-1)
{
fputs("too many clients\n",stderr);
exit(1);
}
FD_SET(connfd
,&allset
);
if (connfd
>maxfd
)
maxfd
=connfd
;
if(i
>maxi
)
maxi
=i
;
--nready
;
if (nready
==0)
continue;
}
for (i
=0;i
<=maxi
;i
++)
{
sockfd
=client
[i
];
if (sockfd
<0)
continue;
if (FD_ISSET(sockfd
,&readset
))
{
n
=read(sockfd
,buf
,sizeof(buf
));
if (n
==0)
{
colse(sockfd
);
FD_CLR(sockfd
,&allset
);
client
[i
]=-1;
}
else if (n
>0)
{
for (j
=0;j
<n
;j
++)
buf
[j
]=toupper(buf
[j
]);
sleep(2);
write(sockfd
,buf
,n
);
}
--nready;
if (nready
==0)
break;
}
}
}
close(listenfd
);
return 0;
}
客户端代码
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666
int main()
{
int cfd
;
struct sockaddr_in serv_addr
;
char buf
[BUFSIZ
];
int n
;
cfd
=socket(AF_INET
,SOCK_STREAM
,0);
memset(&serv_addr
,0,sizeof(serv_addr
));
serv_addr
.sin_family
=AF_INET
;
serv_addr
.sin_port
=htons(SERV_PORT
);
inet_pton(AF_INET
,SERV_IP
,&serv_addr
.sin_addr
.s_addr
);
connect(cfd
,(struct sockaddr
*)&serv_addr
,sizeof(serv_addr
));
while(1)
{
fgets(buf
,sizeof(buf
),stdin);
write(cfd
,buf
,strlen(buf
));
n
=Read(cfd
,buf
,sizeof(buf
));
write(STDOUT_FILENO
,buf
,n
);
}
close(cfd
);
return 0;
}
将服务端、客户端生成可执行文件之后,先启动服务器,再启动客户端与服务器建立连接,用户输入字符,服务器将读取到的字符转换为大写,再写回客户端的屏幕上,测试结果如下所示: 服务器会显示客户端的IP地址以及端口号,可发现客户端与服务器来自同一台主机,这是没有问题的 客户端会显示用户输入的字符串以及转为大写后写回的字符串,如图: 注意:数据传输完毕后,一定要客户端先断开连接,避免服务器出现TIME_WAIT状态,从而占用服务器资源。