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

poll()提示有消息但recv()无消息且阻塞的Socket问题

Socket服务器poll()误报可读导致recv()阻塞问题

搭建基础Socket服务器,使用Google Chrome通过localhost测试消息接收功能。此前遇到recv()阻塞问题,查阅select()与poll()区别后改用poll()方案。当前代码能获取消息,但poll()始终提示有消息可读,实际调用recv()时出现阻塞。

简化代码

/*
request is a pointer to a struct with the following members:
char *data;
int size;
*/
request->data = (char *)malloc(1);
char *data_ptr = request->data;
if (request->data == NULL)
{
    perror("Error allocating memory");
    return 8;
}
request->size = 1;
struct pollfd fds[1];
struct pollfd read;
read.fd = *connectionptr;
read.events = POLL_IN | POLL_PRI;
fds[0] = read;
int available_ops = poll(fds, 1, 0);
while (available_ops > 0)
{
    if (fds[0].revents & POLL_IN || fds[0].revents & POLL_PRI)
    {
        if (recv(*connectionptr, data_ptr, 1, 0) < 0)
        {
            perror("Error receiving data");
            return 6;
        }
        request->data = (char *)realloc(request->data, request->size + 1);
        request->size++;
        data_ptr = request->data + request->size - 1;
        if (request->data == NULL)
        {
            perror("Error allocating mem");
            return 8;
        }
    }
    available_ops = poll(fds, 1, 0);
    printf("Read %s thus far, %d characters more to read\n", request->data, available_ops);
}

运行输出

Read GET / HTTP/1.1
Host: 127.0.0.1:443
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

thus far, 1 characters more to read

程序在此处停滞,调试确认是recv()调用导致阻塞。Socket已配置SO_KEEPALIVE(关闭后问题仍存在)与SO_REUSEADDR。

完整可复现代码

main.c

#include "server.h"

int main(void)
{
    int socket_desc = socket(AF_INET6, SOCK_STREAM, 0);
    if (socket_desc < 0)
    {
        perror("Error initializing socket");
        close(socket_desc);
        return 1;
    }
    printf("Socket created\n");
    int reuse = 1;
    int keep_alive = 1;
    if (setsockopt(socket_desc, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
    {
        perror("Socket Address Reuse could not be set");
        close(socket_desc);
        return 2;
    }
    printf("Socket set to reuse address\n");
    if (setsockopt(socket_desc, SOL_SOCKET, SO_KEEPALIVE, &keep_alive, sizeof(keep_alive)) < 0)
    {
        perror("Keep Alive could not be activated");
        close(socket_desc);
        return 3;
    }
    printf("Socket set to keep connection alive\n");
    struct sockaddr_in6 address;
    memset(&address, 0, sizeof(address));
    address.sin6_family = AF_INET6;
    address.sin6_port = htons(443);
    address.sin6_addr = in6addr_any;
    int binding = bind(socket_desc, (struct sockaddr *)&address, sizeof(address));
    if (binding < 0)
    {
        perror("Bind failed");
        close(socket_desc);
    }
    printf("Socket bound to port 443\n");
    if (listen(socket_desc, 5) < 0)
    {
        perror("Error in listening");
        close(socket_desc);
        return 4;
    }
    printf("Listening...\n");
    struct sockaddr client_info = {0};
    socklen_t client_info_length = sizeof(client_info);
    int soc_conn = accept(socket_desc, &client_info, &client_info_length);
    if (soc_conn < 0)
    {
        perror("Error accepting connection to client");

        close(socket_desc);
        return 5;
    }
    struct chararr receive_buffer;
    int code = process_request(&soc_conn, &receive_buffer);
    if (code != 0)
    {
        free(receive_buffer.data);
        close(socket_desc);
        close(soc_conn);
        return code;
    }

    printf("Message Sent!\n");
    printf("ending program...\n");
    close(socket_desc);
    close(soc_conn);
    free(receive_buffer.data);
    return 0;
}

int process_request(int *connectionptr, struct chararr *request)
{
    request->data = (char *)malloc(1);
    char *data_ptr = request->data;
    if (request->data == NULL)
    {
        perror("Error allocating memory");
        return 8;
    }
    request->size = 1;
    printf("Receiving Data\n");
    struct pollfd fds[1];
    struct pollfd readcheck;
    readcheck.fd = *connectionptr;
    readcheck.events = POLL_IN | POLL_PRI;
    fds[0] = readcheck;
    int available_ops = poll(fds, 1, 0);
    while (available_ops > 0)
    {
        
        if (fds[0].revents & POLL_IN || fds[0].revents & POLL_PRI)
        {
            if (recv(*connectionptr, data_ptr, 1, 0) < 0)
            {
                perror("Error receiving data");
                return 6;
            }
            request->data = (char *)realloc(request->data, request->size + 1);
            request->size++;
            data_ptr = request->data + request->size - 1;
            if (request->data == NULL)
            {
                perror("Error allocating mem");
                return 8;
            }
        }
        available_ops = poll(fds, 1, 0);
        printf("Read %s thus far, %d characters more to read\n", request->data, available_ops);
    }
    return 0;
}

server.h

#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <poll.h>

struct chararr
{
    char *data;
    int size;
};

int process_request(int *connectionptr, struct chararr *request);

问题原因及修复方案

核心问题

  1. poll()的revents未重置:每次调用poll()前未清空fds[0].revents,旧事件标志会保留,导致后续poll()误判为有数据可读。
  2. recv()返回值未完全处理:仅判断recv() < 0的错误情况,未处理recv() == 0(客户端关闭连接)的场景,此时poll()会返回POLL_IN,但recv()无数据可读,最终导致阻塞。
  3. 内存分配与字符串终止问题:接收的字符串未添加\0终止符,导致printf输出异常,且无法正确识别请求结束标识。

修复步骤

  1. 重置revents:每次调用poll()前将fds[0].revents设为0,避免旧事件干扰。
  2. 处理recv()所有返回值:判断recv()返回0时退出循环;返回大于0时处理接收到的字节数。
  3. 添加字符串终止符:接收完成后为request->data添加\0,确保字符串格式正确。
  4. 设置poll()超时:避免无限等待,可设置合理超时时间。

修复后的process_request函数示例

int process_request(int *connectionptr, struct chararr *request)
{
    request->size = 0;
    // 初始分配更大内存,减少realloc次数
    request->data = (char *)malloc(128);
    if (request->data == NULL)
    {
        perror("Error allocating memory");
        return 8;
    }
    char *data_ptr = request->data;
    printf("Receiving Data\n");

    struct pollfd fds[1];
    fds[0].fd = *connectionptr;
    fds[0].events = POLL_IN | POLL_PRI;

    while (1)
    {
        fds[0].revents = 0; // 重置事件标志
        // 设置5秒超时,避免无限阻塞
        int available_ops = poll(fds, 1, 5000);

        if (available_ops < 0)
        {
            perror("poll failed");
            return 7;
        }
        else if (available_ops == 0)
        {
            printf("poll timeout\n");
            break;
        }

        if (fds[0].revents & (POLL_IN | POLL_PRI))
        {
            // 预留1字节给终止符,避免缓冲区溢出
            ssize_t recv_len = recv(*connectionptr, data_ptr, 127 - request->size, 0);
            if (recv_len < 0)
            {
                perror("Error receiving data");
                return 6;
            }
            else if (recv_len == 0)
            {
                printf("Client closed connection\n");
                break;
            }

            request->size += recv_len;
            data_ptr += recv_len;

            // 缓冲区不足时扩容
            if (request->size >= 128 - 1)
            {
                char *new_data = realloc(request->data, request->size * 2);
                if (new_data == NULL)
                {
                    perror("Error reallocating memory");
                    return 8;
                }
                request->data = new_data;
                data_ptr = request->data + request->size;
            }

            // 检查HTTP请求是否结束(空行标识)
            if (request->size >= 4 && memcmp(data_ptr - 4, "\r\n\r\n", 4) == 0)
            {
                printf("Received full HTTP request\n");
                break;
            }
        }

        printf("Read %.*s thus far, %d events pending\n", request->size, request->data, available_ops);
    }

    // 添加字符串终止符
    request->data[request->size] = '\0';
    return 0;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 19:18:09