select函数
函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
man page 中对select函数的说明如下:
select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform the corresponding I/O operation (e.g., read(2)) without blocking.
也就是说,select函数可以监控多个文件描述符,等待直到其中一个或多个文件可以进行读取或写入操作而不被阻塞时,才返回。
参数:
nfds: 要监视的fd最大值加1,设置select监视的文件描述符范围可以提高效率
readfds: 要进行读取操作的文件描述符集
writefds: 要进行写入操作的文件描述符集
exceptfds: 要监视异常的文件描述符集
timeout: 使用struct timeval描述的超时时间,设为NULL时,表示永远等待
返回值:
返回-1表示出错
返回0表示没有文件描述符准备好
返回正值表示已经准备好的描述符个数
fd_set:
fd_set用来描述一个文件描述符集。可以使用以下四个接口对它进行操作。
#include
void FD_ZERO(fd_set, *fdset);
void FD_CLR(int fd, fd_set, *fdset);
void FD_SET(int fd, fd_set, *fdset);
int FD_ISSET(int fd, fd_set, *fdset);
FD_ZERO 用来清空一个fdset(清除所有位)
FD_CLR 用来清除fdset中指定位
FD_SET 用来设置fdset中指定位
FD_ISSET 测试指定位是否设置,已设置返回非零,否则返回0
3个fd_set指针可以为NULL,表示不关心相应的文件集。
函数在调用select前,应该先使用FD_ZERO清空每个要使用的fd_set,然后使用FD_SET设置关心的文件描述符。在select返回后使用FD_ISSET检查每个关心的位是否仍旧设置。
select函数在实现并发服务器时是很好用的,这时我们可以每建立一个连接都加入fdset中进行监视,当任意一个连接可读时再去读取数据然后处理,这样可以很简单地实现并发。下面是一个使用select实现的TCP并发服务器的例子:
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// tcp-server.c
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_MESSAGE_LEN 128
#define MAX_CONNECTION_NUM 16
typedef struct
{
int sock;
struct sockaddr_in client_addr;
void * prvivate; //连接私有数据,用来存放连接上下文等
} conn_t;
static conn_t conn[MAX_CONNECTION_NUM] ;
int proc_msg(int conn_hd, char *buf, int len);
//增加一个连接
//初始化conn_t数据结构,并将socket加入监视的文件描述符集
static int add_conn(int sck, struct sockaddr_in *client_addr)
{
int i;
for(i=0; i
if(conn[i].sock == -1)
{
#if 0
//为连接私有数据申请空间
conn[i].private = malloc(...);
if(conn[i].private == NULL)
{
ERR_PRINT("Alloc memmory failed.\n");
return -1;
}
memset(conn[i].private, 0, ...);
#endif
if( (sck+1) > fdmax)
{
fdmax = sck+1;
}
conn[i].sock = sck;
FD_SET(sck, &fdset_rd);
memcpy(&conn[i].client_addr, client_addr, sizeof(struct sockaddr_in));
return i;
}
}
return -1;
}
//删除一个连接
//从监视的文件描述符集中删除socket
//并销毁conn_t
static int del_conn(int hd)
{
int max=0;
int i;
if(hd >= MAX_CONNECTION_NUM)
return -1;
if(conn[hd].sock == -1)
return -1;
FD_CLR(conn[hd].sock, &fdset_rd);
close(conn[hd].sock);
conn[hd].sock = -1;
if(fdmax == conn[hd].sock+1)
{
for(i=0;i
if( (conn[i].sock != -1) && ((conn[i].sock+1)>max) )
max = conn[i].sock+1;
}
fdmax = max;
}
#if 0
//释放申请的空间
if(conn[i].private != NULL)
{
p = conn[i].private;
conn[i].private = NULL;
free(conn[i].private);
}
#endif
return 0;
}
int main(int argc, char* argv[])
{
struct sockaddr_in sock_addr, client_addr;
int sockfd, clientfd;
int i;
fd_set rdset;
struct timeval tv;
int len,ret;
char buff[MAX_MESSAGE_LEN+1] = {0};
memset(conn, 0, sizeof(conn));
for(i=0; i
conn[i].sock = -1;
}
//建立socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
ERR_PRINT("Open socket error.\n");
return -1;
}
//设为非阻塞
if( fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1)
{
DBG_PRINT("Set server socket nonblock failed\n");
}
bzero(&sock_addr, sizeof(sock_addr));
sock_addr.sin_family = AF_INET;
sock_addr.sin_port = htons(IOSERVER_TCP_PORT);
sock_addr.sin_addr.s_addr = htons(INADDR_ANY);
bzero(&(sock_addr.sin_zero), 8);
//绑定端口
if(bind(sockfd, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) != 0)
{
ERR_PRINT("TCP port binding faild. %s\n", strerror(errno));
return -1;
}
//开始监听
ret = listen(sockfd, MAX_CONNECTION_NUM);
if(ret)
{
ERR_PRINT("TCP server listen faild. %s\n", strerror(errno));
return -1;
}
//初始将服务器监听socket加入监视文件描述符集
FD_ZERO(&fdset_rd);
FD_SET(sockfd, &fdset_rd);
fdmax = sockfd+1;
while(1)
{
//每次调用select前都需要重新设置
memcpy(&rdset, &fdset_rd, sizeof(fd_set));
tv.tv_sec = 0;
tv.tv_usec = 0;
ret = select(fdmax, &rdset, NULL, NULL, &tv);
switch(ret)
{
case -1:
ERR_PRINT("select returned %d\n", ret);
break;
case 0:
break;
default:
//建立一个连接
if(FD_ISSET(sockfd, &rdset))
{
len = sizeof(client_addr);
clientfd = accept(sockfd, (struct sockaddr*)&client_addr, (socklen_t*)&len);
if(clientfd > 0)
{
DBG_PRINT("get one connection\n");
if(add_conn(clientfd, &client_addr) == -1)
{
ERR_PRINT("Add connetion failed,close this connection\n");
close(clientfd);
}
}
}
//扫描所有连接,并从准备好的连接接收数据并处理
for(i=0; i
if(conn[i].sock != -1)
{
memset(buff, 0, sizeof(buff));
if(FD_ISSET(conn[i].sock, &rdset))
{
len = recv(conn[i].sock, buff, MAX_MESSAGE_LEN,0);
if(len > 0)
{
porc_msg(&conn[i], buff, len);
}
}
}
}
break;
}
}
close(sockfd);
return 0;
}