Socket 的阻塞(Blocking)和非阻塞(NON-blocking)模式是网络编程中两种重要的 I/O 操作方式,主要区别在于程序在等待 I/O 操作完成时的行为。以下是它们的区别:
调用会一直等待,直到操作完成或出错才返回。
线程/进程会被挂起,无法执行其他任务,直到 I/O 操作结束。
适用于简单、同步的网络通信,编程模型直观。
recv():如果没有数据到达,线程会一直阻塞,直到有数据。
send():如果发送缓冲区满,线程会阻塞,直到数据被发送。
accept():如果没有新连接,线程会阻塞,直到有客户端连接。
connect():在 TCP 连接建立成功或失败前,线程会阻塞。
c
char buffer[1024]; int bytes = recv(sockfd, buffer, sizeof(buffer), 0); // 阻塞,直到收到数据或出错 printf("Received: %d bytes\n", bytes);如果没有数据可读,程序会卡在 recv() 处,直到数据到达或连接关闭。
编程简单,逻辑清晰。
适合低并发、同步处理的场景。
并发能力差:每个连接需要一个线程/进程,资源消耗大(C10K 问题)。
响应性差:如果一个连接阻塞,整个线程无法处理其他任务。
调用立即返回,无论操作是否完成。
线程不会被挂起,可以继续执行其他任务。
需要轮询或事件驱动(如 select/poll/epoll) 来检查 I/O 状态。
适用于高并发、异步 I/O 场景(如 Web 服务器、游戏服务器)。
在 Linux 下:
c
int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); // 设置为非阻塞
在 Windows 下:
c
unsigned long mode = 1; ioctlsocket(sockfd, FIONBIO, &mode); // 设置为非阻塞
recv():如果没有数据,立即返回 -1,并设置 errno = EWOULDBLOCK(Linux)或 WSAEWOULDBLOCK(Windows)。
send():如果发送缓冲区满,立即返回 -1,表示无法立即发送。
accept():如果没有新连接,立即返回 -1。
connect():立即返回 -1,并设置 errno = EINPROGRESS(连接仍在进行)。
c
char buffer[1024]; int bytes = recv(sockfd, buffer, sizeof(buffer), 0); if (bytes == -1) {
if (errno == EWOULDBLOCK) {
printf("No data available, try later.\n");
} else {
perror("recv error");
} } else {
printf("Received: %d bytes\n", bytes); }如果没有数据,recv() 不会阻塞,而是立即返回错误,程序可以继续执行其他逻辑。
高并发:单线程可以管理多个 Socket(配合 select/epoll)。
响应性好:不会因为某个 Socket 卡住整个程序。
适合高性能服务器(如 Nginx、Redis)。
编程复杂:需要处理 EWOULDBLOCK 和异步状态。
CPU 占用可能较高(如果使用忙轮询)。
| 特性 | 阻塞模式 | 非阻塞模式 |
|---|---|---|
| 调用行为 | 等待操作完成才返回 | 立即返回,成功/失败需检查 |
| 线程状态 | 挂起,不占用 CPU | 继续执行,可处理其他任务 |
| 并发能力 | 低(需多线程) | 高(单线程 + I/O 多路复用) |
| 编程复杂度 | 简单 | 较复杂(需处理异步状态) |
| 适用场景 | 简单客户端、低并发服务器 | 高并发服务器(Web、游戏、实时系统) |
阻塞模式:适合简单应用,如客户端程序、低并发的服务端(如 FTP)。
非阻塞模式:适合高并发服务器(如 HTTP Server、WebSocket),通常结合 select/poll/epoll(Linux)或 IOCP(Windows)使用。
非阻塞 Socket 通常结合 I/O 多路复用 使用,避免忙轮询:
select() / poll():检查多个 Socket 是否可读/可写(跨平台,但性能一般)。
epoll()(Linux):高效事件通知机制,支持海量连接。
kqueue()(FreeBSD/MacOS):类似 epoll。
IOCP(Windows):异步 I/O 模型。
c
int epfd = epoll_create1(0); struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET; // 边缘触发(Edge-Triggered)
ev.data.fd = sockfd; epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
while (1) {
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (int i = 0; i < n; i++) {
if (events[i].data.fd == sockfd) {
// 有数据可读,调用 recv(不会阻塞)
recv(sockfd, buf, sizeof(buf), 0);
}
} }阻塞 Socket:简单但低效,适合低并发。
非阻塞 Socket:复杂但高效,适合高并发,通常配合 epoll/select 使用。
免责声明: 凡注明来源本网的所有作品,均为本网合法拥有版权或有权使用的作品,欢迎转载,注明出处。非本网作品均来自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。