欢迎关注本公众号,专注面试题拆解
分享一套视频课程:《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电子书资料赠送
精彩文章合集
专题推荐