O que acontece com dados auxiliares de fluxo unix em leituras parciais?

18

Então, eu li muitas informações sobre dados auxiliares de fluxo unix, mas uma coisa que falta em toda a documentação é o que deveria acontecer quando há uma leitura parcial?

Suponha que eu esteja recebendo as seguintes mensagens em um buffer de 24 bytes

msg1 [20 byes]   (no ancillary data)
msg2 [7 bytes]   (2 file descriptors)
msg3 [7 bytes]   (1 file descriptor)
msg4 [10 bytes]  (no ancillary data)
msg5 [7 bytes]   (5 file descriptors)

Na primeira chamada para recvmsg, recebo todo o msg1 (e parte do msg2? O sistema operacional fará isso?) Se eu faço parte do msg2, obtenho os dados auxiliares imediatamente e preciso salvá-los para a próxima leitura quando eu sei o que a mensagem estava realmente me dizendo para fazer com os dados? Se eu liberar os 20 bytes do msg1 e depois ligar novamente para recvmsg, ele entregará o msg3 e o msg4 ao mesmo tempo? Os dados auxiliares de msg3 e msg4 são concatenados na estrutura da mensagem de controle?

Embora eu possa escrever programas de teste para descobrir isso experimentalmente, estou procurando documentação sobre como os dados auxiliares se comportam em um contexto de streaming. Parece estranho que eu não consiga encontrar nada oficial nele.


Vou adicionar minhas descobertas experimentais aqui, que obtive deste programa de teste:

https://github.com/nrdvana/daemonproxy/blob/master/src/ancillary_test.c

Linux 3.2.59, 3.17.6

Parece que o Linux anexará partes das mensagens de suporte auxiliar ao final de outras mensagens, desde que nenhuma carga útil auxiliar anterior precise ser entregue durante esta chamada para recvmsg. Quando os dados auxiliares de uma mensagem estiverem sendo entregues, ele retornará uma leitura curta em vez de iniciar a próxima mensagem de dados auxiliares. Portanto, no exemplo acima, as leituras que recebo são:

recv1: [24 bytes] (msg1 + partial msg2 with msg2's 2 file descriptors)
recv2: [10 bytes] (remainder of msg2 + msg3 with msg3's 1 file descriptor)
recv3: [17 bytes] (msg4 + msg5 with msg5's 5 file descriptors)
recv4: [0 bytes]

BSD 4.4, 10.0

O BSD fornece mais alinhamento que o Linux e fornece uma breve leitura imediatamente antes do início de uma mensagem com dados auxiliares. Mas, felizmente, anexará uma mensagem de suporte não auxiliar ao final de uma mensagem de suporte auxiliar. Portanto, para o BSD, parece que se o seu buffer for maior que a mensagem de suporte auxiliar, você terá um comportamento quase semelhante a um pacote. As leituras que recebo são:

recv1: [20 bytes] (msg1)
recv2: [7 bytes]  (msg2, with msg2's 2 file descriptors)
recv3: [17 bytes] (msg3, and msg4, with msg3's 1 file descriptor)
recv4: [7 bytes]  (msg5 with 5 file descriptors)
recv5: [0 bytes]

FAÇAM:

Ainda gostaria de saber como isso acontece no Linux, iOS, Solaris, etc. mais antigo, e como isso pode acontecer no futuro.

M Conrad
fonte
Não confunda fluxos e pacotes; em um fluxo, não há garantia de que os dados serão entregues nos mesmos blocos que foram enviados; para isso, você precisaria de um protocolo baseado em pacotes e não de um fluxo.
Ctrl-alt-delor
isso é precisamente por isso que eu estou fazendo esta pergunta
M Conrad
O pedido deve ser preservado. É isso que os fluxos fazem. Se uma leitura de bloqueio retornar 0, será o fim do fluxo. Se retornar outro número, pode haver mais, você precisa fazer pelo menos mais uma leitura para descobrir. Não existe mensagem1, mensagem2 etc. Nenhum delimitador de mensagem é transmitido. Você precisa adicionar isso ao seu protocolo, se precisar.
Ctrl-alt-delor 17/02/2015
11
Especificamente, tenho um protocolo de fluxo de texto e estou adicionando um comando que passa um descritor de arquivo com uma linha de texto. Preciso saber em que ordem esses dados auxiliares são recebidos em relação ao texto da mensagem para escrever o código corretamente.
M Conrad
11
@Conrad: Eu tentaria obter uma cópia da especificação POSIX.1g. Se não estiver explicitamente escrito lá, você poderá esperar um comportamento específico da implementação.
Laszlo Valko 24/02

Respostas:

1

Os dados auxiliares são recebidos como se estivessem na fila junto com o primeiro octeto de dados normal no segmento (se houver).

- POSIX.1-2017

Para o resto da sua pergunta, as coisas ficam um pouco peludas.

... Para os fins desta seção, um datagrama é considerado um segmento de dados que encerra um registro e inclui um endereço de origem como um tipo especial de dados auxiliares.

Os segmentos de dados são colocados na fila à medida que os dados são entregues ao soquete pelo protocolo. Segmentos de dados normais são colocados no final da fila conforme são entregues. Se um novo segmento contiver o mesmo tipo de dados que o segmento anterior e não incluir dados auxiliares, e se o segmento anterior não terminar um registro, os segmentos serão mesclados logicamente em um único segmento ...

Uma operação de recebimento nunca deve retornar dados ou dados auxiliares de mais de um segmento.

Soquetes BSD modernos combinam exatamente com esse extrato. Isso não é surpreendente :-).

Lembre-se de que o padrão POSIX foi escrito após o UNIX e após divisões como BSD x Sistema V. Um dos principais objetivos era ajudar a entender o intervalo de comportamento existente e impedir divisões ainda maiores nos recursos existentes.

O Linux foi implementado sem referência ao código BSD. Parece se comportar de maneira diferente aqui.

  1. Se eu ler corretamente, parece que Linux é adicionalmente fusão "segmentos", quando um novo segmento faz incluir dados auxiliares, mas o segmento anterior não.

  2. Seu argumento de que "o Linux anexará partes das mensagens que contêm acessórios ao final de outras mensagens, desde que nenhuma carga auxiliar anterior precise ser entregue durante esta chamada para recvmsg", não parece totalmente explicada pelo padrão. Uma explicação possível envolveria uma condição de corrida. Se você ler parte de um "segmento", receberá os dados auxiliares. Talvez o Linux tenha interpretado isso como significando que o restante do segmento não conta mais como incluindo dados auxiliares! Portanto, quando um novo segmento é recebido, ele é mesclado - de acordo com o padrão ou com a diferença 1 acima.

Se você deseja escrever um programa portátil máximo, evite essa área completamente. Ao usar dados auxiliares, é muito mais comum usar soquetes de datagramas . Se você deseja trabalhar em todas as plataformas estranhas que tecnicamente aspiram fornecer algo parecido com o POSIX, sua pergunta parece estar se aventurando em um canto escuro e não testado.


Você poderia argumentar que o Linux ainda segue vários princípios significativos:

  1. "Os dados auxiliares são recebidos como se estivessem na fila junto com o primeiro octeto de dados normal no segmento".
  2. Os dados auxiliares nunca são "concatenados", como você diz.

No entanto, não estou convencido de que o comportamento do Linux seja particularmente útil quando você o compara ao comportamento do BSD. Parece que o programa que você descreve precisaria adicionar uma solução alternativa específica para Linux. E não sei uma justificativa para o motivo pelo qual o Linux espera que você faça isso.

Pode parecer sensato ao escrever o código do kernel Linux, mas sem nunca ter sido testado ou exercido por nenhum programa.

Ou pode ser exercido por algum código de programa que funciona principalmente nesse subconjunto, mas em princípio pode ter "bugs" ou condições de corrida.

Se você não consegue entender o comportamento do Linux e seu uso pretendido, acho que isso significa tratar isso como um "canto obscuro e não testado" no Linux.

sourcejedi
fonte
Obrigado pela revisão aprofundada! Eu acho que o principal aqui é que eu posso lidar com isso com segurança com dois buffers (cada um com a parte de dados e a parte auxiliar); Se eu receber descritores de arquivo na primeira leitura e eles não pertencerem à mensagem, mas outra mensagem iniciar, se a próxima leitura também contiver dados auxiliares, significa que definitivamente encontrarei o final da minha mensagem de dados como a primeira carga útil auxiliar. nessa segunda leitura. Alternando para frente e para trás, sempre devo ser capaz de corresponder mensagem com carga com base na localização do primeiro byte.
M Conrad