1.功能介绍
1.1 网络通信中,对于套接字(文件描述符)在任意时刻是否有数据可读,我们不知道,只会用while 10毫秒循环收发,select能够解决这个问题,时时监听套接字的读写情况,有收到数据就读取。
2.相关函数说明
2.1 int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
说明:select()用来等待文件描述符状态的改变。参数n代表最大的文件描述符加1,参数readfds、writefds和exceptfds称为文件描述符集,填了readfd代表监听可读事件(收数据),不填代表不监听该事件,其他两个同理,timeout代表超时时间设置,就是个时间,可不设置,填NULL,struct timeval结构体如下:
struct timeval{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
}
2.2 字符集相关函数操作
int FD_ZERO(fd_set *fdset);
int FD_CLR(int fd, fd_set *fdset);
int FD_SET(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);
说明:FD_ZERO是初始化字符集变量,使用FD_SET将套接字fd(文件描述符)加入字符集fdset,使用FD_CLR将套接字fd(文件描述符)从字符集fdset中删除,使用FD_ISSET来判断套接字fd(文件描述符)是否加入字符集fdset进行监听。
3.使用场景
3.1 有多个套接字(高并发)需要收发数据。
4.使用代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
int main()
{
//创建socket
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd < = 0)
{
return -1;
}
//设置端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//绑定
struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
//监听
listen(lfd, 128);
//定义文件描述符集变量
fd_set readfds, tmpfds;
//初始化文件描述符集变量
FD_ZERO(&readfds);
FD_ZERO(&tmpfds);
//将lfd加入到readfds中
FD_SET(lfd, &readfds);
int maxfd = lfd;
int cfd;
int i;
int sockfd;
int nready;
int n;
char buf[1024];
sleep(10);
while(1)
{
FD_ZERO(&tmpfds);
tmpfds = readfds;
//将文件描述符集委托给内核监控
//tmpfds是传入传出参数,
//传入:告诉内核要监测哪些文件描述符
//传出:内核告诉应用程序那些文件发生了变化, 但是并没有具体告诉到底是哪些.
nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
printf("nready==[%d]\n", nready);
if(nready<0)
{
if(errno==EINTR)//若被信号中断,则errno为EINTR
{
perror("select error");
continue;
}
close(lfd);
exit(-1);
}
//若有客户端连接请求, 则接受新的连接,同时将新的连接加入到文件描述符集中
if(FD_ISSET(lfd, &tmpfds))
{
cfd = accept(lfd, NULL, NULL);
if(lfd < = 0)
{
continue;
}
//将cfd加入到文件描述符集中
FD_SET(cfd, &readfds);
if(maxfd<cfd)
{
maxfd = cfd;
}
if(--nready==0)
{
continue;
}
}
//下面是有客户端发送数据到来的情况
for(i=lfd+1; i<=maxfd; i++)
{
//判断某个文件描述符是否有变化
sockfd = i;
if(FD_ISSET(sockfd, &tmpfds))
{
//读数据
memset(buf, 0x00, sizeof(buf));
n = Read(sockfd, buf, sizeof(buf));
if(n<=0)
{
//若读数据失败或者对方关闭连接, 则将sockfd从文件描述符集中删除
close(sockfd);
FD_CLR(sockfd, &readfds);
}
else
{
for(int k=0; k<n; k++)
{
buf[k] = toupper(buf[k]);
}
Write(sockfd, buf, n);
}
if(--nready==0)
{
break;
}
}
}
}
close(lfd);
return 0;
}
5.缺点
5.1 当客户端多个连接, 但少数活跃的情况, select效率较低。
例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下。
5.2 fd_set能容纳的文件描述符数量由FD_SETSIZE指定,一般情况下,FD_SETSIZE等 于1024,这就限制了select能同时处理的文件描述符的总量(FD_SETSIZE=1024 , fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核, 一般不建议这么做)。