Отслеживание источника утечек дескрипторов в приложении WinSock MFC

#c #sockets #mfc #memory-leaks #winsock

#c #сокеты #mfc #утечки памяти #winsock

Вопрос:

Мы разрабатываем приложение, в котором используем основанный на WinSock подход sime socket для взаимодействия с внешним модулем. Наше требование — убедиться, что соединение всегда будет включено, поэтому по этой причине мы постоянно повторяем попытки подключения каждые 1 минуту всякий раз, когда нас отключают.

Наша проблема начинается здесь. Мы заметили, что при каждой повторной попытке повторного подключения к сокету происходит утечка ровно двух дескрипторов Windows. Мы перепробовали так много вариантов, но ни один из них не работает. Какие дескрипторы могли протекать и как мы могли бы идентифицировать виновника?

Ниже приведен код, который мы используем прямо сейчас:

 bool CSocketClass::ConnectToServer(int nLineNo)
{
string strIPAddress;
int nPortNo;
SOCKET* l_ClientSocket;
int ConnectionResu<
//----------------------
// Create a SOCKET for connecting to server
if (nLineNo == 1)   
     {
    m_objLine1.m_ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    strIPAddress = m_objLine1.m_strIPAddress;
    nPortNo = m_objLine1.m_nPortNo;
    l_ClientSocket = amp;(m_objLine1.m_ClientSocket);
}
else
{
    m_objLine2.m_ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    strIPAddress = m_objLine2.m_strIPAddress;
    nPortNo = m_objLine2.m_nPortNo;
    l_ClientSocket = amp;(m_objLine2.m_ClientSocket);
}
if(INVALID_SOCKET == *l_ClientSocket)
{
    return false;
}
//----------------------
// The sockaddr_in structure specifies the address family,
// IP address, and port of the server to be connected to.
sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr( strIPAddress.c_str() );
clientService.sin_port = htons( nPortNo );
//----------------------
// Connect to server.
ConnectionResult = connect( *l_ClientSocket, (SOCKADDR*) amp;clientService, sizeof(clientService) ) ;  if (ConnectionResult == SOCKET_ERROR)
{
    if (nLineNo == 1)
    {
        //ERROR in line1
    }
    else
    {
        //ERROR in line2
    }
    return false;
}
else
//In case of successful connection
{

    //Other actions
}
return true;
}
  

Комментарии:

1. Ваша утечка не в опубликованном коде. В качестве дополнительного примечания, не closesocket(INVALID_SOCKET) — это в лучшем случае бессмысленно, а в худшем — вредно.

2. Изначально мы закрывали сокеты, только если они подключены, но даже это не решало нашу задачу.. Вы уверены, что утечка не в этом коде? Потому что я буквально видел (во время отладки), что после инструкции connect в моем коде происходит утечка ровно 2 дескрипторов.. И просто чтобы убедиться, что я говорю об утечках дескрипторов Windows, которые мы можем видеть в диспетчере задач, я не говорю об утечках памяти.

3. @vrajs5: Вы не закрываете эти сокеты, когда закончите с ними. Это не утечка при подключении — она становится утечкой, когда вы забываете очистить.

4. @vrajs5: Вы, например, закрывали старые сокеты перед вызовом этой функции?

5. @Erik : Мы вызываем эти функции, если старый сокет отключен.. И просто для безопасности мы закрываем этот старый сокет и после этого вызываем эту функцию.. Я знаю, что вы пытаетесь сказать, что происходит утечка, когда она не очищается.. Но мы вызываем closesocket перед повторным подключением..

Ответ №1:

Попробуйте бесплатный Process Explorer от Microsoft. Он отобразит все открытые дескрипторы для процесса вместе с такой информацией, как имя (для файла, мьютекса, события и т.д. обрабатывает). Также будут выделены вновь созданные и закрытые дескрипторы, поэтому, если вы пройдете цикл своего кода и обновите отображение, вы сможете увидеть точные дескрипторы, которые были утечены.

Комментарии:

1. 1 Process Explorer — это здорово, другим вариантом было бы использовать инструмент «handle» также из sysinternals, чтобы проверить, какие дескрипторы открыты

Ответ №2:

Допустим, вы правильно приобрели socket:

 m_objLine1.m_ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) 
m_objLine1.m_ClientSocket != INVALID_SOCKET // true
  

но тогда вы не можете подключиться, поэтому

 ConnectionResult = connect( *l_ClientSocket, (SOCKADDR*) amp;clientService,
    sizeof(clientService) ) 
ConnectionResult == SOCKET_ERROR // true
  

в этом случае вам следует закрыть полученный дескриптор сокета:

 closesocket(m_objLine1.m_ClientSocket);
  

У вас есть две строки, поэтому я предполагаю, что вы вызываете эту функцию дважды, по одному разу для каждой строки, так что
вот почему произошла утечка двух дескрипторов.

Ответ №3:

Я бы посоветовал вам попробовать Intel Parallel Inspector, чтобы определить утечки памяти и где они происходят.

Существует пробная загрузка, если вы хотите попробовать.

Комментарии:

1. Вы говорите об инспекторах утечки памяти? Потому что я говорю об утечках дескрипторов…

2. Intel Parallel Inspector также способен обнаруживать утечку дескрипторов Windows. Я использовал его для поиска утечек дескрипторов GDI, я предполагаю, что это будет работать так же для дескрипторов сокетов.

Ответ №4:

Простой способ обнаружить утечки дескрипторов — это протоколировать все.

Каждый раз, когда вы получаете дескриптор, регистрируйте, что вы его получили, а также любые другие подробности об обстоятельствах. Каждый раз, когда вы освобождаете дескриптор, регистрируйте, что вы его выпустили. Оба раза укажите фактический дескриптор (просто некоторый шестнадцатеричный код).

Затем вы получаете журнал, который выглядит следующим образом (просто для примера):

 Obtained handle 0xf000 (nLineNo = 5)
Obtained handle 0xb000 (nLineNo = 6)
Obtained handle 0xd0d0 (nLineNo = 7)
Released handle 0xf000
Released handle 0xb000
  

Просматривая это вручную, вы можете видеть, что вы получили дескриптор 0xd0d0, когда nLineNo было 7, и он так и не был выпущен. Это немного, но помогает, и, если ситуация становится сложной, вы даже можете попробовать регистрировать трассировки стека при каждом получении / выпуске. Кроме того, если журнал всегда надежно создается подобным образом, вы можете начать вводить точки останова на основе фактических значений (например, прерывание в точке программы, когда дескриптор равен 0xd0d0, чтобы вы могли видеть, что с ним происходит).

Если это более практично, вы можете начать переносить свои дескрипторы внутрь самой программы, например, std::set всех полученных дескрипторов вместе с любыми подробностями о том, когда они были получены, и вы можете эффективно начать взламывать свою программу, чтобы отслеживать, что она делает (затем отменить все свои изменения, как только вы это исправили).

Надеюсь, это поможет — это одна из причин, по которой я стараюсь, по крайней мере, сохранять std::set все, что я получаю, так что в худшем случае вы можете повторить их при завершении работы и освободить их все (и записать большое сообщение «ИСПРАВЬТЕ ЭТО!»!)

Комментарии:

1. Я не получаю дескрипторы вручную / напрямую.. Я просто создаю сокет и закрываю его.. Я также регистрирую события открытия и закрытия (их нет в этом коде) Но это не решает нашу проблему..

Ответ №5:

Попробуйте добавить shutdown(SD_BOTH) к дескрипторам сокета после closesocket(); Также попробуйте добавить режим ожидания примерно на 100 мс (только для теста) и посмотрите, как это происходит.