2014-09-24 2 views
2

У меня есть служба Windows, написанная на C++, которая функционирует как TCP-сервер, который слушает входящие соединения.Блокировка двух потоков в службе Windows C++

Я инициализировал серверный сокет и поместил код accept в отдельный поток. Это будет принимать и обрабатывать входящие соединения.

Однако мне также необходимо остановить этот поток, если служба получает сигнал STOP. Поэтому я подумал о создании объекта события, используя CreateEvent и ожидая его передачи. Это ожидание произойдет в потоке, который создает поток accept. Поэтому я мог использовать функцию TerminateThread, чтобы остановить поток accept, когда принимается сигнал STOP.

Однако MSDN говорит, что

TerminateThread опасная функция, которая должна использоваться только в самых крайних случаях.

Насколько строго это следует соблюдать, и мой подход правильный? Что может быть другим способом сделать это?

+0

Очень строго. В вашей ситуации, я думаю, вы можете использовать CancelSynchronousIo для отмены функции accept(). –

ответ

2

В Windows вы можете разблокировать блокировку accept звонка из другого потока, просто позвонив по номеру closesocket. Блокирующий вызов accept будет возвращать -1, и ваш код может выйти из любого цикла, в котором он находится, проверив другое условие выхода, которое вы уже установили (например, глобальная переменная).

Это также работает с Mac (и вероятные производные BSD) с функцией close, но не Linux. Более универсальное решение UNIX для этой проблемы - here.

Некоторые коды pseduo для решения Windows ниже.

SOCKET _listenSocket; 
bool _needToExit = false; 
HANDLE _hThread; 

void MakeListenThreadExit() 
{ 
    _needToExit = true; 
    closesocket(_listenSocket); 
    _listenSocket = INVALID_SOCKET; 

    // wait for the thread to exit 
    WaitForSingleObject(_hThread, INFINITE);  
} 

DWORD __stdcall ListenThread(void* context) 
{ 
    while (_needToExit == false) 
    {    
     SOCKET client = accept(_listenSocket, (sockaddr*)&addr, &addrSize); 
     if ((client == -1) || _needToExit) 
     { 
      break; 
     } 
     ProcessClient(client); 
    }  
    return 0; 
} 
+0

Никогда не думал об этом !! Спасибо за решение. – Cygnus

+0

Это не работает и никогда не должно использоваться. У вас есть следующее состояние гонки: вы собираетесь вызвать 'accept'. Вы называете 'closesocket'. Другой поток (возможно, в библиотеке, которую вы не контролируете) выделяет сокет и получает тот же дескриптор, который вы только что закрыли. Теперь вы вызываете 'accept' на * неправильном * сокете и крадете соединения из библиотеки. Просто нет способа убедиться, что вы все еще заблокированы в 'accept', когда вы переходите к вызову' closesocket'. –

+0

@DavidSchwartz - это фактически ** делает ** произведение. Что касается того, следует ли использовать *, то для обсуждения. ОП должен тщательно рассмотреть ваш вопрос. Я планирую дважды проверить, но я уверен, что Win32 использует либо увеличивающие идентификаторы дескриптора сокета, либо разумный алгоритм, чтобы гарантировать, что значения дескриптора сокета не будут повторно использоваться быстро - для борьбы с этими типами ошибок.В вашем состоянии гонки, которое вы начертите, вам нужно будет создать (2^32) -1 дескрипторы сокетов в состоянии гонки, чтобы получить дублирующийся дескриптор сокета, который был ранее закрыт. – selbie

1

В этой ситуации не используйте accept() на блокирующем гнезде. Вместо этого используйте неблокирующий сокет. Затем вы можете использовать select() с таймаутом, чтобы ваш поток мог периодически проверять условие завершения. Или лучше, используйте WSACreateEvent() с WSASelectEvent(). Создайте два объекта событий, один для обнаружения клиентских подключений, и один для обнаружения завершения потока. Затем вы можете использовать WSAWaitForMultipleEvents() для ожидания обоих событий одновременно. Используйте WSASetEvent(), чтобы сигнализировать о завершении события, когда это необходимо, и вызывать accept() или WSAAccept() всякий раз, когда сигнализируется другое событие. WSAWaitForMultipleEvents() расскажет вам, на каком событии действовать.