Como detectar quando uma mensagem de buffer de protocolo é totalmente recebida?

Este é um tipo de ramificação da minha outra pergunta . Leia se quiser, mas não é necessário.

Basicamente, percebi que para usar efetivamente o BeginReceive () do C # em mensagens grandes, eu preciso (a) ler o comprimento do pacote primeiro, depois ler exatamente quantos bytes ou (b) usar um delimitador de fim de pacote. Minha pergunta é, estão presentes em buffers de protocolo? Ainda não os usei, mas repassando a documentação, não parece haver um header de comprimento ou um delimitador.

Se não, o que devo fazer? Devo apenas construir a mensagem e prefixar / sufixo com o header de comprimento / delimitador de EOP?

Você precisa include o tamanho ou o marcador final em seu protocolo. Nada é embutido em sockets baseados em stream (TCP / IP) além de suportar um stream indefinido de octetos dividido arbitrariamente em pacotes separados (e os pacotes podem ser derramados em trânsito também).

Uma abordagem simples seria que cada “mensagem” tivesse um header de tamanho fixo, incluísse tanto uma versão de protocolo quanto um tamanho de carga útil e quaisquer outros dados fixos. Então o conteúdo da mensagem (payload).

Opcionalmente, um rodapé de mensagem (tamanho fixo) pode ser adicionado com uma sum de verificação ou até mesmo com uma assinatura criptográfica (dependendo de seus requisitos de confiabilidade / segurança).

Conhecer o tamanho da carga útil permite que você continue lendo um número de bytes que será suficiente para o restante da mensagem (e se uma leitura for concluída com menos, fazendo outra leitura para os bytes restantes até que toda a mensagem tenha sido recebida).

Ter um indicador de mensagem final também funciona, mas você precisa definir como lidar com a mensagem que contém a mesma seqüência de octetos …

Desculpas para chegar tarde na festa. Eu sou o autor do protobuf-net, uma das implementações do C #. Para uso de rede, você deve considerar os methods “[De] SerializeWithLengthPrefix” – assim, ele irá lidar automaticamente com os comprimentos para você. Existem exemplos na fonte.

Eu não vou entrar em detalhes enormes em um post antigo, mas se você quiser saber mais, adicione um comentário e eu vou voltar para você.

Concordo com Matt que um header é melhor que um rodapé para Protocol Buffers, pela principal razão de que, como PB é um protocolo binário, é problemático criar um rodapé que também não seria uma sequência de mensagens válida. Muitos protocolos baseados em rodapé (normalmente os EOLs) funcionam porque o conteúdo da mensagem está em um intervalo definido (geralmente 0x20 – 0x7F ASCII).

Uma abordagem útil é ter seu código de nível mais baixo apenas lendo buffers fora do socket e apresentá-los a uma camada de enquadramento que reúne mensagens completas e lembra as parciais (eu apresento uma abordagem assíncrona a isso (usando o CCR) aqui , embora para um protocolo de linha).

Para consistência, você sempre pode definir sua mensagem como uma mensagem PB com três campos: um int fixo como o comprimento, um enum como o tipo e uma seqüência de bytes que contém os dados reais. Isso mantém todo o seu protocolo de rede transparente.

Os pacotes TCP / IP, bem como UDP, incluem alguma referência ao seu tamanho. O header IP contém um campo de 16 bits que especifica o comprimento do header IP e os dados em bytes. O header TCP contém um campo de 4 bits que especifica o tamanho do header TCP em palavras de 32 bits. O header UDP contém um campo de 16 bits que especifica o comprimento do header UDP e os dados em bytes.

Aqui está a coisa.

Usando os sockets padrão comuns do Windows, esteja você usando o namespace System.Net.Sockets em C # ou o material nativo do Winsock no Win32, nunca verá os headers IP / TCP / UDP. Esses headers são removidos para que o que você obtém ao ler o soquete seja a carga real, isto é, os dados que foram enviados.

O padrão típico de tudo que eu já vi e fiz usando sockets é que você define um header no nível do aplicativo que precede os dados que você deseja enviar. No mínimo, esse header deve include o tamanho dos dados a serem seguidos. Isso permitirá que você leia cada “mensagem” em sua totalidade sem ter que adivinhar seu tamanho. Você pode obter o máximo que quiser com ele, por exemplo, padrões de synchronization, CRCs, versão, tipo de mensagem, etc., mas o tamanho da “mensagem” é tudo o que você realmente precisa.

E pelo que vale a pena, sugiro usar um header em vez de um delimitador de fim de pacote. Não tenho certeza se existe uma desvantagem significativa para o delimitador EOP, mas o header é a abordagem usada pela maioria dos protocolos IP que já vi. Além disso, parece-me mais intuitivo processar uma mensagem desde o início, em vez de esperar que algum padrão apareça no meu stream para indicar que minha mensagem está completa.

EDIT: Acabei de tomar conhecimento do projeto Google Protocol Buffers. Pelo que eu posso dizer, é um esquema de serialização binária / desserialização para o WCF (tenho certeza que é uma simplificação grosseira). Se você estiver usando o WCF, não precisará se preocupar com o tamanho das mensagens enviadas, pois o encanamento WCF cuida disso nos bastidores, o que provavelmente explica por que você não encontrou nada relacionado ao tamanho da mensagem no Protocolo. Documentação de buffers. No entanto, no caso de sockets, saber o tamanho ajudará tremendamente como discutido acima. Meu palpite é que você irá serializar seus dados usando os Buffers de Protocolo e, em seguida, colocar o header do aplicativo que você quiser antes de enviá-lo. No lado do recebimento, você vai retirar o header e depois desserializar o restante da mensagem.