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

Windows7下C++ Socket接收大量数据块失败的问题求助

问题:TCP Socket接收大量数据块中途停止的解决方法

我在Windows 7电脑上尝试使用C++的Socket流式传输多个代表图像的数据块,使用的代码如下:

int main() {
    std::string _ipaddress = "192.168.0.1"; 
    int dport = 6342;
    
    SOCKADDR_IN dtarget;
    dtarget.sin_family = AF_INET;
    dtarget.sin_port = htons(dport);
    dtarget.sin_addr.s_addr = inet_addr(_ipaddress.c_str());

    SOCKET _data_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (connect(_data_sock, (SOCKADDR*)&dtarget, sizeof(dtarget)) == SOCKET_ERROR) {
        return 1;
    }

    for (int i = 0; i < 100000; i++) {
        fd_set read_flags, write_flags;
        FD_ZERO(&read_flags);
        FD_ZERO(&write_flags);
        FD_SET(_data_sock, &read_flags);

        struct timeval waitd;
        waitd.tv_sec = 5;
        int sel;
        sel = select(_data_sock + 1, &read_flags, &write_flags, (fd_set*)0, &waitd);

        if (FD_ISSET(_data_sock, &read_flags)) {
            FD_CLR(_data_sock, &read_flags);

            int readCount = 0;
            int nread = 0;
            int chunkSize = 8576;
            char* chunkBuf = (char*)calloc(chunkSize, 1);
            do {
                if ((nread = recv(_data_sock, chunkBuf + readCount, chunkSize - readCount, 0)) != SOCKET_ERROR) {
                    readCount += nread;
                };
            } while (nread != 0 && readCount < chunkSize && nread != SOCKET_ERROR);
            // do something with chunkBuf
        }
    }
    return 0;
}

程序先连接SOCKET _data_sock后进入for循环,在循环内声明、初始化并设置fd_set,随后使用recv()接收数据块到缓冲区chunkBuf。

代码能正常接收约30000个数据块(数量略有浮动),但之后突然无法继续接收数据。我怀疑是文件描述符数量限制,但将相关声明移到循环外也无法解决问题。请问如何修改代码以支持接收无限数量的数据块?


问题分析与修复方案

核心问题拆解

  1. 内存泄漏:每次循环都用calloc分配chunkBuf但未释放,3万次循环后内存占用超200MB,系统内存耗尽导致程序停摆。
  2. select处理不完整:未处理select返回错误或超时的情况,可能导致后续数据接收逻辑被跳过。
  3. 循环次数限制:for循环最多仅执行10万次,无法实现无限接收。
  4. Winsock初始化缺失:Windows下必须调用WSAStartup初始化套接字库,原代码遗漏了这一步,可能导致隐性错误。

修改后的完整代码

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>

#pragma comment(lib, "ws2_32.lib")

int main() {
    // 初始化Winsock库(Windows下必填)
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup初始化失败" << std::endl;
        return 1;
    }

    std::string _ipaddress = "192.168.0.1"; 
    int dport = 6342;
    
    SOCKADDR_IN dtarget;
    dtarget.sin_family = AF_INET;
    dtarget.sin_port = htons(dport);
    // 替换不安全的inet_addr,用inet_pton解析IP地址
    if (inet_pton(AF_INET, _ipaddress.c_str(), &dtarget.sin_addr) != 1) {
        std::cerr << "IP地址格式无效" << std::endl;
        WSACleanup();
        return 1;
    }

    SOCKET _data_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (_data_sock == INVALID_SOCKET) {
        std::cerr << "创建套接字失败:" << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    if (connect(_data_sock, (SOCKADDR*)&dtarget, sizeof(dtarget)) == SOCKET_ERROR) {
        std::cerr << "连接失败:" << WSAGetLastError() << std::endl;
        closesocket(_data_sock);
        WSACleanup();
        return 1;
    }

    // 缓冲区移到循环外,重复使用避免内存泄漏
    const int chunkSize = 8576;
    char* chunkBuf = (char*)calloc(chunkSize, 1);
    if (!chunkBuf) {
        std::cerr << "内存分配失败" << std::endl;
        closesocket(_data_sock);
        WSACleanup();
        return 1;
    }

    // 无限循环接收,直到连接断开或出错
    while (true) {
        fd_set read_flags;
        FD_ZERO(&read_flags);
        FD_SET(_data_sock, &read_flags);

        struct timeval waitd;
        waitd.tv_sec = 5;
        waitd.tv_usec = 0; // 补全微秒字段,避免select行为异常
        int sel = select(_data_sock + 1, &read_flags, nullptr, nullptr, &waitd);

        if (sel == SOCKET_ERROR) {
            std::cerr << "select调用失败:" << WSAGetLastError() << std::endl;
            break;
        } else if (sel == 0) {
            // 超时处理,可根据需求调整(比如打印日志或继续等待)
            std::cout << "接收超时,继续等待..." << std::endl;
            continue;
        }

        if (FD_ISSET(_data_sock, &read_flags)) {
            int readCount = 0;
            int nread = 0;
            do {
                nread = recv(_data_sock, chunkBuf + readCount, chunkSize - readCount, 0);
                if (nread == SOCKET_ERROR) {
                    std::cerr << "接收数据失败:" << WSAGetLastError() << std::endl;
                    goto cleanup;
                } else if (nread == 0) {
                    // 对方主动关闭连接
                    std::cout << "连接已被对方关闭" << std::endl;
                    goto cleanup;
                }
                readCount += nread;
            } while (readCount < chunkSize);

            // 处理接收到的数据块
            // do something with chunkBuf

            // 重置缓冲区(如果需要)
            memset(chunkBuf, 0, chunkSize);
        }
    }

cleanup:
    // 统一清理资源
    free(chunkBuf);
    closesocket(_data_sock);
    WSACleanup();
    return 0;
}

关键修改说明

  1. 解决内存泄漏:将缓冲区分配移到循环外,循环内重复使用,退出时统一释放。
  2. 完善Winsock生命周期管理:添加WSAStartup初始化和WSACleanup清理,符合Windows套接字规范。
  3. 处理所有异常情况:覆盖select错误、超时,recv错误、连接关闭等场景,避免程序无响应。
  4. 移除次数限制:用while(true)实现无限接收,直到连接正常断开或出错。
  5. 优化IP解析:用inet_pton替代inet_addr,支持更规范的IP地址格式且安全性更高。
  6. 补全timeval字段:初始化tv_usec,避免select因未初始化字段产生异常行为。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 02:17:34