一、select函数
1.1、select函数的作用
- select函数允许程序监听多个文件描述符,直到其中一个文件描述符处于就绪状态(有数据可读或可写)。
函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
头文件: #include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
- nfds表示最大文件描述符的值加一,
- readfds表示读文件描述符的集合,
- readfds表示写文件描述符的集合
- exceptfds表示监视的异常文件句柄集合,
- timeout表示超时时间,如果为NULL表示永久等待;
需要注意的是,select函数会逐渐减少timeout的值以记录剩余时间,因此每次调用select函数需要重新初始化超时时间。
- 返回值
成功返回对应的文件描述符,超时返回0,失败返回-1。 - 注意事项
在调用select函数之后,select会将文件描述符集合中没有数据到来的文件描述符清除掉,只保留有数据的文件描述符在集合中,因此每次调用select之前必须重新调用FD_CLR和FD_SET重新对文件描述符集合进行初始化。
1.2、select函数需要和以下函数组合使用
- FD_SET() 将文件描述符fd放到文件描述符集合set中
- FD_ISSET() 判断指定的文件描述符fd是否存在于set中
- FD_CLR()将文件描述符fd从件描述符集合set中清除
- FD_ZERO()初始化文件描述符集合,在文件描述符集合创建之后,其初始值是未知的,必须进行初始化
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
二、使用select创建TCP服务器,监听多个客户端
#include <unistd.h>
#include <pthread.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
#define MAX_CLIENT_FD 100
int g_client_fd[MAX_CLIENT_FD] = {-1};
#define SERVER_PORT 8089
#define MAX_LISTEN_CLIENT_NUM 10
pthread_t g_server_tid;
pthread_t g_client_tid;
void *tcp_server(void *param)
{
int ret = -1;
int i=0;
int sockfd = -1;
int select_fd = -1;
int client_fd = -1;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
struct hostent *ip;
char buffer[1024];
struct timeval time_out;
fd_set server_fd_set;
int max_fd = 0;
FD_ZERO(&server_fd_set);
memset(&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET; //使用IPV4
server_addr.sin_port = htons(SERVER_PORT);//服务器端口号
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//主机可能有多个网卡,INADDR_ANY表示绑定所有网卡
sockfd = socket(AF_INET,SOCK_STREAM,0);//创建TCP,指定ip类型为IPV4
printf("create socket %d\r\n",sockfd);
if(sockfd<0)
{
printf("socket create error\r\n");
}
time_out.tv_sec = 60;
time_out.tv_usec = 0;
#if 0
if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &recv_time, sizeof(recv_time))<0)
{
printf("failed to set socket receiving timeout!\r\n");
}
#endif
ret = bind(sockfd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));//将socket和服务器地址绑定
if(0 != ret)
{
printf("server bind error\r\n");
goto exit;
}
ret = listen(sockfd,MAX_LISTEN_CLIENT_NUM);//开启监听服务
if(0 != ret)
{
printf("listen error\r\n");
goto exit;
}
printf("create server sucess!\r\n");
socklen_t addr_len = sizeof(struct sockaddr);
max_fd = sockfd;
while(1)
{
max_fd = client_fd > sockfd?client_fd:sockfd;
time_out.tv_sec = 60;//select会更改timeout的值,所以需要重新初始化超时时间
time_out.tv_usec = 0;
FD_ZERO(&server_fd_set);
FD_SET(sockfd,&server_fd_set);
for(i=0;i<MAX_CLIENT_FD;i++)
{
if(g_client_fd[i]>0)
{
FD_SET(g_client_fd[i],&server_fd_set);
}
}
printf("maxfd:%d\r\n",max_fd);
select_fd = select(max_fd+1,&server_fd_set,NULL,NULL,&time_out);
printf("select fd:%d\r\n",select_fd);
switch(select_fd)
{
case 0:printf("select timeout\r\n");break;
case -1:printf("error occured\r\n");break;
default:
{
if(FD_ISSET(sockfd,&server_fd_set))
{
client_fd = accept(sockfd,(struct sockaddr*)&client_addr,&addr_len);
if(client_fd >= 0)
{
printf("new client fd = %d\r\n",client_fd);
for(i=0;i<MAX_CLIENT_FD;i++)
{
if(g_client_fd[i] == -1)
{
g_client_fd[i] = client_fd;
break;
}
}
}
else
{
printf("accept error\r\n");
}
}
else
{
for(i=0;i<MAX_CLIENT_FD;i++)
{
if(FD_ISSET(g_client_fd[i],&server_fd_set))
{
memset(buffer,0,sizeof(buffer));
ret = recv(g_client_fd[i],buffer,sizeof(buffer), 0);//最后一个参数为0,表示默认阻塞接收,前面select解除了阻塞说明有数据可读
if(ret > 0)
{
printf("recv(%d):%s",ret,buffer);
fflush(stdout);
send(g_client_fd[i],buffer,ret,0);
}
else
{
printf("client disconnected\r\n");
g_client_fd[i] = 0;
close(g_client_fd[i]);
}
}
}
}
}break;
}
}
exit:
close(sockfd);
return NULL;
}
void main(int argc,char *argv[])
{
int ret = -1;
ret = pthread_create(&g_server_tid,NULL,&tcp_server,NULL);
if(0 != ret)
{
printf("tcp client create error\r\n");
}
pthread_join(g_server_tid,NULL);
}