Linux下I/O多路转接-select函数 原创 Linux平台 2022年4月8日 11:20 夏至未至 1183 当前内容 8101 字,在路上,马上到,马上到 ### 目录 [TOC] ### 函数介绍 #### 函数功能 监视描述符集合中的描述符状态变化。程序会在select函数等待,直到有描述符就绪。 #### 函数原型 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); #### 函数参数 | 参数名 | 参数描述 | | ------------ | ------------ | | nfds | 当前描述符集合最大描述符+1 | | readfds | 监控可读事件的描述符集合 | | writefds | 监控可写事件的描述符集合 | | execptfds | 监控异常事件的描述符集合 | | timeout | 用来设置监控时间,如果指定时间没有描述符就绪,则超时返回。NULL:表示一直等待,直到有描述符事件就绪 | struct timeval { long tv_sec; /* seconds 秒*/ long tv_usec; /* microseconds 微秒*/ }; 结构体成员两个,第一个单位是秒,第二个单位是微妙 ,作用是时间为两个之和; 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。 ### fd_set介绍 #### 何为fd_set select() 机制中提供一fd_set的数据结构,可以理解为一个集合,实际上是一个位图,每一个特定位来标志相应大小文件描述符,这个集合中存放的是文件描述符,即就是文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件可读。Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set结构如下: typedef struct { /*XPG4.2requiresthismembername.Otherwiseavoidthename fromtheglobalnamespace.*/ #ifdef__USE_XOPEN __fd_mask fds_bits[__FD_SETSIZE/__NFDBITS]; #define__FDS_BITS(set)((set)->fds_bits) #else __fd_mask __fds_bits[__FD_SETSIZE/__NFDBITS]; #define__FDS_BITS(set)((set)->__fds_bits) #endif }fd_set; #### 如何操作fd_set fd_set 集合可以通过一些宏由人为来操作,程序员通过操作4类宏,来完成最fd_set的操作: void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd的位 int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd的位是否为真 存在则返回非零,不存在返回零。 void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位 void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位 #### fd_set图示  其中readfds、writefds等都是fd_set类型,其中的每一位都表示一个fd,即文件描述符。 ### select应用源码 #### 场景 基于select实现的网络服务器和客户端 #### 服务端 server.c #include #include #include #include #include #include #include #include #include #include #include /* *网络服务器,select参与调度 * */ //ERR_EXIT(M)是一个错误退出宏 #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0) int main(void) { signal(SIGPIPE, SIG_IGN); //1.创建套接字 int listenfd; //被动套接字(文件描述符),即只可以accept, 监听套接字 if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT("socket error"); //调用上边的宏 struct sockaddr_in servaddr; //memset(&servaddr, 0, sizeof(servaddr)); //三个结构体成员 //设置本地IP 和端口 servaddr.sin_family = AF_INET; servaddr.sin_port = htons(8080); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */ /* inet_aton("127.0.0.1", &servaddr.sin_addr); */ //2.设置套接字属性 int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt error"); //3.绑定 if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) ERR_EXIT("bind error"); //4.监听 if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前 ERR_EXIT("listen error"); struct sockaddr_in peeraddr; //传出参数 socklen_t peerlen = sizeof(peeraddr); //传入传出参数,必须有初始值 int conn; // 已连接套接字(变为主动套接字,即可以主动connect) int i; int client[FD_SETSIZE]; int maxi = 0; // client数组中最大不空闲位置的下标 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);//accept不再阻塞 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 #include #include #include #include #include #include #include #include #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) // listenfd = socket(AF_INET, SOCK_STREAM, 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"); /* inet_aton("127.0.0.1", &servaddr.sin_addr); */ 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); //select返回表示检测到可读事件 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; } 本文标题: Linux下I/O多路转接-select函数 本文作者: 夏至未至 发布时间: 2022年4月8日 11:20 最近更新: 2022年4月8日 11:28 原文链接: 许可协议: 署名-非商业性-禁止演绎 4.0 国际(CC BY-NC-ND 4.0) 请按协议转载并保留原文链接及作者 select(1) 上一个 Linux下I/O多路转接-epoll函数 下一个 Linux下I/O多路转接-poll函数 当前文章评论暂未开放,请移步至留言处留言。