select函数必需的头文件:<sys/select.h>和<sys/time.h>。这个函数允许进程指示内核等待多个事件(任何描述符字准备好读或写或有异常条件待处理)中的任一个发生,并仅在事件发生或经过某指定的时间后才唤醒进程。所以调用select的进程会在这个函数阻塞,等待事件发生或者超时。
函数原型:select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
传递给select函数的参数会告诉内核:我们所关心的文件描述符;对每个描述符我们所关心的状态;我们要等待多长时间。
从select返回后,内核告诉我们:对我们的要求,已经准备好的描述符个数。
参数说明:
第一个参数int型,指定select中监视的文件句柄数。
三个fd_set参数是类似的,分别代表监视的读、写、异常文件句柄集合。对应的监视集合中有对应的事件发生,则select返回正数,若没有事件发生并超时,select返回0,若发生错误则返回0。struct fd_set是一个存放文件描述符的集合,实际上是一个long类型的数组,每一个数组元素都能与一打开的文件句柄建立联系,但建立联系的工作要由我们手动完成,FD_SET(int fd, fd_set *fdset)建立文件句柄fd与fdset的联系,同理有FD_CLR、FD_ISSET还有FD_ZERO。
timeout参数指定超时时间,若传入NULL(即不传入时间结构),select就是阻塞状态直到有事件返回;若设置为0秒0毫秒(不是NULL),select就是非阻塞函数,不管文件描述符是否有变化都立即返回(non-blocking);若时间大于0,则select在timeout时间内阻塞,期间有事件到来就返回,否则在超时后返回。
简单的使用流程:
1)套接字句柄-socket描述符
假设服务端有一个listenfd,用于侦听客户端连接
int listenfd = socket(...);
然后bind和listen
2)将描述符加入到读(写、异常)监视集合中
FD_ZERO(&rdfds);
FD_SET(listenfd, &rdfds);
3)设置超时
struct timeval tv;
tv.tv_sec = ...;
tv.tv_usec = ...;
4)调用select
一般情况下只用到读事件
ret = select(maxfd+1, &rdfds, NULL, NULL, &tv);
if (ret < 0)
perror("select error");
else if (ret == 0)
printf("超时\n");
else
{
//如果listenfd这个被监视的描述符变成可读
if (FD_ISSET(listenfd, &rdfds))
{
//说明有新的client连接,将新的连接的socket描述符加入到读监视集合中
connfd = accept(...);
FD_SET(connfd, &allset);
//还需要保存这个客户端socket到client队列中,用于后续的通信
}
//遍历client队列,查看是哪个socket读就绪
for(...)
{
...
FD_ISSET(..)
//如果某个客户端socket退出,则清除对它的监视
FD_CLR(...)
}
}
服务端使用select的源程序:
//:selectServer.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#define BUF_LEN 1024
#define SERV_PORT 6000
#define FD_SIZE 100
#define MAX_BACK 100
int main(int argc, char **argv)
{
int listenfd, connfd, sockfd, maxfd, maxi, i;
int nready, client[FD_SIZE]; //接收select返回值,保存客户端套接字
int lens;
size_t n; //read字节数
//fd_set的数据结构,实际上是一long类型的数组,
//每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系
fd_set rset, allset;
char buf[BUF_LEN];
socklen_t clilen;
struct sockaddr_in servaddr, cliaddr;
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("Create socket Error: %d\n", errno);
exit(EXIT_FAILURE);
}
//bzero是传统BSD函数,属于POSIX标准,使用头文件string.h,bzero无返回值,推荐使用memset替代bzero
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) == -1)
{
printf("Bind Error: %d\n", errno);
exit(EXIT_FAILURE);
}
if (listen(listenfd, MAX_BACK) == -1)
{
printf("Listen Error: %d\n", errno);
exit(EXIT_FAILURE);
}
maxfd = listenfd;
maxi = -1;
for(i = 0; i < FD_SIZE; i ++)
{
client[i] = -1;
}
FD_ZERO(&allset);
//把服务端侦听的listenfd放到allset中
FD_SET(listenfd, &allset);
while(1)
{
rset = allset;
//select(最大句柄数+1, 监视的文件句柄集合, 写, 异常, 超时)
if ((nready = select(maxfd + 1, &rset, NULL, NULL, NULL)) == -1)
{
printf("Select Error: %d\n", errno);
exit(EXIT_FAILURE);
}
if (nready <= 0)
{
continue;
}
//如果是侦听的listenfd就绪,则说明是有新的连接进入
if(FD_ISSET(listenfd, &rset))
{
clilen = sizeof(cliaddr);
printf("Start doing...\n");
//新连接
if (( ) == -1)
{
printf("Accept Error: %d\n", errno);
continue;
}
for (i = 0; i < FD_SIZE; i ++)
{
//保存这个新的client fd
if (client[i] < 0)
{
client[i] = connfd;
break;
}
}
if (i == FD_SIZE)
{
printf("To many clients...Reject\n");
close(connfd);
continue;
}
FD_SET(connfd, &allset);
if (connfd > maxfd)
{
maxfd = connfd;
}
if (i > maxi)
{
maxi = i;
}
}
//否则轮训一遍,看看是哪个客户端socket的读就绪
for (i = 0; i <= maxi; i ++)
{
if ((sockfd = client[i]) > 0)
{
if (FD_ISSET(sockfd, &rset))
{
memset(buf, 0, sizeof(buf));
n = read(sockfd, buf, BUF_LEN);
if (n < 0)
{
printf("Error!\n");
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
continue;
}
if (n == 0)
{
printf("No data\n");
//close(sockfd);
//FD_CLR(sockfd, &allset);
//client[i] = -1;
continue;
}
printf("Server Recv: %s\n", buf);
if (strcmp(buf, "q") == 0)
{
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
continue;
}
printf("Server send: %s\n", buf);
write(sockfd, buf, n);
}
}
}
}
return 0;
}
简单的客户端程序:
//:selectClient.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define BUF_LEN 1024
#define SERVER_PORT 6000
int main(int argc, char const **argv)
{
int connfd;
int message_len;
struct sockaddr_in remote_addr;
char buf[BUF_LEN];
memset(&remote_addr, 0, sizeof(remote_addr));
memset(buf, 0, BUF_LEN);
remote_addr.sin_family = AF_INET;
remote_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
remote_addr.sin_port = htons(SERVER_PORT);
connfd = socket(AF_INET, SOCK_STREAM, 0);
if (connfd < 0)
{
//perror和strerror都是C语言提供的库函数,用于获取与errno相关的错误信息
//不同的是perror向stderr输出结果,strerror(errno)向stdout输出结果
perror("socket error!");
return -1;
}
if (connect(connfd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) < 0)
{
perror("connect error!");
return -1;
}
printf("Connected! You can send message.\n");
while(1)
{
gets(buf);
if (strcmp(buf, "q") == 0)
{
write(connfd, buf, message_len);
printf("Exit.\n");
break;
}
if ((message_len = strlen(buf)) > 0)
{
write(connfd, buf, message_len);
memset(buf, 0, BUF_LEN);
message_len = read(connfd, buf, BUF_LEN);
if (message_len > 0)
{
printf("Receive from server: %s\n", buf);
}
}
}
//客户端主动断开连接
close(connfd);
return 0;
}
参考资料:
http://www.vimer.cn/2009/10/select%E5%87%BD%E6%95%B0%E6%80%BB%E7%BB%93.html
http://blog.sina.com.cn/s/blog_6dc9e4cf0100ycuw.html