下图为select()函数的介绍
select函数根据希望进行的文件操作对文件描述符进行了分类,这里对文件描述符的处理主要涉及4个宏函数,如下表2所示
另外,select()函数中的 timeout 是一个 struct timeval类型的指针,该结构体如下所示:
可以看到,这个时间结构体的精确度可以设置到微秒级,这对于大多数的应用而言已经足够了。
使用select()函数的过程可概括为:先调用FD_ZERO() 将指定的fd_set清零,然后调用宏FD_SET()将需要测试的fd加入fd_set,接着调用函数select测试fd_set中的所有fd,最后用用宏FD_ISSET()检查某个fd在函数select调用后,相应位是否仍然为1。在执行完对相关文件描述符的操作后,使用FD_CLR来描述符集。
//服务器
//使用方法: ./server ip port
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define maxs 1024
void do_task(int *clientfd,int maxsfd,fd_set* rset, fd_set* allset)
{
int nread;
char buf[maxs];
for (int i=0; imaxfd)
maxfd=connfd;
FD_SET(connfd,&allset); //添加连接套接字到文件描述符集合中
if (--nread<=0) //如果只返回一个可读的,则不用往下执行了
continue;
}
if (FD_ISSET(STDIN_FILENO,&rset)) //可输入
{
scanf("%s",quit);
if (strcasecmp(quit, "QUIT")==0) //是否退出
{
close(listenfd);
return 0;
}
}
do_task(clientfd,maxfd,&rset,&allset); //执行客户请求
}
return 0;
}
//客服端
//使用方法:./client ip port
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define maxs 1024
void do_task(int sockfd)
{
fd_set rset,allset;
FD_ZERO(&allset);
char buf[maxs];
int nread;
//将输入描述符和套接字描述符加入集合中
FD_SET(STDIN_FILENO,&allset);
FD_SET(sockfd,&allset);
int maxfd=STDIN_FILENO>sockfd? STDIN_FILENO :sockfd;
while (1)
{
rset=allset;
if (select(maxfd+1, &rset, NULL, NULL, NULL)==-1) //阻塞式调用select
{
perror("select error");
continue;
}
if (FD_ISSET(sockfd,&rset)) //套接字可读
{
nread=read(sockfd, buf, sizeof(buf)); //接收服务器发送数据
if (nread==0) //服务器出错
{
printf("server close the connection\n");
break;
}
else if(nread==-1)
{
perror("read error");
break;
}
else
{
//显示数据
write(STDOUT_FILENO, "get server send data:",21);
write(STDOUT_FILENO, buf, nread);
}
}
if(FD_ISSET(STDIN_FILENO,&rset)) //当前可输入
{
gets(buf);
if (strlen(buf)==0)
continue;
if(strcasecmp(buf, "quit")==0)
{
printf("client exit...\n");
break;
}
write(sockfd, buf, strlen(buf)); //发送输入数据
printf("send to server ok\n");
}
}
}
int main(int argc,char ** argv)
{
int sockfd;
struct sockaddr_in server;
bzero(&server, sizeof(server));
//设置服务器信息
inet_aton(argv[1], &server.sin_addr);
server.sin_port=htons(atoi(argv[2]));
server.sin_family=AF_INET;
sockfd=socket(AF_INET, SOCK_STREAM, 0);
if (connect(sockfd, (struct sockaddr*)&server, sizeof(server))<0)
{
perror("connect error");
return -1;
}
do_task(sockfd);
close(sockfd);
return 0;
}
/*
TCP服务器(单进程)
用法:./server port
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFSIZE 1024
static void bail(const char *on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what, stderr);
fputc('\n',stderr);
exit(1);
}
struct sockfd_opt //处理每个socket描述符的结构体
{
int fd; //描述符
int (*do_task)(struct sockfd_opt *p_so); //回调函数
};
std::listList; //链表元素
fd_set rfds,orfds; //文件描述符集合
int maxfd; //文件描述符的最大取值
//向客户端发回日期时间
int send_reply(struct sockfd_opt *p_so)
{
char reqBuf[BUFSIZE];//接收缓存
char dtfmt[BUFSIZE];//日期-时间结果字符串
time_t td; //当前时间和日期
struct tm tm;
long z;
if ((z=read(p_so->fd, reqBuf,sizeof(reqBuf)))<0)
{
//关闭当前套接字描述符
close(p_so->fd);
//注意:这里必须从orfds集合中删除此关闭的文件描述符,以方便下次调用select
FD_CLR(p_so->fd,&orfds);
//从套接字选项链表中删除当前选项p_so;
List.remove(p_so);
//若读操作返回-1且不是RST分段
if (z<0 && (errno|=ECONNRESET))
bail("read()");
}
else
{
reqBuf[z]=0;
time(&td);
tm=*localtime(&td);
strftime(dtfmt, sizeof(dtfmt), reqBuf, &tm);
//向客户端发回结果
z=write(p_so->fd, dtfmt,strlen(dtfmt));
if (z<0)
bail("write()");
}
return 0;
}
//接收TCP连接
int creat_conn(struct sockfd_opt *p_so)
{
struct sockaddr_in client; //客户端ip地址
int conn_fd;
socklen_t sin_size;
sin_size=sizeof(client);
if ((conn_fd=accept(p_so->fd, (struct sockaddr*)&client, &sin_size))==-1)
{
fprintf(stderr,"Accept error:%s\a\n",strerror(errno));
exit(1);
}
fprintf(stdout,"server got connection from %s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
if(List.size()+1==FD_SETSIZE) //若连接超过了最大值
{
fprintf(stderr,"too many clients!\n");
return -1;
}
if ((p_so=(struct sockfd_opt*)malloc(sizeof(sockfd_opt)))==NULL)
{
perror("malloc");
return -1;
}
p_so->fd=conn_fd;
//设置当前套接字选项的回调函数
p_so->do_task=send_reply;
List.push_back(p_so); //将此套接字加入队列
//将此已连接套接字加入到orfds集合中
FD_SET(conn_fd,&orfds);
if (conn_fd>maxfd)
maxfd=conn_fd;
return 0;
}
//初始化监听套接字选项
int init(int sk)
{
sockfd_opt *p_so;
if ((p_so=(struct sockfd_opt*)malloc(sizeof(sockfd_opt)))==NULL)
{
perror("malloc");
return -1;
}
//设置监听套接字选项的回调函数
p_so->do_task=creat_conn;
p_so->fd=sk;
//将监听套接字选项加入到链表尾
List.push_back(p_so);
//清空orfds
FD_ZERO(&orfds);
//将监听套接字加入到orfds集合
FD_SET(sk,&orfds);
return 0;
}
int main(int argc,char *argv[])
{
int listen_fd; //用于监听的套接字描述符
struct sockaddr_in server;
int port;
socklen_t optlen;
port=atoi(argv[1]);
if((listen_fd=socket(PF_INET,SOCK_STREAM, 0))==-1)
bail("socket()");
//设置套接字选项
int opt;
optlen=sizeof(opt);
int ret=setsockopt(listen_fd,SOL_SOCKET, SO_REUSEADDR, &opt, optlen);
if (ret)
bail("setsockopt()");
//服务器监听地址准备
memset(&server, 0, sizeof(server));
server.sin_family=PF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(port);
//绑定服务器到监听套接字
if((bind(listen_fd, (struct sockaddr*)&server, sizeof(server)))==-1)
bail("bind()");
//开始监听
if(listen(listen_fd,5)==-1)
bail("listen()");
maxfd=listen_fd;
if (init(listen_fd))
bail("init()");
printf("server is waiting for acceptance of new client\n");
struct sockfd_opt *p_so;
for (; ; )
{
//恢复rfds为最初设置的orfds,准备下次select的调用
rfds=orfds;
//执行select
int n=select(maxfd+1, &rfds,NULL, NULL,NULL);
//在返回的n个可读的文件描述符中进行迭代
for(;n>0;n--)
{
std::list::iterator it=List.begin();
while (it!=List.end())
{
p_so=*it;
//检查此套接字文件描述符是否被置位
if(FD_ISSET(p_so->fd,&rfds))
{
p_so->do_task(p_so);
}
++it;
}
}
}
return 0;
}
//TCP客户端
/*
用法:./client hostname port
说明:本程序使用TCP连接和TCP服务器通信,当连接建立后,向服务器发送如下格式字符串
格式字符串示例:
(1) %D
(2) %A %D %H:%M:%S
(3) %A
(4) %H:%M:%S
(5)...
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFSIZE 1024
#define backlog 128 //等待队列大小
static void bail(const char *on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what, stderr);
fputc('\n',stderr);
exit(1);
}
int main(int argc,char *argv[])
{
int sockfd;
char rcvBuf[BUFSIZE];
char reqBuf[BUFSIZE];
struct sockaddr_in server;
struct hostent *host;
int port;
long z;
fd_set rfds,orfds;
int ret,maxfd=-1;
if ((host=gethostbyname(argv[1]))==NULL)
bail("gethostbyname()");
if ((port=atoi(argv[2]))<0)
{
fprintf(stderr,"get port error");
exit(1);
}
//创建客户端套接字文件描述符
if ((sockfd=socket(PF_INET,SOCK_STREAM, 0))==-1)
bail("socket()");
//创建服务器地址
memset(&server, 0, sizeof(server));
server.sin_family=PF_INET;
server.sin_port=htons(port);
server.sin_addr=*((struct in_addr*)host->h_addr);
//连接到服务器
if (connect(sockfd, (struct sockaddr*)&server, sizeof(server))==-1)
bail("connect()");
//清空文件描述符集合orfds
FD_ZERO(&orfds);
//标准输入加入orfds集合
FD_SET(STDIN_FILENO,&orfds);
maxfd=STDIN_FILENO;
//把当前客户端套接字文件描述符sockfd加到orfds
FD_SET(sockfd,&orfds);
if (sockfd>maxfd)
maxfd=sockfd;
printf("connected to server:%s\n",inet_ntoa(server.sin_addr));
for (; ; )
{
rfds=orfds; //重新恢复用于检测的可读描述符集
printf("\nEnter format string(^D or 'quit' to exit):");
fflush(stdout);
//调用select开始等待可读文件描述符集可用
ret=select(maxfd+1, &rfds,NULL, NULL,NULL);
if (ret==-1)
{
printf("select:%s",strerror(errno));
break;
}
else
{
//检测套接字描述符sockfd
if (FD_ISSET(sockfd,&rfds))
{
//读取服务器应答
if((z=read(sockfd, rcvBuf,sizeof(rcvBuf)))==-1)
bail("read");
//若服务器关闭连接
if(z==0)
{
fprintf(stderr,"server has closed the socket.\n");
fprintf(stderr,"press enter to exit...\n");
getchar();
break;
}
//显示当前日期和时间
printf("result from %s port %u:\n\t '%s'\n",inet_ntoa(server.sin_addr),ntohs(server.sin_port),rcvBuf);
}
else if(FD_ISSET(STDIN_FILENO,&rfds))//若标准输入可读
{
if(!fgets(reqBuf,BUFSIZE, stdin))
{
//用户按ctrl+D组合键结束输入,eof
printf("\n");
break;
}
z=strlen(reqBuf);
//去掉用户输入字符串中的换行符
if(z>0 && reqBuf[--z]=='\n')
reqBuf[z]=0;
if (z==0)
continue;
//用户输入quit退出
if (!strcasecmp(reqBuf,"QUIT"))
break;
//将格式字符串发送到服务器
z=write(sockfd, reqBuf, strlen(reqBuf));
if (z<0)
bail("write()");
}
}
}
close(sockfd);
return 0;
}