select
函数用于监控文件描述符的变化,当指定条件未满足时,它会阻塞程序执行,直到有文件描述符准备好或超时。Linux中的select函数与阻塞
在Linux编程中,select
函数是一种用于多路复用I/O的机制,它允许一个进程监视多个文件描述符(如套接字、管道等),以查看是否有任何文件描述符准备好进行读取、写入或有异常发生。select
函数的使用涉及到阻塞和非阻塞两种模式,本文将详细探讨select
函数及其阻塞行为。
一、select
函数
select
函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds: 集合中所有文件描述符的范围,即所有文件描述符的最大值加1。
readfds: 需要监视的文件描述符集合,用于检查是否有数据可读。
writefds: 需要监视的文件描述符集合,用于检查是否可以写数据。
exceptfds: 需要监视的文件描述符集合,用于检查是否有异常条件发生。
timeout: 等待时间,决定select
是阻塞还是非阻塞。
二、阻塞与非阻塞行为
select
函数的阻塞与非阻塞行为主要取决于timeout
参数的值:
1、阻塞模式:
当timeout
为NULL时,select
进入阻塞模式,会一直等待直到至少有一个文件描述符变得可读、可写或有异常发生。
struct timeval tv; tv.tv_sec = 5; // 5秒超时 tv.tv_usec = 0; FD_ZERO(&readfds); FD_SET(sockfd, &readfds); int result = select(sockfd + 1, &readfds, NULL, NULL, &tv);
在这个例子中,如果没有数据到达,select
会阻塞最多5秒。
2、非阻塞模式:
当timeout
设置为0时,select
立即返回,无论是否有文件描述符准备好,这种模式下,select
不会阻塞,主要用于轮询文件描述符状态。
FD_ZERO(&readfds); FD_SET(sockfd, &readfds); int result = select(sockfd + 1, &readfds, NULL, NULL, NULL); if (result == 0) { printf("No data within non-blocking select "); }
在这个例子中,select
会立即返回,如果有数据可读则返回正值,否则返回0。
3、超时模式:
当timeout
设置为一个大于0的值时,select
会在指定的时间后返回,无论是否有文件描述符准备好,这种方式可以防止无限期阻塞。
struct timeval tv; tv.tv_sec = 2; // 2秒超时 tv.tv_usec = 500000; // 0.5秒 FD_ZERO(&readfds); FD_SET(sockfd, &readfds); int result = select(sockfd + 1, &readfds, NULL, NULL, &tv); if (result == 0) { printf("Timeout occurred! No data within 2.5 seconds. "); }
三、使用场景与注意事项
1、单 socket 操作:
对于单个socket,可以使用阻塞模式,这样可以避免CPU空转,一个简单的客户端程序可以阻塞等待服务器响应。
2、多 socket 操作:
对于多个socket,通常使用非阻塞模式配合循环,以实现高效的I/O复用,一个聊天服务器需要同时处理多个客户端连接。
3、结合epoll:
在一些高并发场景下,select
的性能可能不够理想,此时可以考虑使用更高效的epoll
来替代select
。
四、代码示例
以下是一个完整的示例程序,展示了如何使用select
函数在阻塞模式下等待数据的到来:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/time.h> #include <errno.h> void error(const char *msg) { perror(msg); exit(1); } int main() { int sockfd, newsockfd; char buffer[256]; struct sockaddr_in server_addr, client_addr; struct timeval tv; fd_set readfds; int retval; socklen_t clilen; // Create socket sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); // Bind socket to port bzero((char *)&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(12345); if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) error("ERROR on binding"); // Listen for connections listen(sockfd, 5); clilen = sizeof(client_addr); // Accept connection newsockfd = accept(sockfd, (struct sockaddr *)&client_addr, &clilen); if (newsockfd < 0) error("ERROR on accept"); // Set up the file descriptor set FD_ZERO(&readfds); FD_SET(newsockfd, &readfds); // Set a timeout of 5 seconds tv.tv_sec = 5; tv.tv_usec = 0; // Use select for blocking wait retval = select(newsockfd + 1, &readfds, NULL, NULL, &tv); if (retval == -1) error("SELECT error"); else if (retval) { printf("Data is available now. "); bzero(buffer, 256); retval = read(newsockfd, buffer, 255); if (retval < 0) error("ERROR reading from socket"); printf("Here is the message: %s ", buffer); } else { printf("%d seconds passed and no data received. ", tv.tv_sec); } close(newsockfd); close(sockfd); return 0; }
这个示例展示了一个简单的echo服务器,它在阻塞模式下使用select
等待客户端的数据,如果5秒内没有收到数据,程序将输出超时信息。
五、FAQs
1、为什么select函数总是返回1?
select
返回1表示有一个文件描述符已经准备好,这并不一定意味着有数据可读或可写,具体需要通过宏(如FD_ISSET
)来判断哪个文件描述符准备好了。
int result = select(...); if (result > 0 && FD_ISSET(sockfd, &readfds)) { // Read data from sockfd }
2、如何处理多个文件描述符的读写?
可以使用循环遍历所有文件描述符,检查它们是否在相应的集合中。
for (int i = 0; i <= maxfd; i++) { if (FD_ISSET(i, &readfds)) { // Read from file descriptor i } else if (FD_ISSET(i, &writefds)) { // Write to file descriptor i } }
各位小伙伴们,我刚刚为大家分享了有关“linux select 阻塞”的知识,希望对你们有所帮助。如果您还有其他相关问题需要解决,欢迎随时提出哦!