面试题:网络编程中select如何设置非阻塞模式?

旅行   2024-10-17 07:55   广东  

欢迎关注本公众号,专注面试题拆解

分享一套视频课程:《C++实现百万并发服务器》 面试需要项目的可以找我获取
,免费分享。 欢迎V:fb964919126







网络编程中select如何设置非阻塞模式?





这里说的非阻塞其实是有两个部分:

第一:select 函数自身的非阻塞

当 select 的 timeout 设置为 {0, 0} 时,select 会立即返回,这种情况下 select 被认为是非阻塞的。

当 select 的 timeout 设置为 NULL 时,select 会一直阻塞,直到至少有一个描述符状态发生了变化,这种情况下 select 被认为是阻塞的。

fd_set readfds;struct timeval timeout = {0, 0}; // 设置超时时间为立即返int ret = select(sockfd + 1, &readfds, NULL, NULL, &timeout);

其实select函数本身设计可以看做是阻塞模式的,只不过这个值为0是特殊情况。平时用的最多的情况其实是给一个相对最优的时间作为超时时间。


select() 的 timeout 设置不会改变描述符本身的阻塞模式。即使 select() 立即返回,描述符本身仍然是阻塞的或非阻塞的,取决于它们本身的设置。


第二:描述符的非阻塞

当描述符(如套接字)设置为非阻塞模式时,读写操作不会阻塞,而是立即返回。

当描述符设置为阻塞模式时,读写操作会阻塞,直到数据可用或写入完成。


使用 O_NONBLOCK 设置为非阻塞

int set_nonblocking(int fd) {    int flags = fcntl(fd, F_GETFL, 0);    if (flags == -1) {        perror("fcntl(F_GETFL)");        return -1;    }    flags |= O_NONBLOCK; // 设置非阻塞标志    if (fcntl(fd, F_SETFL, flags) == -1) {        perror("fcntl(F_SETFL)");        return -1;    }    return 0;}


O_NONBLOCK 标志设置:

改变描述符本身的阻塞模式,使描述符变为非阻塞模式。

影响所有与描述符相关的读写操作。


再理解下阻塞和非阻塞:

针对描述符套接字,非阻塞在尝试读取或写入时没有数据可用时,这些操作会立即返回,并可能设置错误标志。但是在有数据可用时,即便是非阻塞套接字,但是它从内核态拷贝数据到用户态的动作是阻塞的。

从内核态拷贝数据到用户态的操作可以被认为是阻塞。‌ 在这个过程中,CPU需要等待数据从内核缓冲区复制到用户进程缓冲区,因此进程会被阻塞,无法处理其他任务‌,虽然现代CPU做这个拷贝非常快,几乎是瞬间完成。


select同步非阻塞代码示例:

#include <sys/time.h>#include <sys/select.h>#include <unistd.h>#include <fcntl.h>#include <iostream>#include <cstring>
int set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { perror("fcntl(F_GETFL)"); return -1; } flags |= O_NONBLOCK; // 设置非阻塞标志 if (fcntl(fd, F_SETFL, flags) == -1) { perror("fcntl(F_SETFL)"); return -1; } return 0;}
int main() { int sockfd; // 假设 sockfd 已经被正确创建并连接 set_nonblocking(sockfd);
fd_set readfds; struct timeval timeout = {0, 0}; // 设置超时时间为立即返回
while (true) { FD_ZERO(&readfds); // 清空文件描述符集合 FD_SET(sockfd, &readfds); // 添加 sockfd 到集合
int ret = select(sockfd + 1, &readfds, NULL, NULL, &timeout); if (ret == -1) { perror("select"); break; } else if (ret == 0) { std::cout << "No activity on socket" << std::endl; } else { if (FD_ISSET(sockfd, &readfds)) { std::cout << "Socket is ready to read" << std::endl; char buffer[1024]; ssize_t bytesRead = read(sockfd, buffer, sizeof(buffer) - 1); if (bytesRead > 0) { buffer[bytesRead] = '\0'; std::cout << "Received data: " << buffer << std::endl; } else if (bytesRead == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { std::cout << "No data available" << std::endl; } else { std::cerr << "Failed to read data: " << strerror(errno) << std::endl; } } } }
return 0;}


1:设置非阻塞模式:

使用 set_nonblocking 函数将套接字设置为非阻塞模式。

2:设置 timeout 为 {0, 0}:

struct timeval timeout = {0, 0} 表示 select() 函数将立即返回。

3:调用 select():

select(sockfd + 1, &readfds, NULL, NULL, &timeout) 会立即返回,无论是否有描述符准备好。

4:处理读取操作:

如果描述符准备好读取,尝试读取数据。如果 read() 返回 -1 并且 errno 为 EAGAIN,说明没有数据可用,不会阻塞。


总结:

在使用 select 时,“同步非阻塞”通常指的是:

select 的非阻塞模式:通过将 timeout 设置为 {0, 0},使得 select 立即返回。

描述符的非阻塞模式:通过将描述符设置为非阻塞模式,使得读写操作不会阻塞。

end



CppPlayer 



关注,回复【电子书】珍藏CPP电子书资料赠送

精彩文章合集

专题推荐

【专辑】计算机网络真题拆解
【专辑】大厂最新真题
【专辑】C/C++面试真题拆解

CppPlayer
一个专注面试题拆解的公众号
 最新文章