蓝桉云顶

Good Luck To You!

Linux中的select函数是如何实现阻塞机制的?

Linux 中的 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 阻塞”的知识,希望对你们有所帮助。如果您还有其他相关问题需要解决,欢迎随时提出哦!

  •  雁南归未迟
     发布于 2024-02-17 21:52:12  回复该评论
  • 网站内链优化常见方法这篇文章深入浅出,为提升网站内部链接效率提供了实用的策略和技巧,非常值得一看。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

«    2024年11月    »
123
45678910
11121314151617
18192021222324
252627282930
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
搜索
最新留言
文章归档
网站收藏
友情链接