c ++ linux accept () блокировка после закрытия сокета - PullRequest
5 голосов
/ 20 февраля 2012

У меня есть поток, который прослушивает новые подключения

new_fd = accept(Listen_fd, (struct sockaddr *) & their_addr, &sin_size);

, и другой поток, который закрывает Listen_fd, когда приходит время закрыть программу.После того как Listen_fd закрыт, он все равно блокируется.Когда я использую GDB для отладки, accept () не блокируется.Я подумал, что это может быть проблема с SO_LINGER, но он не должен быть включен по умолчанию и не должен меняться при использовании GDB.Любая идея, что происходит, или любое другое предложение о закрытии сокета листинга?

Ответы [ 5 ]

6 голосов
/ 20 февраля 2012

Поведение accept при вызове чего-то, что не является допустимым сокетом FD, не определено. «Не допустимый сокет FD» включает числа, которые когда-то были действительными сокетами, но с тех пор были закрыты. Вы можете сказать «но Borealid, он должен возвращать EINVAL!», Но это не гарантировано - например, один и тот же номер FD может быть переназначен на другой сокет между вашими close и accept вызовами.

Таким образом, даже если бы вы должны были изолировать и исправить то, что приводит к сбою вашей программы, вы все равно могли бы снова начать сбой в будущем. Не делайте этого - исправьте ошибку, из-за которой вы пытаетесь accept установить соединение в закрытом сокете.

Если вы имели в виду, что вызов, который был ранее сделан для accept , продолжает блокировку после close, то вы должны послать сигнал потоку, который заблокирован в accept. Это даст ему EINTR, и он может полностью отключиться - и затем закроет сокет. Не закрывайте его из потока, отличного от того, который его использует.

5 голосов
/ 08 октября 2013

Использование: sock.shutdown (socket.SHUT_RD)

Тогда accept вернется EINVAL. Никаких уродливых перекрестных сигналов не требуется!

Из документации Python: «Примечание close() освобождает ресурс, связанный с соединением, но не обязательно немедленно закрывает соединение. Если вы хотите закрыть соединение своевременно, позвоните shutdown() до close()

http://docs.python.org/3/library/socket.html#socket.socket.close

Я столкнулся с этой проблемой несколько лет назад, когда программировал на C. Но я нашел решение сегодня только после того, как столкнулся с той же проблемой в Python, И размышлял, используя сигналы (чёрт!), И ПОТОМ вспомнил заметку о * 1016. *!

Что касается комментариев, в которых говорится, что вы не должны закрывать / использовать сокеты между потоками ... в CPython глобальная блокировка интерпретатора должна защищать вас (при условии, что вы используете файловые объекты, а не необработанные целочисленные файловые дескрипторы).

Вот пример кода:

import socket, threading, time

sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind (('', 8000))
sock.listen (5)

def child ():
  print ('child  accept ...')
  try:  sock.accept ()
  except OSError as exc :  print ('child  exception  %s' % exc)
  print ('child  exit')

threading.Thread ( target = child ).start ()

time.sleep (1)
print ('main   shutdown')
sock.shutdown (socket.SHUT_RD)

time.sleep (1)
print ('main   close')
sock.close ()

time.sleep (1)
print ('main   exit')
1 голос
/ 23 марта 2019

Функция shutdown() может быть тем, что вы ищете.Вызов shutdown(Listen_fd, SHUT_RDWR) приведет к тому, что любой заблокированный вызов на accept() вернет EINVAL.Соединение вызова с shutdown() с использованием атомарного флага может помочь определить причину EINVAL.

Например, если у вас есть этот флаг:

std::atomic<bool> safe_shutdown(false);

Затем вы можете указать другому потоку прекратить прослушивание с помощью:

shutdown_handler([&]() {
  safe_shutdown = true;
  shutdown(Listen_fd, SHUT_RDWR);
});

Для полноты, вот как ваш поток может вызвать accept:

while (true) {
  sockaddr_in clientAddr = {0};
  socklen_t clientAddrSize = sizeof(clientAddr);
  int connSd = accept(Listen_fd, (sockaddr *)&clientAddr, &clientAddrSize);
  if (connSd < 0) {
    // If shutdown_handler() was called, then exit gracefully
    if (errno == EINVAL && safe_shutdown)
      break;
    // Otherwise, it's an unrecoverable error
    std::terminate();
  }
  char clientname[1024];
  std::cout << "Connected to "
            << inet_ntop(AF_INET, &clientAddr.sin_addr, clientname,
                         sizeof(clientname))
            << std::endl;
  service_connection(connSd);
}
1 голос
/ 20 февраля 2012

Это обходной путь, но вы можете select на Listen_fd с таймаутом, и если тайм-аут произошел, проверьте, что вы собираетесь закрыть программу. Если это так, выйдите из цикла, если нет, вернитесь к шагу 1 и выполните следующее select.

0 голосов
/ 20 февраля 2012

Вы проверяете возвращаемое значение закрытия? Из руководств Linux, (http://www.kernel.org/doc/man-pages/online/pages/man2/close.2.html) «Вероятно, неразумно закрывать файловые дескрипторы, когда они могут использоваться системными вызовами в других потоках в том же процессе. Поскольку файловый дескриптор может использоваться повторно, существуют некоторые неясные условия гонки, которые могут вызвать непреднамеренные побочные эффекты». Вы можете использовать выбор вместо принятия и ждать какого-то события от другого thead, а затем закрыть сокет в потоке слушателя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...