http://blog.csdn.net/li_ning_/article/details/52165993
fd_set
你终于还是来了,能看到这个标题进来的,我想,你一定是和我遇到了一样的问题,一样的疑惑,接下来几个小时,我一定竭尽全力,写出我想说的,希望也正是你所需要的:
关于Linux下I/O多路转接之select,我不想太多的解释,用较少的文章引出今天我要说的问题:fd_set...自我感觉,这个东西,是理解select的关键。
一、关于select函数:
![](https://img-blog.csdn.net/20160809204955143?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
以上只是截屏,以保证本人说的是真话,下面解释:
系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。
程序会停在select这里等待,直到被监视的文件句柄有一个或 多个发生了状态改变。关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三 个,
0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE * 结构的表示就是stdin、stdout、stderr。
1.参数nfds是需要监视的最大的文件描述符值+1;
2.rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合。
3.struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。
下面的宏提供了处理这三种描述词组的方式:
1. FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位。
2. FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真 。
3.FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位 。
4.FD_ZERO(fd_set *set);用来清除描述词组set的全部位 参数timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:
![](https://img-blog.csdn.net/20160809205804468?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
结构体成员两个,第一个单位是秒,第二个单位是微妙 ,作用是时间为两个之和;
更多关于select的应用,咱移驾看这位大神:http://blog.sina.com.cn/s/blog_5c8d13830100pwaf.html 关于select的使用,理解了也就好弄了,关于应用,后附代码:
下面才是我想说的东西:
二、fd_set:
1>>fd_set是什么:
select()机制中提供一fd_set的数据结构,可以理解为一个集合,实际上是一个位图,每一个特定位来标志相应大小文件描述符,这个集合中存放的是文件描述符,即就是文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件可读。Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,程序员通过操作4类宏,来完成最fd_set的操作,在上文已经提及。
2>>fe_set怎么表示:
![](https://img-blog.csdn.net/20160809212403355?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
其中readfds、writefds等都是fd_set类型,其中的每一位都表示一个fd,即文件描述符。
3>>fd_set用法:
过去,一个fd_set通常只能包含<32的fd(文件描述字),因为fd_set其实只用了一个32位矢量来表示fd;现在,UNIX系统通常会在头文件中定义常量FD_SETSIZE,它是数据类型fd_set的描述字数量,其值通常是1024,这样就能表示<1024的fd。根据fd_set的位矢量实现,我们可以重新理解操作fd_set的四个宏:
fd_set set;
FD_ZERO(&set); /*将set的所有位置0,如set在内存中占8位则将set置为00000000*/
FD_SET(0, &set); /* 将set的第0位置1,如set原来是00000000,则现在变为10000000,这样fd==1的文件描述字就被加进set中了 */
FD_CLR(4, &set); /*将set的第4位置0,如set原来是10001000,则现在变为10000000,这样fd==4的文件描述字就被从set中清除了 */
FD_ISSET(5, &set); /* 测试set的第5位是否为1,如果set原来是10000100,则返回非零,表明fd==5的文件描述字在set中;否则返回0*/
我们在回到原函数:select
int select(int nfds, fd_set *readset, fd_set *writeset,fd_set* exceptset, struct timeval *timeout);
功能:测试指定的fd可读、可写、有异常条件待处理。
参数:
1.nfds
需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在上边例子中readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所有1024位。
readset :用来检查可读性的一组文件描述字。
writeset :用来检查可写性的一组文件描述字。
exceptset :用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)
timeout:有三种可能:
1. timeout=NULL(阻塞:直到有一个fd位被置为1函数才返回)
2. timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)
3. timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回)
返回值:
1.当监视的相应的文件描述符集中满足条件时,比如说读文件描述符集中有数据到来时,内核(I/O)根据状态修改文件描述符集,并返回一个大于0的数。
2.当没有满足条件的文件描述符,且设置的timeval监控时间超时时,select函数会返回一个为0的值。
3.当select返回负值时,发生错误。
备注:
三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位仍然为1。
使用select函数的过程一般是:
先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。
以下是一个测试单个文件描述字可读性的例子:
![](https://img-blog.csdn.net/20160809225045798?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
基于select实现的网络服务器和客户端:
server.c
- #include<stdio.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #include<unistd.h>
- #include<stdlib.h>
- #include<errno.h>
- #include<arpa/inet.h>
- #include<netinet/in.h>
- #include<string.h>
- #include<signal.h>
- #include<sys/wait.h>
-
-
-
-
-
-
- #define ERR_EXIT(m) \
- do { \
- perror(m); \
- exit(EXIT_FAILURE); \
- } while (0)
-
-
- int main(void)
- {
- signal(SIGPIPE, SIG_IGN);
-
-
- int listenfd;
- if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
- ERR_EXIT("socket error");
-
- struct sockaddr_in servaddr;
-
-
-
-
- servaddr.sin_family = AF_INET;
- servaddr.sin_port = htons(8080);
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
-
-
-
-
- int on = 1;
- if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
- ERR_EXIT("setsockopt error");
-
-
- if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
- ERR_EXIT("bind error");
-
-
- if (listen(listenfd, SOMAXCONN) < 0)
- ERR_EXIT("listen error");
-
- struct sockaddr_in peeraddr;
- socklen_t peerlen = sizeof(peeraddr);
-
- int conn;
- int i;
- int client[FD_SETSIZE];
- int maxi = 0;
- for (i = 0; i < FD_SETSIZE; i++)
- client[i] = -1;
-
- int nready;
- int maxfd = listenfd;
- fd_set rset;
- fd_set allset;
- FD_ZERO(&rset);
- FD_ZERO(&allset);
- FD_SET(listenfd, &allset);
-
- while (1) {
- rset = allset;
- nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
- if (nready == -1) {
- if (errno == EINTR)
- continue;
- ERR_EXIT("select error");
- }
-
- if (nready == 0)
- continue;
-
- if (FD_ISSET(listenfd, &rset)) {
-
- conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);
- if (conn == -1)
- ERR_EXIT("accept error");
-
- for (i = 0; i < FD_SETSIZE; i++) {
- if (client[i] < 0) {
- client[i] = conn;
- if (i > maxi)
- maxi = i;
- break;
- }
- }
-
- if (i == FD_SETSIZE) {
- fprintf(stderr, "too many clients\n");
- exit(EXIT_FAILURE);
- }
-
- printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),
- ntohs(peeraddr.sin_port));
-
- FD_SET(conn, &allset);
- if (conn > maxfd)
- maxfd = conn;
-
- if (--nready <= 0)
- continue;
- }
-
- for (i = 0; i <= maxi; i++) {
- conn = client[i];
- if (conn == -1)
- continue;
-
- if (FD_ISSET(conn, &rset)) {
-
- char recvbuf[1024] = {0};
- int ret = read(conn, recvbuf, 1024);
- if (ret == -1)
- ERR_EXIT("readline error");
- else if (ret == 0) {
- printf("client close \n");
- FD_CLR(conn, &allset);
- client[i] = -1;
- close(conn);
- }
-
- fputs(recvbuf, stdout);
- write(conn, recvbuf, strlen(recvbuf));
-
- if (--nready <= 0)
- break;
- }
- }
- }
- return 0;
- }
client.c
- #include<stdio.h>
- #include<sys/types.h>
- #include<sys/socket.h>
- #include<unistd.h>
- #include<stdlib.h>
- #include<errno.h>
- #include<arpa/inet.h>
- #include<netinet/in.h>
- #include<string.h>
-
-
- #define ERR_EXIT(m) \
- do { \
- perror(m); \
- exit(EXIT_FAILURE); \
- } while (0)
-
-
-
-
- int main(void)
- {
- int sock;
- if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
-
- ERR_EXIT("socket error");
-
-
- struct sockaddr_in servaddr;
- memset(&servaddr, 0, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_port = htons(8080);
- servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
-
-
- if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
- ERR_EXIT("connect error");
- struct sockaddr_in localaddr;
- char cli_ip[20];
- socklen_t local_len = sizeof(localaddr);
- memset(&localaddr, 0, sizeof(localaddr));
- if( getsockname(sock,(struct sockaddr *)&localaddr,&local_len) != 0 )
- ERR_EXIT("getsockname error");
- inet_ntop(AF_INET, &localaddr.sin_addr, cli_ip, sizeof(cli_ip));
- printf("host %s:%d\n", cli_ip, ntohs(localaddr.sin_port));
-
- fd_set rset;
- FD_ZERO(&rset);
- int nready;
- int maxfd;
- int fd_stdin = fileno(stdin);
- if (fd_stdin > sock)
- maxfd = fd_stdin;
- else
- maxfd = sock;
- char sendbuf[1024] = {0};
- char recvbuf[1024] = {0};
-
- while (1)
- {
-
- FD_SET(fd_stdin, &rset);
- FD_SET(sock, &rset);
- nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
- if (nready == -1)
- ERR_EXIT("select error");
-
- if (nready == 0)
- continue;
-
- if (FD_ISSET(sock, &rset))
- {
-
- int ret = read(sock, recvbuf, sizeof(recvbuf));
- if (ret == -1)
- ERR_EXIT("read error");
- else if (ret == 0)
- {
- printf("server close\n");
- break;
- }
-
- fputs(recvbuf, stdout);
- memset(recvbuf, 0, sizeof(recvbuf));
- }
-
- if (FD_ISSET(fd_stdin, &rset))
- {
-
- if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
- break;
-
- write(sock, sendbuf, strlen(sendbuf));
- memset(sendbuf, 0, sizeof(sendbuf));
- }
- }
-
- close(sock);
- return 0;
- }
赐教!