paulo1205
(usa Ubuntu)
Enviado em 09/11/2016 - 12:05h
O comportamento normal -- pelo menos no UNIX -- quando você manda ler de um
pipe é que a operação de leitura fique bloqueada enquanto não chegarem todos os
bytes solicitados. Por exemplo, se eu mando ler mil
bytes , mas o remetente só envia novecentos e noventa e nove, a operação de leitura vai ficar parada esperando pelo
byte que falta para completar os mil.
No seu caso, você manda ler de
byte em
byte , mas faz essa operação em
loop . Desse modo, se o remetente enviar um total de cem bytes, as cem primeiras iterações do loop de leitura vão completar imediatamente, mas a centésima primeira vai parar, esperando por mais um
byte , mesmo que ele nunca seja enviado pelo transmissor.
A única forma de forçar o sincronismo de um
pipe através do próprio
pipe é fechar a ponta de envio de dados. Nesse caso, o sistema operacional responde aos eventos de leitura no
pipe com uma quantidade de
bytes menor do que a solicitada. Uma leitura de tamanho zero implica que a comunicação chegou ao final.
Para contornar o bloqueio em operações de leitura há diversos meios. Dentre eles, lembro agora dos seguintes:
- Usar
select () ou
poll () para examinar o descritor de leitura, a fim de ver se há bytes disponíveis para leitura antes de disparar a operação de leitura propriamente dita. Ao usar essas funções, você pode inclusive optar por usar um intervalo máximo de espera (
timeout ) para que cheguem dados pelo canal de comunicação.
- Marcar o descritor de leitura como não-blocante, através de uma chamada a
fcntl (). Nesse caso, chamadas a
read () num
pipe em que a ponta de leitura ainda estiver aberta podem retornar com uma contagem de
bytes menor do que a solicitada. No caso de não haver dados disponíveis no momento, o valor de retorno será
-1 , e
errno terá o valor
EAGAIN (ou
EWOULDBLOCK ; dependendo da implementação, os dois nomes podem inclusive ser equivalentes). O fechamento da ponta de escrita continuará sendo indicado por um valor de retorno zero para
read ().
- Usar alarmes assíncronos ou
timers cercando a operação de leitura, de modo que, se ela demorar mais do que o tempo programado, ela seja interrompida. Nesse caso, se
read () já tiver recebido alguns
bytes , ela pode retornar com uma contagem menor do que a solicitada. Se nenhum
byte tiver ainda sido lido, ela o valor de retorno é
-1 , e o código de erro em
errno é
EINTR .
Em conjunto com todos os casos acima, mas que também pode servir como quarta alternativa, você deve criar um protocolo de comunicação que lhe possibilite diminuir o esforço com operações de entrada e saída, bem como facilitar a identificação de possíveis pontos favoráveis a uma pausa na leitura.
Por exemplo, se você estabelecer que suas mensagens são orientadas a linha, você poderia se beneficiar das funções da biblioteca de E/S que já fazem operações orientadas a linha, e pode examinar cada linha lida a fim de ver se elas indicar que é o momento de parar de ler. O código abaixo dá uma ideia aproximada disso (não testei o código, mas ele pode ter algumas
race conditions , particularmente ).
int read_errno;
unsigned n_lines=0;
char line[1000];
size_t line_len;
char *rc;
FILE *pipe_in;
pipe_in=fdopen(pipe_fd[0], "r");
setvbuf(pipe_in, NULL, _IOLBF, 0); // OPCIONAL: pode melhorar (ou piorar -- não testei) num protocolo orientado a linhas.
while(1){
alarm(5); // Define um timeout de 5 segundos.
errno=0;
rc=fgets(line, sizeof line, pipe_in);
read_errno=errno; // Salvo errno porque a próxima operação pode interferir com ele.
alarm(0); // Desliga alarme após fim da operação de leitura.
if(rc==NULL){
if(read_errno==EINTR)
continue; // Timeout sem dados: repete o loop esperando a chegada de dados.
if(!feof(pipe_in)) // Testa se chegou ao fim de dados (fechamento da ponta de escrita do pipe).
fprintf(stderr, "Erro de leitura ao ler a %dª linha: %s.\n", n_lines+1, strerror(read_errno));
break;
}
line_len=strlen(line);
if(line[line_len-1]!='\n'){
// Linha incompleta: pode ser longa demais, ou pode ser que faltem dados no pipe.
if(line_len+1==sizeof line){
// Dados não cabem na linha. Dá o tratamento que você definir.
}
else{
// Timeout durante a leitura da linha. Você pode querer
// continuar lendo a partir do ponto em que parou, ou pode
// abortar. Você escolhe.
}
}
else{
// Linha completa. Pode tomar decisões de protocolo em função do conteúdo recebido.
++n_lines;
line[line_len-1]='\0';
if(strcmp(line, "abort")==0){
fclose(pipe_in);
break;
}
else if(strncmp(line, "echo ", 5)==0){
puts(line+5);
}
else{
fprintf(stderr, "Comando inválido \"%s\" na linha %u.\n", line, n_lines);
}
}
}