2013-08-16 2 views
5

Там есть нить любит это:Как разрешить поток, который блокирует recv() изящно?

{ 
    ......  
    while (1) 
    { 
     recv(socket, buffer, sizeof(buffer), 0); 
     ...... 
    } 
    close(socket);   
} 

Поскольку поток блокируется на ПРИЕМ вызова(), как я могу позволить выход нити изящно?

+1

Проверьте возвращаемое значение 'recv' и' break', когда оно равно 0 или отрицательно? – cnicutar

+0

Вы спрашиваете, есть ли способ сделать тайм-аут recv() после заданного интервала, если он не получит ответ? – gwilkins

ответ

5

Вы можете назвать: -

shutdown(sock, SHUT_RDWR) //on the remote end 

Проверить this аут.

+0

Удаленный конец может быть в другом процессе на другом хосте. –

2

Объявляем глобальный флаг выхода:

int bExit = 0; 

Пусть cirtical части проверить его:

while(1) 
{ 
    ssize_t result = recv(socket, buffer, sizeof(buffer), 0); 
    if ((-1 == result) && (EINTR == error) && bExit) 
    { 
    break; 
    } 

    ... 
} 

Сломать читателя, сначала установите флаг выхода

bExit = 1; 

затем отправить сигнал для считывающей нити

pthread_kill(pthreadReader, SIGUSR1); 

Примечание: Что я оставил в этом примере является защита bExit от одновременного доступа.

Это может быть достигнуто с помощью мьютекса или соответствующего объявления.

+2

Обратите внимание: _ Если заблокированный вызов на один из следующих интерфейсов (включенный recv) прерывается обработчиком сигнала, тогда вызов будет автоматически перезагружен после того, как обработчик сигнала вернется, если используется флаг SA_RESTART; в противном случае вызов завершится с ошибкой EINTR. Кроме того, вы ошибаетесь 'errno' с возвращаемым значением' recv() '. –

+0

@MaximYegorushkin: Спасибо, что указали мне на беспорядок 'result' /' errno'. Я как-то все еще был с 'pthread_ *' -API, который возвращает 'errno' -значения, но сам устанавливает' errno'. Также очень важен намек на 'SA_RESTART', я пропустил упоминание об этом, еще раз спасибо за это. – alk

5

Не блокировать на recv. Скорее реализуйте блок «select» и протестируйте входной fdset, чтобы определить, есть ли что-нибудь для чтения в первую очередь.

Если есть данные для чтения, вызовите recv. Если нет, loop, проверьте 'isrunning' volitile Boolean переменную, которая устанавливается другим потоком при запуске shutdown.

+0

Я думаю, что эффективность этого метода низкая, не так ли? –

+2

На самом деле это очень высоко. Он позволяет вам обслуживать несколько сокетов одновременно на поток. Я использовал этот метод на серверах с тысячами подключений. Блоки из 64 сокетов управлялись с помощью выбора в потоке. Даже при полном объеме загрузка ЦП оставалась на уровне менее десяти процентов. –

+0

: спасибо! По моему несовершенству ваша программная модель - это не каждый поток, обслуживающий каждый сокет, а поток, обслуживающий множество сокетов. Считаете ли вы, что каждая нить, обслуживающая каждый сокет, более эффективна? –

0

Кроме того, установить сокет быть блокирующими:

int listen_sd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); 
if (listen_sd < 0) { 
    perror("socket() failed"); 
} 

int on = 1; 

//set socket to be non-blocking 
int rc = ioctl(listen_sd, FIONBIO,(char *)&on); 
if (rc < 0) { 
    perror("ioctl() failed"); 
    close(listen_sd); 
} 

Затем вы можете вызвать ПРИЕМ() на сокете, и он не будет блокировать. Если нет ничего, чтобы прочитать то глобальная переменная ERRNO устанавливается на постоянной EWOULDBLOCK

int rc = recv(listen_sd, buffer, sizeof(buffer), 0); 
if (rc < 0) { 
    if (errno != EWOULDBLOCK) { 
     perror("recv() failed"); 
    } 
} 
+0

'if (errno! = EWOULDBLOCK) {' должно быть лучше 'if ((errno! = EWOULDBLOCK) && (errno! = EAGAIN)) {'. – alk

+0

@alk Они имеют одинаковое значение, когда оба они определены. – EJP

2

Объявить некоторые «стоп» булево, проверить его после каждого ПРИЕМ (Обратно) и прекращается, если он установлен. Чтобы выключить, установите bool и закройте сокет из другого потока. Блокировка recv() будет возвращена «немедленно» с ошибкой, но это не имеет значения, потому что вы все равно закончите :)

+4

Обратите внимание на возможное состояние гонки здесь, если поток когда-либо закрывает сам сокет: тогда вызов другого потока close() может случайно закрыть другой сокет, который впоследствии был создан с использованием того же номера сокета. –

+0

Возможно, лучше отправить сигнал другому потоку, если ваша ОС не перезапускает системные вызовы после сигнала или чего-то нелепого. – cHao

1

Я бы, скорее всего, использовал сигналы, как в случае @ alk answer (также обсуждалось here) ,

В качестве альтернативы вы можете использовать мультиплексированный ввод-вывод.

При инициализации создайте глобальную трубу (2). Когда пришло время завершить программу, закройте конец записи - теперь считывающий конец будет мгновенно выбирать (2)/опрос (2) для чтения (для EOF). Между тем ваши потоки, связанные с recv-подключением, включают в себя конец для чтения этого канала вместе с их сокетами, например, в вызове с блокировкой выбора (2) на неопределенный срок.Если считываемый конец канала становится читаемым, потоки ввода-вывода знают, что пришло время законно прекратить работу.

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

3

Либо:

  1. Установите тайм-аут чтения, с setsockopt() и SO_RCVTIMEO, и всякий раз, когда он запускает проверить переменную состояния, чтобы увидеть, если вы сказали себе, перестать читать.
  2. Если вы хотите прекратить чтение гнезда навсегда, выключите его для ввода с помощью shutdown(sd, SHUT_RD). Это приведет к возврату recv().
  3. Установите сокет в неблокирующий режим и используйте select() с таймаутом, чтобы рассказать вам, когда читать, принимая ту же стратегию с переменной состояния, что и в (1) выше. Однако неблокирующий режим приводит к значительным осложнениям в операции отправки, поэтому вы должны предпочесть (1) или (2) выше.

В вашем псевдокоде отсутствует EOS- и проверка ошибок. Надеюсь, на самом деле это не похоже.