【问题标题】:memory leak with sockets and map带有套接字和映射的内存泄漏
【发布时间】:2012-05-20 02:39:04
【问题描述】:

我有一个套接字服务器,每次建立新连接时,都会实例化一个 XClient 类,并将其插入到地图中。我正在通过任务管理器查看内存使用情况。例如,每次建立新连接时,我的程序的内存使用量都会增加 800kb。在该类中,有一个连接变量,它将告诉我该客户端是否处于活动状态。我创建了一个线程来无休止地运行并遍历我的地图的所有元素,并且我正在检查连接的变量是真还是假。如果它是假的,我(至少我认为我是......)释放先前实例化的 XClient 类使用的内存。但是,内存使用量仅减少了 800kb 的一半(例如,没有精确值)。因此,当客户端连接时:+800kb。当客户端断开连接时:-400kb。我想我有内存泄漏?如果我连接了 100 个客户端,那么未释放的 400kb 将变成未使用的 4000kb(?)内存,这将是一个问题。

所以,这是我的代码。 遍历所有元素的线程:

DWORD Update(XSockets *sockets)
{
while(true)
{
    for(sockets->it = sockets->clients.begin(); sockets->it != sockets->clients.end(); sockets->it++)
    {
        int key = (*sockets->it).first;
        if(sockets->clients[key]->connected == false) // remove the client, releasing memory
        {
            delete sockets->clients[key];
        }
    }
    Sleep(100);
}
return true;
}

将新的 XClients 实例添加到我的地图的代码:

bool XSockets::AcceptConnections()
{
struct sockaddr_in from;

while(true)
{
    try
    {
        int fromLen = sizeof(from);
        SOCKET client = accept(this->loginSocket,(struct sockaddr*)&from,&fromLen);
        if(client != INVALID_SOCKET)
        {
            srand(time(NULL));
            int clientKey = rand();
            XClient* clientClass = new XClient(inet_ntoa(from.sin_addr),clientKey,client);
            this->clients.insert(make_pair(clientKey,clientClass));
        }
        Sleep(100);
    }
    catch(...)
    {
        printf("error accepting incoming connection!\r\n");
        break;
    }
}

closesocket(this->loginSocket);
WSACleanup();

return true;
}

还有声明:

    map<int,XClient*> clients;
map<int,XClient*>::iterator it;

【问题讨论】:

  • 检查XClient::~XClient()(析构函数)以确保它正确释放所有内容。
  • 我在析构函数中添加了一个printf,但似乎没有调用析构函数...
  • printf 不是一种可靠的调试方式。尝试断点。
  • 我还注意到您的代码在删除条目后并未从地图中删除条目。因此,您将读取已删除对象的connected 成员变量,并多次在同一个指针上调用delete,并出现各种不良行为。
  • 我的错,printf 在析构函数中工作正常。那么,我应该如何正确删除地图中的条目?

标签: c++ sockets memory map memory-leaks


【解决方案1】:

您有几个问题,但主要问题是您似乎在线程之间共享map,根本没有任何同步。这可能会导致各种麻烦。

【讨论】:

  • 只有 Update 方法在线程下运行。还是没关系?
  • 您有 > 1 个线程(即主线程计为 1)。你需要同步。
【解决方案2】:

您使用的是c++11 还是Boost?为了避免像这样的内存泄漏噩梦,您可以创建一个map 的共享指针。这样,您可以让结构自行清理。

我会这样做:

#include <memory>
#include <map>
#include <algorithm>
#include <functional>
#include <mutex>

typedef std::shared_ptr<XClient> XClientPtr;
std::map<int, XClientPtr> client;
std::mutex the_lock;

bool XSockets::AcceptConnections()
{
/* snip */

    auto clientClass = std::make_shared<XClient>(/*... params ...*/);
    the_lock.lock();
    clients[clientKey] = clientClass;
    the_lock.unlock();
/* snip */
}

bool client_is_connected(const std::pair<int, XClientPtr> &p) {
    return p.second->connected;
}

DWORD Update(XSockets *sockets) {
    while(true) { /* You should probably have some kind of
                     exit condition here. Like a global "running" bool
                     so that the thread will eventually stop. */
        the_lock.lock();

        auto it = sockets->clients.begin(), end = sockets->clients.end();
        for(; it != end; ) {
            if (!it->second->connected)
                //Clients will be destructed here if their refcount goes to 0
                sockets->clients.erase(it++); 
            else
                ++it;
        }
        the_lock.unlock();
        Sleep(100);
    }
    return 1;
}

注意:以上代码未经测试。我什至没有尝试编译它。

【讨论】:

  • 不知何故,我不能包含互斥头:没有这样的文件或目录
  • 可能不是编译器标准库的一部分。但你明白了。从外观上看,您正在使用winapi。尝试InitializeCriticalSection 和相关文档中定义的技术。
  • 我无法包含您提供的所有标题:算法、功能和内存。如果我添加功能标题,我的 VS 2010 会显示很多错误...
  • 你的 VS2010 怎么样了?它应该包括所有这些标题。事实上,按照C++ 标准,这些标头已经过时了。你的环境坏了吗?您使用的是原生 C++ 还是托管?
  • 干得好!学过的知识?不要在 using 指令中使用 namespace std。那一行是什么,XClient.h:29?
【解决方案3】:

What happens to an STL iterator after erasing it in VS, UNIX/Linux?。在您的情况下,您并没有删除所有内容,因此您不希望使用 for 循环。

sockets->it = sockets->clients.begin();
while (sockets->it != sockets->clients.end())
{
    int key = (*sockets->it).first;
    if(sockets->clients[key]->connected == false) // remove the client, releasing memory
    {
        delete sockets->clients[key];
        sockets->clients.erase(sockets->it++);
    }
    else
    {
        sockets->it++;
    }
}

【讨论】:

  • 你不能用it-&gt;second代替clients[key]吗?
  • 是的,但我想对他的代码做尽可能少的改动。
  • BTW sockets-&gt;clients.erase(sockets-&gt;it++); 使 it 无效。你应该改用sockets-&gt;it = sockets-&gt;clients.erase(sockets-&gt;it);
  • @BenVoigt 在 C++11 之前,签名是 void erase(iterator pos),因此 sockets-&gt;it = sockets-&gt;clients.erase(sockets-&gt;it) 将无法在旧编译器上编译。此外,“对已擦除元素的引用和迭代器无效。其他引用和迭代器不受影响。”,因此sockets-&gt;it++it 留在有效的state 中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-22
  • 2015-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多