news 2026/8/7 1:06:10

阻塞和非阻塞

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
阻塞和非阻塞

“阻塞”和“非阻塞”主要是在说:当一个操作暂时无法完成时,调用者是停在那里等,还是立刻返回。

它经常出现在:

read() write() recv() send() accept() connect()

尤其是网络编程里。

一、阻塞是什么意思

阻塞就是:函数暂时做不了,就一直等,直到能做为止。

比如:

char buf[1024]; recv(sockfd, buf, sizeof(buf), 0);

如果sockfd是阻塞 socket,并且现在没有数据可读,那么recv会卡住。

程序会停在这里:

recv(sockfd, buf, sizeof(buf), 0); // 没数据就不往下走

直到发生下面某种情况:

对端发来了数据

对端关闭连接

出错

被信号打断

设置了超时并超时

所以阻塞 I/O 的感觉是:

你要读数据? 现在没数据。 那你在这里等着。

二、 非阻塞是什么意思

非阻塞就是:函数暂时做不了,也不会等,而是马上返回。

同样是:

recv(sockfd, buf, sizeof(buf), 0);

如果sockfd是非阻塞 socket,并且现在没有数据可读,那么recv不会卡住,而是返回:

-1

并设置:

errno == EAGAIN || errno == EWOULDBLOCK

意思是:

现在没数据,你之后再试。

所以非阻塞 I/O 的感觉是:

你要读数据? 现在没数据。 我不等,直接告诉你暂时不能读。

三、 一个直观比喻

阻塞:

你去奶茶店买奶茶。 店员说还没做好。 你站在柜台前一直等,什么都不干。 等做好后,拿到奶茶再走。

非阻塞:

你去奶茶店问好了没。 店员说还没好。 你马上离开,去做别的事。 过一会儿再来问。

在程序里,“站在那里等”就是线程被阻塞。

四、阻塞 read/recv 示例

char buf[1024]; ssize_t n = recv(fd, buf, sizeof(buf), 0); std::cout << "recv returned\n";

如果fd是阻塞的,并且没有数据,那么:

std::cout << "recv returned\n";

这一行不会执行,直到recv返回。

也就是说,整个线程卡在recv上。

五、 非阻塞 recv 示例

先把 socket 设置成非阻塞:

#include <fcntl.h> #include <unistd.h> void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); }

然后:

char buf[1024]; ssize_t n = recv(fd, buf, sizeof(buf), 0); if (n > 0) { // 读到了 n 字节 } else if (n == 0) { // 对端关闭连接 } else { if (errno == EAGAIN || errno == EWOULDBLOCK) { // 现在没数据,不是错误,之后再读 } else { // 真正的错误 } }

非阻塞 I/O 的核心就是要处理:

EAGAIN EWOULDBLOCK

它们表示“现在做不了,但不是致命错误”。

六、accept 的阻塞和非阻塞

accept用来接受新连接。

阻塞模式下:

int client_fd = accept(listen_fd, nullptr, nullptr);

如果没有客户端连接进来,程序就卡住。

非阻塞模式下:

int client_fd = accept(listen_fd, nullptr, nullptr);

如果暂时没有新连接,返回-1,并且:

errno == EAGAIN || errno == EWOULDBLOCK

所以非阻塞服务器里经常这样写:

while (true) { int client_fd = accept(listen_fd, nullptr, nullptr); if (client_fd >= 0) { set_nonblocking(client_fd); // 加入 epoll } else { if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } // 其他错误 break; } }

这里的while很常见,尤其是epoll的边缘触发模式下。

七、 send/write 的阻塞和非阻塞

很多人以为send一定能立刻把数据发出去,其实不是。

阻塞 socket:

send(fd, data, len, 0);

如果内核发送缓冲区满了,send会卡住,直到缓冲区有空间。

非阻塞 socket:

ssize_t n = send(fd, data, len, 0);

如果发送缓冲区满了,它返回:

-1

并设置:

errno == EAGAIN || errno == EWOULDBLOCK

还有一个重要点:send可能只发送一部分。

比如你想发 10000 字节:

ssize_t n = send(fd, data, 10000, 0);

返回值可能是:

3000

意思是只发出去了 3000 字节,剩下 7000 字节要你之后继续发。

所以非阻塞网络程序里一般要有发送缓冲区:

struct Connection { int fd; std::string write_buffer; };

没发完的数据放进write_buffer,等下次EPOLLOUT再发。

八、connect 的阻塞和非阻塞

connect也有区别。

阻塞模式:

connect(fd, addr, len);

会一直等到连接成功或失败。

非阻塞模式下:

int ret = connect(fd, addr, len);

如果连接还在进行中,通常返回:

-1

并且:

errno == EINPROGRESS

意思是:

连接正在建立,还没完成。

这时你可以用epoll监听这个 fd 的可写事件:

EPOLLOUT

等它可写时,再用getsockopt检查是否连接成功。

九、阻塞的问题

阻塞本身不是坏事。

简单程序里,阻塞很好用:

while (true) { int client = accept(listen_fd, nullptr, nullptr); recv(client, buf, sizeof(buf), 0); send(client, response, response_len, 0); }

代码直观。

但高并发服务器里,阻塞会带来问题。

假设一个线程处理一个连接:

10000 个连接 = 10000 个线程

这会有:

线程创建成本高

上下文切换成本高

栈内存占用大

调度开销大

慢连接会占住线程

如果一个客户端一直不发数据,线程就一直卡在recv上。

十、非阻塞的问题

非阻塞也不是万能的。

如果你这么写:

while (true) { ssize_t n = recv(fd, buf, sizeof(buf), 0); if (n < 0 && errno == EAGAIN) { continue; } }

这会疯狂空转,CPU 直接飙高。

因为没有数据时,你一直问:

有数据吗? 没有。 有数据吗? 没有。 有数据吗? 没有。

所以非阻塞 I/O 通常要配合:

select / poll / epoll

也就是:

没有事件时睡眠; 有事件时再尝试读写。

这就是epoll的作用。

十一、 epoll 和非阻塞的关系

epoll通常配合非阻塞 fd 使用。

典型流程:

把 socket 设为非阻塞 注册到 epoll epoll_wait 等事件 有 EPOLLIN 再 recv 读到 EAGAIN 停止 继续 epoll_wait

代码结构像这样:

while (true) { int n = epoll_wait(epfd, events, 1024, -1); for (int i = 0; i < n; ++i) { int fd = events[i].data.fd; if (events[i].events & EPOLLIN) { while (true) { ssize_t ret = recv(fd, buf, sizeof(buf), 0); if (ret > 0) { // 处理数据 } else if (ret == 0) { // 对端关闭 close(fd); break; } else { if (errno == EAGAIN || errno == EWOULDBLOCK) { break; } close(fd); break; } } } } }

这就是高性能网络编程里最经典的模式之一。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/7 1:02:54

零基础逆袭成为Web全栈工程师:选择北京网站建设培训班开启高薪职业转型之路

在北京这座充满机遇与挑战的超级大都市里,每天都有成千上万的年轻人带着梦想奔波在地铁站、写字楼和出租屋之间。很多人问我,在这个技术迭代快如闪电、AI似乎正在颠覆一切的行业里,普通人到底还有没有机会通过系统学习掌握硬核技能,实现职业翻身?我的回答永远都是肯定的,…

作者头像 李华
网站建设 2026/8/7 0:59:40

零基础想转行网安,这份白帽黑客成长路线图请收好

先划清界限&#xff1a;白帽与黑帽的本质区别 提到“黑客”&#xff0c;很多人脑海里浮现的往往是电影里那种敲几下键盘就能瘫痪整个银行系统的神秘角色。这种艺术加工让黑客群体蒙上了一层亦正亦邪的色彩&#xff0c;但在现实的技术世界里&#xff0c;这条界线分得比刀锋还锐利…

作者头像 李华
网站建设 2026/8/7 0:54:25

2024年深度解析网站建设需要注意哪些核心细节以确保商业成功

咱们今天不聊那些虚无缥缈的理论,也不整那些看着高大上但根本落不了地的黑话。我就想以一个过来人的身份,跟各位老板、创业者或者是负责这块工作的同事,掏心窝子聊聊一件事,那就是网站建设需要注意什么。说实话,很多人有个误区,觉得建站这事特别简单。找个模板,拖拖拽拽…

作者头像 李华
网站建设 2026/8/7 0:49:04

DeepSeek LeetCode 3841. 查询树上回文路径 Java实现

解题思路这道题的核心在于如何高效判断树中任意两点路径上的字符能否重排为回文串。回文串的判定条件&#xff1a;一个字符串能重排成回文串&#xff0c;当且仅当其出现奇数次的字符最多只有一个。例如 "aac" 中 a 出现2次&#xff08;偶&#xff09;&#xff0c;c 出…

作者头像 李华
网站建设 2026/8/7 0:45:44

如何选择优质的网站建设招标方案以打造高转化率数字化营销入口并避开隐形陷阱

说实话,写这篇文章之前,我反复掂量了很久。因为在互联网行业摸爬滚打这么多年,我见过的因为一个错误的“网站建设招标方案”而让整个项目烂尾的案例,简直比过春节返乡堵车时加塞的车还多。很多老板或者是负责项目的同事,拿到招标文件的那一刻,心里想的往往是“赶紧发出去…

作者头像 李华
网站建设 2026/8/7 0:37:33

长沙3合1网站建设如何助力中小企业低成本实现数字化转型与高效获客全攻略

在这个移动互联网飞速迭代的时代,对于咱们长沙的中小企业主或者创业团队来说,最头疼的问题往往不是产品不够好,也不是服务不够硬,而是“酒香也怕巷子深”。以前大家觉得,开个店在繁华地段就好,只要人来了,生意自然少不了。但现在不一样了,大家的注意力都被手机屏幕截胡…

作者头像 李华