套接字上的内存泄漏

Posted

技术标签:

【中文标题】套接字上的内存泄漏【英文标题】:Memory leak on socket 【发布时间】:2015-10-21 22:53:53 【问题描述】:

我正在编写一个 tcp 代理,虽然它似乎可以工作,但它留下了内存泄漏。我操纵代码将传入的数据包转发给自己,以创建 10000 个套接字并关闭它们以查看泄漏的位置。但是我无法弄清楚。我使用了 deleaker,它没有显示任何泄漏(除了一个我不在乎的小泄漏。)

然后我取消勾选这两个框,这出来了。

任何帮助将不胜感激!

代码:

#include <winsock2.h>
#include <stdio.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <tchar.h>
#include <process.h> /* _beginthread() */

// Need to link with Ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

#define PORT    "1234" /* Port to listen on */
#define BUF_SIZE 4096    /* Buffer for  transfers */

typedef struct 

    char *host;
    char *port;
    SOCKET sock;


HandleStruct;

unsigned int S2C(SOCKET from, SOCKET to)


    char buf[BUF_SIZE];
    unsigned int disconnected = 0;
    size_t bytes_read, bytes_written;
    bytes_read = recv(from, buf, BUF_SIZE, 0);
    if (bytes_read == 0) 

        disconnected = 1;

    

    else 

        bytes_written = send(to, buf, bytes_read, 0);
        if (bytes_written == -1) 

            disconnected = 1;

        


    

    return disconnected;



unsigned int C2S(SOCKET from, SOCKET to)


    char buf[BUF_SIZE];
    unsigned int disconnected = 0;
    size_t bytes_read, bytes_written;
    bytes_read = recv(from, buf, BUF_SIZE, 0);
    if (bytes_read == 0) 

        disconnected = 1;

    

    else 

        bytes_written = send(to, buf, bytes_read, 0);
        if (bytes_written == -1) 

            disconnected = 1;

        


    

    return disconnected;



void handle(void *param)


    HandleStruct *args = (HandleStruct*) param;
    SOCKET client = args->sock;
    const char *host = args->host;
    const char *port = args->port;
    SOCKET server = -1;
    unsigned int disconnected = 0;
    fd_set set;
    unsigned int max_sock;
    struct addrinfo *res = NULL;
    struct addrinfo *ptr = NULL;
    struct addrinfo hints;
    /* Get the address info */
    ZeroMemory( &hints, sizeof(hints) );
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    if (getaddrinfo(host, port, &hints, &res) != 0) 

        perror("getaddrinfo");
        closesocket(client);
        return;

    

    /* Create the socket */
    server = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (server == INVALID_SOCKET) 

        perror("socket");
        closesocket(client);
        return;

    

    /* Connect to the host */
    if (connect(server, res->ai_addr, res->ai_addrlen) == -1) 

        perror("connect");
        closesocket(client);
        return;

    

    if (client > server) 

        max_sock = client;

    

    else 

        max_sock = server;

    

    /* Main transfer loop */
    while (!disconnected) 

        FD_ZERO(&set);
        FD_SET(client, &set);
        FD_SET(server, &set);
        if (select(max_sock + 1, &set, NULL, NULL, NULL) == SOCKET_ERROR) 

            perror("select");
            break;

        

        if (FD_ISSET(client, &set)) 

            disconnected = C2S(client, server);

        

        if (FD_ISSET(server, &set)) 

            disconnected = S2C(server, client);

        


    

    closesocket(server);
    closesocket(client);
fprintf(stderr, "Sockets Closed: %d/%d", server, client);
    _endthread();
    return;



int _tmain(int argc)


    WORD wVersion = MAKEWORD(2, 2);
    WSADATA wsaData;
    int iResult;
    SOCKET sock;
    struct addrinfo hints, *res;
    int reuseaddr = 1; /* True */
    /* Initialise Winsock */
    if (iResult = (WSAStartup(wVersion, &wsaData)) != 0) 

        fprintf(stderr, "WSAStartup failed: %dn", iResult);
        return 1;

    

    char * host = "127.0.0.1";
    char * port = "1234";

    /* Get the address info */
    ZeroMemory(&hints, sizeof hints);
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    if (getaddrinfo(NULL, PORT, &hints, &res) != 0) 

        perror("getaddrinfo");
        WSACleanup();
        return 1;

    

    /* Create the socket */
    sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (sock == INVALID_SOCKET) 

        perror("socket");
        WSACleanup();
        return 1;

    

    /* Enable the socket to reuse the address */
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseaddr,
    sizeof(int)) == SOCKET_ERROR) 

        perror("setsockopt");
        WSACleanup();
        return 1;

    

    /* Bind to the address */
    if (bind(sock, res->ai_addr, res->ai_addrlen) == SOCKET_ERROR) 

        perror("bind");
        WSACleanup();
        return 1;

    

    /* Listen */
    if (listen(sock, 6500) == SOCKET_ERROR) 

        perror("listen");
        WSACleanup();
        return 1;

    

    freeaddrinfo(res);
    int i = 0;

    HandleStruct *arg;
    arg = (HandleStruct *)malloc(sizeof( HandleStruct));
/* Main loop */
    while(1) 

        int size = sizeof(struct sockaddr);
        struct sockaddr_in their_addr;
        SOCKET newsock;
        ZeroMemory(&their_addr, sizeof (struct sockaddr));
        newsock = accept(sock, (struct sockaddr*)&their_addr, &size);

        if (newsock == INVALID_SOCKET) 

            perror("acceptn");

        
        else 

            arg->sock = newsock;
            arg->host = host;
            arg->port = port;

            if (i < 10000) 
                _beginthread(handle, 0, (void*) arg);
                i++;
            
        
    

    closesocket(sock);
    WSACleanup();
    return 0;


【问题讨论】:

【参考方案1】:

我不熟悉阅读您发布的屏幕截图中的程序;但是,您可能应该关注这一行:

arg = (HandleStruct *)malloc(sizeof( HandleStruct));

在这里,您通过malloc()HandleStruct 分配内存,随后调用free() 似乎没有在任何地方清理。您将arg 传递给handle(),但仍不释放内存。

似乎清理 arg 不是 handle() 的责任,所以你应该在 while 循环之后调用 free(),或者你可以在每个循环的开始分配HandleStruct,并在结束时释放它。

或者您可以省去麻烦并使用std::unique_ptr,并且可以选择将您的线程更改为std::thread,它会自行记录谁拥有内存等:

void handle(std::unique_ptr<HandleStruct> args)

    // Manipulate args
    ...


int main()

    std::unique_ptr<HandleStruct> pHandle = std::make_unique<HandleStruct>();
    for (;;)
    
        ...
        pHandle->sock = newsock;
        pHandle->host = host;
        pHandle->port = port;
        // Create thread:
        std::thread t(&handle, pHandle);
        // Wait for thread to finish so pHandle doesn't change while we are using it on another thread
        // t.join();
    

【讨论】:

malloc 只会导致一个 48 字节的内存泄漏。程序本身在启动时使用 1200kb,当我在其上运行 10000 个线程时,内存使用量直接上升到 5300kb,如果考虑创建的线程数,这是合乎逻辑的。但是由于线程被_endthread停止,不应该释放内存吗? 我也不能使用你的方法,因为我不能等待线程完成。我需要在每个新套接字上创建一个新的并发线程。 以另一种方式完成,尽管以您的答案为指导! @Asmo 很高兴您设法修复它!我对std::thread::join() 的使用被注释掉了:我添加了它以防你需要它,但我认为你没有,所以我把它注释掉了。使用std::thread时,绝不需要使用std::thread::join【参考方案2】:

每个套接字都使用操作系统中的一些内存。

这里是 Linux 中的描述:accept

   ENOBUFS, ENOMEM
          Not enough free memory.  This often means that the memory
          allocation is limited by the socket buffer limits, not by the
          system memory.

操作系统可能不会清理它们。

您还尝试创建 10000 个线程,如果在 errno 设置为 EAGAIN 的情况下创建不久之前没有失败,这些可能也会占用一些内存。

【讨论】:

以上是关于套接字上的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

QTcpSocket 内存泄漏

C套接字读取功能导致内存泄漏

是啥导致了netty中的这种内存泄漏

pthread_create() 和内存泄漏

NSArray 上的 Swift 内存泄漏

Windows 7 上的 C++ 内存泄漏