Почему read() не возвращается после выключения/закрытия необработанного сокета?

У меня есть поток, который читает сообщения из необработанного сокета HCI в цикле, подобном этому:

void* loop_hci (void* args) {
    params_hci_t* params = (params_hci_t*) args;
    int result_hci = 0;
    uint8_t* buf_hci = calloc(1, HCI_EVENT_MAX_LENGTH);
    while (!poll_end()) {
        result_hci = read(params->hci_sock, buf_hci, HCI_EVENT_MAX_LENGTH);
        if (result_hci > 0) {
            // ... do stuff with the received data
        }
    }
    ancs_pdebug("HCI loop shutting down...");
    return NULL;    
}

Функция poll_end() работает нормально и по назначению. Он возвращает 0 до тех пор, пока не будет получен сигнал SIGINT, после чего возвращает 1.

В основном потоке я создаю сокет следующим образом:

hci_sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);

А также ветка:

ph->hci_sock = hci_sock;
pthread_create(&t_hci, NULL, &loop_hci, ph);

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

shutdown(hci_sock, SHUT_RD);

Я предполагаю, что read() должен вернуться после того, как я вызову shutdown(), я использую тот же метод в другом потоке для сокета L2CAP, и он работает нормально. Но это не так. Мой вызов pthread_join(t_hci, NULL) в основном потоке никогда не возвращается.

Розетка работает нормально. Я могу читать сообщения от него. Я также пытался вместо этого вызвать close (что я делаю после завершения потоков), но результаты такие же.

В чем может быть проблема, или мои предположения неверны?


person Lasse Meyer    schedule 30.05.2016    source источник
comment
Вы приказываете потоку выйти? Вы делаете проверку read на возврат 0 или -1? Если read возвращает 0 или -1, прерываете ли вы цикл или иным образом завершаете поток? И, если возможно, попробуйте создать минимальный, полный и поддающийся проверке пример и покажите нам.   -  person Some programmer dude    schedule 30.05.2016
comment
Прежде чем я вызову выключение, устанавливается флаг, поэтому poll_end() возвращает 1. Если результат равен ‹= 0, я пропускаю оставшуюся часть цикла. Итак, после того, как чтение возвращается, оно должно разорвать цикл самостоятельно.   -  person Lasse Meyer    schedule 30.05.2016
comment
Что заставляет вас думать, что вызов shudown() для дескриптора файла, используемого другим потоком, вообще разрешен?   -  person EOF    schedule 30.05.2016
comment
Это не? Как еще тогда закончить ветку? Есть ли другой способ получить поток из вызова блокирующей системной функции?   -  person Lasse Meyer    schedule 30.05.2016
comment
Вы можете отправить сигнал (pthread_kill()) или отменить поток pthread_cancel(). Разумным способом было бы использовать [e]poll() или [p]select() на неблокирующем сокете.   -  person EOF    schedule 30.05.2016
comment
См., например. этот старый вопрос, ответ с наибольшим количеством голосов говорит, что вы должны использовать SHUT_RDWR.   -  person Some programmer dude    schedule 30.05.2016
comment
Я уже пробовал SHUT_RDWR, ничего не меняется.   -  person Lasse Meyer    schedule 30.05.2016
comment
shutdown() ничего не делает только с сокетами TCP (SOCK_STREAM). Это сокет SOCK_RAW. @EOF Это вполне допустимый метод для сокетов TCP, но здесь это не сокет TCP.   -  person user207421    schedule 30.05.2016
comment
@JoachimPileborg SHUT_RD достаточно в TCP.   -  person user207421    schedule 30.05.2016
comment
@EJP: Можете ли вы указать мне какую-нибудь документацию, которая подтверждает это как действительное? ' Потому что pubs.opengroup.org/onlinepubs/9699919799 определяет эффект только для последующие операции ввода-вывода, а не одновременные операции ввода-вывода.   -  person EOF    schedule 30.05.2016
comment
@EOF 30+ лет опыта. Ему не имеет смысла не разблокировать параллельный recv(): чего он будет ждать?   -  person user207421    schedule 30.05.2016
comment
@OP Похоже, вы не проверяете результат recv() на ноль. Итак, как вы можете сказать, что он никогда не вернется? Откуда вы знаете, что это не просто зацикливание в вашем коде?   -  person user207421    schedule 30.05.2016
comment
@EJP: это неразумный аргумент. Многие люди долгое время полагались на перенос межгерцового переполнения, такого как дополнение 2, не определяют его таким образом (даже если вы думаете, что это имеет смысл).   -  person EOF    schedule 30.05.2016
comment
@EOF Это не является разумным аргументом. Целочисленное переполнение не имеет ничего общего с вводом-выводом. Вы не ответили на вопрос в моем комментарии: чего именно он будет ждать после отключения ввода?   -  person user207421    schedule 30.05.2016
comment
@EJP: Что еще должно делать целочисленное переполнение, кроме переноса? Ввод/вывод не является волшебным образом более определенным, чем все остальное. Если вызов shutdown() для сокета, который используется другим потоком, не определен, тогда может произойти все что угодно.   -  person EOF    schedule 30.05.2016
comment
@EOF Вы можете спорить, безумно или нет, сколько угодно, но факт остается фактом: это признанная техника, и она действительно работает. В ПТС. И вы не ответили на мой вопрос. Как я узнал в 1971 году, целочисленное переполнение может делать много других вещей, помимо переноса.   -  person user207421    schedule 30.05.2016
comment
Я никогда не видел ни в Windows, ни в Linux вызов recv() или read(), который не возвращает «рано», если fd, на котором он ожидает, закрыт из другого потока. Мне очень сложно представить какой-либо другой результат, когда ресурсы сокета должны быть освобождены, пока их ожидают потоки. Единственным разумным действием, которое мог бы выполнить стек связи, было бы подготовить все ожидающие потоки с ошибкой, а затем очистить/освободить/TIME_WAIT сокет, как того требует используемый протокол.   -  person Martin James    schedule 30.05.2016
comment
@EJP Я проверяю результат. См. 2-й комментарий.   -  person Lasse Meyer    schedule 30.05.2016
comment
Решил проблему с pthread_cancel, как предложил EOF.   -  person Lasse Meyer    schedule 30.05.2016
comment
Еще было бы интересно, как решить эту проблему на сыром сокете без pthread_cancel. опрос/выбор тоже не работает, я пробовал. Опять же, я никогда раньше не работал с этими функциями, поэтому, возможно, я сделал что-то не так.   -  person Lasse Meyer    schedule 30.05.2016
comment
Использование pthread_cancel()-молотка является достойным подходом к решению этой проблемы. Если нет опроса/выбора, то отправьте потоку сигнал, используя pthread_kill(), это приведет к возврату блокирующего системного вызова (здесь read()) с errno, установленным в EINTR (если в сокете не было установлено SA_RESTART). Установите обработчик отправляемого вами сигнала. Ручка ничего не делала, а просто была рядом.   -  person alk    schedule 30.05.2016


Ответы (1)


Проблема, с которой вы столкнулись, может быть связана с тем, как вы обрабатываете сокеты и многопоточность. Вы не должны использовать выключение с необработанными сокетами. Он предназначен для подключенных сокетов, я действительно никогда не пробовал его с необработанными сокетами или пакетными сокетами. Но есть предложение убить или отменить поток, которого я бы избегал, потому что уничтожение потока — это грубая сила, и вы рискуете не распоряжаться ресурсами упорядоченным образом. Вместо этого вы должны разработать свое решение по-другому:

  1. Одна из возможностей — использовать неблокирующие сокеты вместе с select, poll или epoll. У вас будет цикл, который ожидает в select/poll/epoll, пока сокет не будет готов или не будет тайм-аута. Когда вы хотите закрыть сокет, вы просто устанавливаете для переменной, такой как endLoop, например, значение true, указывающее, что поток опроса должен выйти из цикла. См. man для выбора: http://man7.org/linux/man-pages/man2/select.2.html

  2. Другая возможность, которая используется, например, на сервере Thrift, заключается в том, чтобы приложение отправляло специальное сообщение самому себе с определенным кодом. Слушающий поток разблокирует и читает это специальное сообщение, указывающее, что он должен проверить, должен ли он закончить прослушивание (например, прочитав значение переменной).

Таким образом, пока слушающий поток слушает или читает, основной поток установит для переменной endLoop значение true, указывая на то, что он завершит работу, или он сделает (2), чтобы разблокировать слушающий поток.

Два варианта — это элегантные способы решения вашей проблемы. Либо используйте неблокирующие сокеты (1), либо разблокируйте заблокированный поток чтения или прослушивания, отправив сообщение самому себе.

person rodolk    schedule 31.05.2016