You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言客户端recv调用阻塞,服务端已确认收数,仅MacOS存在该问题

macOS下C语言TCP客户端recv阻塞问题排查

我用C语言编写了一个简单的TCP客户端-服务端程序,服务端接收客户端消息后返回响应。服务端能正常接收消息并打印Received: Hello Server后退出,但客户端发送消息后卡在recv调用处,尽管Wireshark显示服务端的响应已发送并得到确认。该问题仅在macOS上出现,其他系统可正常运行。

client.c

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("Could not create socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serv_addr;

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);

    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        perror("Could not generate IP-address");
        exit(EXIT_FAILURE);
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Could not connect to server");
        exit(EXIT_FAILURE);
    }

    if (send(sock, "Hello Server", strlen("Hello Server"), 0) < 0) {
        perror("Could not send message");
        exit(EXIT_FAILURE);
    }

    char buffer[1024] = {0};

    if (recv(sock, buffer, 1023, 0) < 0) {
        perror("Could not receive message from server");
        exit(EXIT_FAILURE);
    }

    printf("%s\n", buffer);
    close(sock);

    return 0;
}

server.c

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("Could not create socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in address;
    socklen_t addrlen = sizeof(address);

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8080);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("Could not bind to host address");
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 1) < 0) {
        perror("Could not start listening");
        exit(EXIT_FAILURE);
    }

    int client_socket = accept(server_fd, (struct sockaddr *)&address, &addrlen);
    if (client_socket < 0) {
        perror("Could not accept client");
        exit(EXIT_FAILURE);
    }

    char buffer[1024] = {0};
    if (recv(client_socket, buffer, 1023, 0) < 0) {
        perror("Could not receive message");
        exit(EXIT_FAILURE);
    }

    printf("Received: %s\n", buffer);

    if (send(client_socket, "Hello World", strlen("Hello World"), 0) < 0) {
        perror("Could not send message");
        exit(EXIT_FAILURE);
    }

    shutdown(client_socket, SHUT_RDWR); // wait for message to be received
    close(server_fd);

    return 0;
}

服务端运行输出:

Received: Hello Server

问题原因分析

这是代码bug,并非macOS的网络特性导致。问题出在服务端的资源清理步骤:

服务端在发送响应后,仅调用shutdown(client_socket, SHUT_RDWR)并关闭了监听socket(server_fd),但没有显式关闭与客户端通信的client_socket文件描述符。虽然进程退出时内核会自动回收打开的文件描述符,但在macOS的TCP栈实现中,这种未显式关闭客户端socket的操作会导致客户端无法正确感知连接状态的变化:

  1. 服务端发送的"Hello World"确实已到达客户端接收缓冲区,但客户端的recv是阻塞调用,在没有收到连接关闭的FIN包时,会一直等待更多数据;
  2. 服务端的shutdown(SHUT_RDWR)虽然关闭了socket的读写流,但未关闭文件描述符,macOS内核可能不会立即向客户端发送FIN包,导致客户端不知道这是最后一批数据,从而卡在recv中。

修复方法

修改服务端代码,在发送响应后显式关闭client_socket,再关闭监听socket:

// 替换原代码中的shutdown和close部分
close(client_socket); // 先关闭客户端连接socket
close(server_fd);     // 再关闭监听socket

或者保留shutdown但补充close(client_socket):

shutdown(client_socket, SHUT_RDWR);
close(client_socket); // 显式关闭客户端socket
close(server_fd);

这样能确保macOS内核正确发送FIN包,客户端的recv在读取完响应数据后会正常返回,不会再阻塞。

内容的提问来源于stack exchange,提问作者Viliam Holly

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:35:55