【问题标题】:Memory/Threads leaks, developing simple HTTP-server with WinSock2内存/线程泄漏,使用 WinSock2 开发简单的 HTTP 服务器
【发布时间】:2012-04-29 14:01:30
【问题描述】:

我开始开发我的工具,它在 TCP 级别与 net 一起工作,它将展示 web-server 的简单功能。

在测试我的程序时,我遇到了非常严重的错误:

  • 内存泄漏
  • 立即创建数千个线程

在 taskmgr.exe 中,您可能会看到约 1.5 个线程和约 50kb 的分配内存。 此外,我将程序编译为 32 位,但在 vmmap 实用程序中您可能会看到很多 64 位堆栈。我的操作系统是 64 位的,但是在 taskmgr.exe 中你可能会看到 *32 ,我不知道 32 位程序如何使用 64 位堆栈,也许在 64 位操作系统中启动 32 位程序是正常的,但我不知道这个操作系统的设计,所以我会很高兴,如果你给我一个关于这个问题的建议。

那么,为什么我的程序会立即创建很多线程? (我猜,while(true) 阻塞的原因).

但是,我想要下一个:

  • 为每个新请求创建每个线程
  • 请求处理完毕后,终止线程并释放内存

我应该如何重新制作我的代码?

谢谢!

这是我的代码(MS VC ++ 9):

#include <iostream>
#include <Windows.h>

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

typedef struct Header
{
friend struct Net;

private:
    WORD wsa_version;
    WSAData wsa_data;

    SOCKET sock;
    SOCKADDR_IN service;

    char *ip;
    unsigned short port;

public:
    Header(void)
    {
        wsa_version = 0x202;

        ip = "0x7f.0.0.1";
        port = 0x51;

        service.sin_family = AF_INET;
        service.sin_addr.s_addr = inet_addr(ip);
        service.sin_port = htons(port);
    }

} Header;

typedef struct Net
{
private:
    int result;

    HANDLE thrd;
    DWORD exit_code;

    void WSAInit(WSAData *data, WORD *wsa_version)
    {
        result = WSAStartup(*wsa_version, &(*data));

        if(result != NO_ERROR)
        {
            std::cout << "WSAStartup() failed with the error: " << result << std::endl;
        }
        else
        {
            std::cout << (*data).szDescription << " " << (*data).szSystemStatus << std::endl;
        }
    }

    void SocketInit(SOCKET *my_socket)
    {
        (*my_socket) = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        if((*my_socket) == INVALID_SOCKET)
        {
            std::cout << "Socket initialization failed with the error: " << WSAGetLastError() << std::endl;
            WSACleanup();
        }
        else
        {
            std::cout << "Socket initialization successful!" << std::endl;
        }
    }

    void SocketBind(SOCKET *my_socket, SOCKADDR_IN *service)
    {
        result = bind((*my_socket), (SOCKADDR*)&(*service), sizeof(*service));

        if(result == SOCKET_ERROR)
        {
            std::cout << "Socket binding failed with the error: " << WSAGetLastError() << std::endl;
            closesocket((*my_socket));
            WSACleanup();
        }
        else
        {
            std::cout << "Socket binding successful!" << std::endl;
        }

        result = listen(*my_socket, SOMAXCONN);

        if(result == SOCKET_ERROR)
        {
            std::cout << "Socket listening failed with the error: " << WSAGetLastError() << std::endl;
        }
        else
        {
            std::cout << "Listening to the socket..." << std::endl;
        }
    }

    static void SocketAccept(SOCKET *my_socket)
    {
        SOCKET sock_accept = accept((*my_socket), 0, 0);

        if(sock_accept == INVALID_SOCKET)
        {
            std::cout << "Accept failed with the error: " << WSAGetLastError() << std::endl;
            closesocket(*my_socket);
            WSACleanup();
        }
        else
        {
            std::cout << "Client socket connected!" << std::endl;
        }

        char data[0x400];
        int result = recv(sock_accept, data, sizeof(data), 0);
        HandleRequest(data, result);
        char *response = "HTTP/1.1 200 OK\r\nServer: Amegas.sys-IS/1.0\r\nContent-type: text/html\r\nSet-Cookie: ASD643DUQE7423HFDG; path=/\r\nCache-control: private\r\n\r\n<h1>Hello World!</h1>\r\n\r\n";
        result = send(sock_accept, response, (int)strlen(response), 0);

        if(result == SOCKET_ERROR)
        {
            std::cout << "Sending data via socket failed with the error: " << WSAGetLastError() << std::endl;
            closesocket(sock_accept);
            WSACleanup();
        }
        else
        {
            result = shutdown(sock_accept, 2);
        }
    }

    static void HandleRequest(char response[], int length)
    {
        std::cout << std::endl;

        for(int i = 0; i < length; i++)
        {
            std::cout << response[i];
        }

        std::cout << std::endl;
    }

    static DWORD WINAPI Threading(LPVOID lpParam)
    {
        SOCKET *my_socket = (SOCKET*)lpParam;
        SocketAccept(my_socket);

        return 0;
    }

public:
    Net(void)
    {
        Header *obj_h = new Header();

        WSAInit(&obj_h->wsa_data, &obj_h->wsa_version);

        SocketInit(&obj_h->sock);
        SocketBind(&obj_h->sock, &obj_h->service);

        while(true)
        {
            thrd = CreateThread(NULL, 0, &Net::Threading, &obj_h->sock, 0, NULL);

            //if(GetExitCodeThread(thrd, &exit_code) != 0)
            //{
            //  ExitThread(exit_code);
            //}
        }

        delete &obj_h;
    }
} Net;

int main(void)
{
    Net *obj_net = new Net();

    delete &obj_net;

    return 0;
}

【问题讨论】:

  • 你不应该delete pointer-to-pointer,所以使用delete obj_net,而不是delete &amp;obj_net。 (或者,最好使用智能指针,例如 boost::scoped_ptr)
  • @Abyx :或者,最好使用标准智能指针,例如 std::unique_ptr
  • @RobertMason,VC++9 中没有unique_ptr
  • 我的错。只是试图让每个人都转换到 C++11。
  • 如果您要学习 C++,我建议您从 learncpp.com 开始 - 这将了解您需要了解的所有基本知识。从我在那个网站上读到的内容来看,英语并不难,这对于母语不是英语的人来说很重要。

标签: c++ multithreading sockets memory-leaks winsock


【解决方案1】:

您应该在接受连接之后创建线程,而不是之前。

您正在做的是创建大量线程,然后让它们中的每一个等待连接。他们中的许多人无事可做。我什至不知道 Windows 的 accept 调用是否是线程安全的——你最终可能会遇到多个线程处理同一个连接。

您需要做的是,在您的主循环(Net 的构造函数 while(true))中,您需要调用 accept()。由于 accept() 阻塞 直到它有一个连接,这将导致主线程等待,直到有人尝试连接。然后,当他们这样做时,您创建另一个线程(或进程 - 更有可能在 UNIX 上)来处理该连接。所以,你的循环现在看起来像这样:

SOCKET sock_accept = accept((*my_socket), 0, 0);

if(sock_accept == INVALID_SOCKET)
{
    std::cout << "Accept failed with the error: " << WSAGetLastError() << std::endl;
    closesocket(*my_socket);
    WSACleanup();
}
else
{
    std::cout << "Client socket connected!" << std::endl;
}
thrd = CreateThread(NULL, 0, &Net::Threading, &obj_h->sock, 0, NULL);
//push back thrd into a std::vector<HANDLE> or something like that
//if you want to keep track of it for later: there's more than one thread

然后,删除您从 SocketAccept 移入此循环的代码。然后,为了美观,我会将 SocketAccept 的名称更改为 SocketHandleConnection。

现在,当您的线程启动时,它已经建立了连接,您需要做的就是处理数据(例如,您从 char data[0x400] 开始执行的操作)。

如果您想处理连接清理,有几种方法可以做到这一点。一,既然你是线程的,你可以让线程自己清理。它与主进程共享内存,因此您可以这样做。但在这个例子中,我没有看到任何你需要清理的东西。

最后,我认为您不了解 ExitThread 的作用。根据 MSDN:

ExitThread 是在 C 代码中退出线程的首选方法。但是,在 C++ 代码中, 在调用任何析构函数或任何其他自动清理之前退出线程 可以执行。因此,在 C++ 代码中,您应该从您的线程函数中返回。

看来您不需要调用 ExitThread - 您只需从函数返回,线程就会自动退出。你不需要从主线程调用它。

最后,你真的应该(如果可以的话)在 c++11 中使用新的标准 C++ 线程,然后如果你付出一点努力将你的代码移植到 boost::asio,你就会拥有一个完全跨平台的应用程序,无需 Windows API C 丑陋:D

免责声明:由于我的大部分经验都与 UNIX 相关,因此我对 Windows 的了解仅略知一二。我试图尽可能准确,但如果我对这些知识如何转换到 Windows 有任何误解,那么我警告你。

【讨论】:

  • 您能帮我突出显示代码(正确的位置),我应该如何更改?非常感谢!
  • 查看我编辑的答案。线程泄漏是通过创建大量无事可做的线程而永远坐下来等待连接。我很惊讶你的代码没有因为创建这么多线程而崩溃。也许 CreateThread 可能会失败,而您没有检查失败?
  • 好吧,我很惊讶。但这很有趣……这能解决你的线程问题吗?
  • 我不明白你最后的评论。 MSVC++ 11 beta 支持它,生产代码也支持它。除此之外,我的意思是改变你的 accept() 调用的位置。
  • 是的,但它确实支持您需要的子集(它支持关键的 80%,剩下的 20% 留给未来的版本,就像大约同时发布的 GCC 4.6)跨度>
【解决方案2】:

为什么要在无限循环中创建线程?当然,这将创建大量线程。我指的是这段代码:

while(true)
    {
        thrd = CreateThread(NULL, 0, &Net::Threading, &obj_h->sock, 0, NULL);
}

【讨论】:

  • 因为可能会有新的连接,我应该如何处理它们?据我了解,对于每个新连接,我必须创建新线程,然后处理它,那么我该如何开发它呢?谢谢!
  • 在接受新套接字后创建线程。但只在一个特殊线程上调用 Accept。
  • 一个特殊线程? “一个特别的”是什么意思?很抱歉提出愚蠢的问题。
  • 只用一个线程接受。并为每个接受的套接字启动一个新线程。
  • 这是主线程。你必须看看为什么你要创建线程。您有一个线程来管理所有其他线程。将此称为您的主线程。然后它会根据需要生成线程来处理来自客户端的连接请求。调用accept() 后,您将获得一个套接字。然后将此套接字提供给线程并告诉它处理该套接字上的连接。
猜你喜欢
  • 1970-01-01
  • 2015-08-14
  • 2012-10-06
  • 2018-07-14
  • 2015-11-06
  • 2013-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多