Как правильно выбрать () для сокетов, которые могут быть закрыты в другом потоке? - PullRequest
2 голосов
/ 13 декабря 2011

У меня есть ситуация, когда поток выбирает сокеты, управляемые отдельным потоком.

Когда сокет закрыт, select() предположительно возвращает этот сокет как «доступный», и он не 'До тех пор, пока я не попытаюсь прочитать из него, я понимаю, что он закрыт.

Но я вижу парадокс: когда сокет закрывается из другого потока, система может перераспределить свой файловый дескриптор длядругие цели.(Я думаю.)

Как я могу гарантировать, что к тому времени, когда я прочту из сокета (просто числовой дескриптор), система еще не утилизирует этот дескриптор и не использует его для нового сокета?Другими словами, насколько я знаю, я мог бы читать из какого-то другого недавно открытого сокета (возможно, сокета, который я даже не должен был включать в свой select()!) Вместо только что закрытого сокета.

Я мог бы сохранить список недавно закрытых дескрипторов, но мне интересно, есть ли лучший способ.

Ответы [ 5 ]

4 голосов
/ 13 декабря 2011

Краткий ответ: не закрывайте сокеты из другого потока, из которого вы читаете в этом!

FD может быть переназначен. Но у вас будут проблемы, если вы читаете с одного FD в нескольких потоках без какой-либо схемы связи между ними. Теперь, если у вас есть структура «Socket description» в разделяемой памяти, которая имеет управляющий семафор и некоторые указания на FD, и другую информацию о состоянии, возможно, это может быть управляемым, но я думаю, вы обнаружите, что самое простое решение почти всегда делать FD специфичными для одного потока ...

3 голосов
/ 13 декабря 2011

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

Добавьте управляющий сокет к вашему набору чтения в вызове выбора, например, Unix сокет. Напишите некоторые управляющие данные, чтобы отделить этот поток от вызова select. Затем поток может проверить, должен ли сокет быть закрыт или нет. Может сделать это как часть структуры вашего сокета или даже как фактические данные вашего контрольного пакета.

, например

fd_set readSet;
FD_ZERO(&readSet);
FD_SET(sock, &readSet);
FD_SET(controlSock, &readSet);

int n = select(maxfd, &readSet, NULL, NULL);
...
if (FD_ISSET(controlSock, &readSet)) {
    /* check if sock should be closed or not, also drain controlSock */
}

, затем просто напишите в управляющий разъем, чтобы подать сигнал на закрытие.

Вы также можете вызвать сигнал, который обычно прерывает блокирующий вызов ввода-вывода. Если они не будут перезапущены автоматически. Вызовы вернут -1, и errno будет установлен в EINTR. Вы можете проверить флажок, чтобы увидеть, следует ли вам закрыться и прервать разговор.

1012 *, например *

if (n == -1 && errno == EINTR)
    goto cleanup;
0 голосов
/ 31 июля 2013

Лучший способ работы здесь:

Работа с неблокирующими сокетами (O_NONBLOCK).

Сделайте ваш API таким, чтобы вы могли добавлять функции обратного вызова для каждого добавляемого вами FDв вызове select ().

MultiplexAdd(fd, callbackFn);

Теперь, когда сокет готов к вводу-выводу, будет вызываться ваш обратный вызов, вы можете делать все, что захотите. (Даже закрыть сокет).

Таким образом, вы можете справитьсяваши сокеты систематическим образом в одном потоке.

Пример кода:

int f1()
{
    MultiplexAdd(fd1, callbackFn1);
    MultiplexAdd(fd2, callbackFn2);

    MultiplexMainLoop(); /* This is where you will be blocked on select() */
}


void callbackFn1(int fd)
{
     /* Processing as desired */
     close(fd);
     MultiplexRemove(fd); /* Remove fd from select, else select will return -1 
                                                                    with EABDF */
}

Даже если вы застряли, вы должны пойти в ситуацию, когда вы должны закрыть сокет отВ другом потоке вы можете создать pipe и добавить его к select().

Сделайте write() из того, что вы закрывали сокет.При обратном вызове в трубу pipeReadCb() вы можете закрыть сокет.

0 голосов
/ 13 декабря 2011

Если вы закроете сокет и затем вызовете select с fd закрытого сокета, установленным в любом из наборов fdsets, select немедленно вернется с EBADF, поэтому не делайте этого.

Если вы хотите, чтобы несколько потоков управляли общим пулом сокетов и обрабатывали их аккуратно, вам нужно будет использовать какую-то блокировку, чтобы один поток не закрывал сокет, пока другой поток вызывает select. Если у вас есть глобальные наборы fd_sets, которые отслеживают, какие сокеты являются «живыми», вы можете использовать блокировку чтения / записи для защиты доступа к набору. Установить блокировку чтения непосредственно перед копированием набора и вызовом select; снять блокировку после выбора возвращает. Получите блокировку записи перед закрытием сокета, затем снимите его после удаления теперь закрытого сокета из набора fd_set.

Другой возможностью является использование атомарных инструкций чтения-изменения-записи для манипулирования глобальным fd_set, а затем, когда вы хотите закрыть сокет, вы сначала удаляете его из глобального fd_set, а затем ждете достаточно долго, чтобы все потоки продвинулись, прежде чем на самом деле закрываю это. Это дает вам условие состязания (другой поток может скопировать глобальный fd_set непосредственно перед тем, как вы удалите закрывающий fd, а не вызывать select до тех пор), поэтому вам нужно знать, как долго ждать достаточно долго, что зависит от вашей системы.

0 голосов
/ 13 декабря 2011

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

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