2013-08-16 2 views
5

C'è un filo piace questo:Come lasciare che un thread che blocca su recv() esca con garbo?

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

perché il thread sta bloccando il recv() chiamata, come posso lasciare che l'uscita filo con grazia?

+1

Verificare il valore restituito di 'recv' e' break' quando è 0 o negativo? – cnicutar

+0

Stai chiedendo se c'è un modo per rendere il timeout della chiamata recv() dopo un intervallo impostato se non ottiene una risposta? – gwilkins

risposta

5

È possibile chiamare: -

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

check this fuori.

+0

Il terminale remoto potrebbe trovarsi in un altro processo su un altro host. –

2

Dichiarare una bandiera uscita globale:

int bExit = 0; 

Facciamo le parti cirtical testarlo:

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

    ... 
} 

Per rompere il lettore, prima impostare il flag di uscita

bExit = 1; 

quindi inviare un segnale al thread del lettore

pthread_kill(pthreadReader, SIGUSR1); 

Nota: Quello che ho lasciato fuori in questo esempio è la protezione della bExit contro l'accesso simultaneo.

Questo potrebbe essere ottenuto utilizzando un mutex o una dichiarazione appropriata.

+2

Si noti che: _Se una chiamata bloccata a una delle seguenti interfacce (recv inclusa), viene interrotta da un gestore di segnale, la chiamata verrà automaticamente riavviata dopo che il gestore di segnale viene restituito se è stato utilizzato il flag SA_RESTART; altrimenti la chiamata fallirà con l'errore EINTR._ Inoltre, si confonde 'errno' con' recv() 'valore restituito. –

+0

@MaximYegorushkin: Grazie per avermi indirizzato alla confusione 'result' /' errno'. In qualche modo ero ancora in 'pthread_ *' -API che restituisce i valori di 'errno' ma imposta' errno' stessa. Anche il suggerimento riguardante 'SA_RESTART' è importante, mi mancava farlo, grazie ancora per questo. – alk

5

Non bloccare su recv. Piuttosto implementare un blocco su 'select' e testare l'input fdset per determinare se c'è qualcosa da leggere in primo luogo.

Se sono presenti dati da leggere, chiamare recv. In caso contrario, loop, controlla la variabile booleana volitile 'isrunning' che viene impostata dall'altro thread una volta avviato l'arresto.

+0

Penso che l'efficienza di questo metodo sia bassa, giusto? –

+2

In realtà è molto alto. Ti consente di servire più socket simultaneamente per thread. Ho usato questo metodo su server che ospitano migliaia di connessioni. I blocchi di 64 socket sono stati gestiti tramite select per thread. Anche a pieno carico il carico della CPU è rimasto inferiore al dieci percento. –

+0

: grazie! Secondo il mio understaning, il tuo modello di programma non è ogni thread che serve ogni socket, ma un thread che serve molti socket. Pensi che ogni thread che serve ogni socket sia più efficiente? –

0

Inoltre, impostare la presa da bloccano il sistema:

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); 
} 

Quindi, è possibile chiamare recv() sul socket, e non bloccherà. Se non c'è niente da leggere poi la variabile globale ERRNO è impostata alla costante EWOULDBLOCK

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

'if (errno! = EWOULDBLOCK) {' dovrebbe essere meglio 'if ((errno! = EWOULDBLOCK) && (errno! = EAGAIN)) {'. – alk

+0

@alk Hanno lo stesso valore quando sono entrambi definiti. – EJP

2

Dichiarare un po booleano 'stop', a controllare dopo ogni recv ritorno() e terminare se è impostato. Per spegnere, imposta il bool e chiudi il socket da un altro thread. Il blocco recv() restituirà 'immediatamente' un errore, ma non importa 'perché stai per terminare comunque :)

+4

Nota la potenziale condizione di competizione qui se il thread chiude mai il socket stesso: quindi la chiamata di altro thread to close() potrebbe chiudere accidentalmente qualche altro socket che è stato successivamente creato utilizzando lo stesso numero di socket. –

+0

Potrebbe essere meglio inviare un segnale all'altro thread, se il sistema operativo non riavvia i syscalls dopo un segnale o qualcosa di pazzesco come quello. – cHao

1

Probabilmente userò segnali come in @ alk's fine answer (anche discusso here) .

In alternativa, è possibile utilizzare I/O multiplex.

All'inizializzazione, creare una pipe globale (2). Quando è il momento di terminare il programma, chiudi la fine di scrittura del tubo - ora il read-end sceglierà immediatamente (2)/sondaggio (2) leggibile (per EOF). Nel frattempo, i tuoi thread recv-bloccati includono il read-end di questa pipe insieme ai loro socket, per esempio, una chiamata select (2) a blocco indefinito.Se il read-end della pipe ritorna leggibile, i thread I/O sanno che è ora di terminare con grazia.

L'avvertimento principale di questa tecnica è di assicurare che il fine-scrittura venga chiuso una sola volta, in quanto un successivo, ingenuo vicino (2) potrebbe stroncare un file innocente a cui è stato assegnato lo stesso descrittore del vecchio tubo write-end.

3

O:

  1. Impostare un timeout di lettura, con setsockopt() e SO_RCVTIMEO, e ogni volta che si innesca controllare una variabile di stato per vedere se ti sei detto di smettere di leggere.
  2. Se si desidera interrompere la lettura del socket per sempre, spegnerlo per l'immissione con shutdown(sd, SHUT_RD). Ciò causerà recv() per restituire zero da ora in poi.
  3. Impostare il socket in modalità non bloccante e utilizzare select() con un timeout per indicare quando leggere, adottando la stessa strategia con una variabile di stato come (1) sopra. Tuttavia, la modalità non bloccante introduce notevoli complicazioni nell'operazione di invio, quindi dovresti preferire (1) o (2) sopra.

Il tuo pseudo-codice manca di EOS- e di controllo degli errori. Spero non sia proprio così.