O conceito básico de codificador e decodificador netty

Tecnologia de codificação e decodificação

Normalmente, também estamos acostumados a chamar Encode como serialização, que serializa objetos em matrizes de bytes para transmissão de rede, persistência de dados ou outros fins. Ao contrário, a decodificação / desserialização restaura a matriz de bytes lida da rede, disco etc. para o objeto original (geralmente uma cópia do objeto original) para facilitar as operações subsequentes da lógica de negócios. Ao fazer chamadas de serviço de processo cruzado remoto (como chamadas RPC), você precisa usar uma tecnologia de codec específica para codificar ou decodificar objetos que precisam ser transmitidos pela rede para completar a chamada remota.

Por que o Netty fornece uma estrutura de codec?

Como uma estrutura de comunicação NIO assíncrona de alto desempenho, a estrutura de codec é uma parte importante do Netty. Embora da perspectiva do microkernel, a estrutura do codec não faça parte do microkernel Netty, mas a estrutura do codec personalizada e estendida por meio do ChannelHandler é indispensável. No entanto, já sabemos que no Netty, as mensagens de entrada lidas da rede precisam ser decodificadas e os datagramas binários são convertidos em mensagens de protocolo da camada de aplicativo ou mensagens de negócios antes que possam ser reconhecidas e processadas pela lógica de aplicativo superior; o mesmo, As mensagens do serviço de saída enviadas pelos usuários para a rede precisam ser codificadas e convertidas em uma matriz binária de bytes (ByteBuf para Netty) antes de serem enviadas para o par da rede. As funções de codificação e decodificação são parte integrante da estrutura NIO.Esta função é indispensável, seja implementada por extensões de customização de negócios ou pelos recursos integrados de codificação e decodificação da estrutura NIO. Para reduzir a dificuldade de desenvolvimento para os usuários, o Netty decorou as funções e APIs comumente usadas para proteger os detalhes de implementação subjacentes. A personalização da função do codec não é muito difícil para desenvolvedores familiarizados com a implementação subjacente do Netty para estender diretamente o desenvolvimento baseado no ChannelHandler. Mas para a maioria dos iniciantes ou usuários que não desejam entender os detalhes da implementação subjacente, eles precisam fornecer uma biblioteca de classes e API mais simples em vez de ChannelHandler. O Netty fez um trabalho muito bom nesse aspecto. Para a função de codec, ele não apenas fornece uma estrutura geral de codec para extensão de usuário, mas também fornece bibliotecas de codec comumente usadas para os usuários usarem diretamente. Com base na garantia de escalabilidade personalizada, tente reduzir a carga de trabalho de desenvolvimento do usuário e o limite de desenvolvimento para melhorar a eficiência do desenvolvimento.

A lista de funções de codec predefinidas pelo Netty é a seguinte: Base64, Protobuf, JBoss Marshalling, Spdy, etc.

Decodificadores comumente usados ​​no Netty

ByteToMessageDecoder decodificador abstrato

Ao usar NIO para programação de rede, geralmente é necessário decodificar a matriz de bytes lida ou buffer de bytes em um objeto POJO que pode ser usado pela empresa. Para facilitar a decodificação de ByteBuf em objetos POJO comerciais, o Netty fornece a classe de decodificação da ferramenta abstrata ByteToMessageDecoder. O decodificador definido pelo usuário herda ByteToMessageDecoder e só precisa implementar o método abstrato de decodificação void (ChannelHandler Context ctx, ByteBuf in, List <Object> out) para completar a decodificação de ByteBuf para o objeto POJO.

Uma vez que o ByteToMessageDecoder não considera cenários como a colagem e descompactação do TCP, o decodificador definido pelo usuário precisa lidar sozinho com o problema de "ler meio pacote". Por causa disso, a maioria das cenas não herdarão diretamente ByteToMessageDecoder, mas herdarão alguns decodificadores mais avançados para proteger o processamento de meio pacote. Em projetos reais, LengthFieldBasedFrameDecoder e ByteToMessageDecoder são geralmente usados ​​em combinação. O primeiro é responsável por decodificar o datagrama lido pela rede em um pacote inteiro de mensagens, e o último é responsável por decodificar todo o pacote de mensagens no objeto de negócios final. Além de se combinar com outros decodificadores para formar um novo decodificador, ByteToMessageDecoder também é a classe pai de muitos decodificadores básicos e sua relação de herança é mostrada na figura a seguir:

LineBasedFrameDecoder decodificador de linha

LineBasedFrameDecoder é um decodificador de retorno de carro e avanço de linha. Se a mensagem enviada pelo usuário usar um retorno de carro e avanço de linha (terminando com \ r \ n ou diretamente com \ n) como o final da mensagem, você pode usar diretamente LineBasedFrameDecoder do Netty para decodificar a mensagem. Você precisa adicionar o LineBasedFrameDecoder ao ChannelPipeline corretamente ao inicializar o servidor ou cliente Netty e não precisa reimplementar um conjunto de decodificadores de quebra de linha sozinho. O princípio de funcionamento de LineBasedFrameDecoder é que ele percorre os bytes legíveis em ByteBuf, por sua vez, para determinar se há "\ n" ou "\ r \ n". Em caso afirmativo, use esta posição como a posição final, do índice legível para a posição final Os bytes do intervalo formam uma linha. É um decodificador com um caractere de nova linha como o símbolo final e suporta dois métodos de decodificação com ou sem o caractere final e suporta a configuração do comprimento máximo de uma única linha. Se o caractere de nova linha ainda não for encontrado após a leitura do comprimento máximo continuamente, uma exceção será lançada e o fluxo de código de exceção lido anteriormente será ignorado. Evita o backlog irrestrito de ByteBuf recebido porque o datagrama não carrega um caractere de nova linha, causando estouro de memória do sistema

Normalmente, LineBasedFrameDecoder é usado em conjunto com StringDecoder para formar um decodificador de texto que muda por linha. Para a análise de protocolos de texto, o decodificador de quebra de texto é muito útil, como a análise de cabeçalhos de mensagens HTTP, mensagens de protocolo FTP, etc.

DelimiterBasedFrameDecoder Delimiter Decoder

DelimiterBasedFrameDecoder é um decodificador que decodifica de acordo com o delimitador especificado. Por meio do delimitador, o fluxo binário pode ser dividido em pacotes de dados completos. O decodificador de alimentação de linha de retorno de carro é, na verdade, um decodificador DelimiterBasedFrameDecoder especial.

Designação do separador: ao contrário do hábito de todos, o separador não usa char ou string como parâmetro de construção, mas ByteBuf

FixedLengthFrameDecoder decodificador de comprimento fixo

FixedLengthFrameDecoder é um decodificador de comprimento fixo que pode decodificar mensagens automaticamente de acordo com o comprimento especificado. Os desenvolvedores não precisam considerar a fixação / descompactação de TCP e outros problemas, o que é muito prático.

Para mensagens de comprimento fixo, se o comprimento real da mensagem for menor do que o comprimento fixo, as operações de preenchimento de bits são freqüentemente realizadas, o que leva a um desperdício de espaço e recursos até certo ponto. Mas suas vantagens também são muito óbvias, o codec é relativamente simples, então ainda existem certos cenários de aplicação em projetos reais.

Com o decodificador FixedLengthFrameDecoder, não importa quantos datagramas sejam recebidos de uma vez, ele decodificará de acordo com o comprimento fixo definido no construtor. Se for uma mensagem de meio pacote, FixedLengthFrameDecoder armazenará em buffer a mensagem de meio pacote e aguardará o próximo pacote chegar antes de ser montado até Um pacote completo foi lido.

Decodificador Universal LengthFieldBasedFrameDecoder

Qualquer pessoa que entenda o mecanismo de comunicação do TCP deve saber a aderência e descompactação subjacentes do TCP. Quando estamos recebendo mensagens, isso mostra que não podemos pensar que a mensagem lida é uma mensagem de pacote completo, especialmente para E / S sem bloqueio e O programa de comunicação de longa conexão.

Como distinguir um pacote inteiro de mensagens, geralmente existem quatro métodos a seguir:

1) Comprimento fixo, por exemplo, cada 120 bytes representa um pacote inteiro de mensagens, e os bits insuficientes são preenchidos na frente. O decodificador é relativamente simples de processar esse tipo de mensagem constante e decodificá-la após ler o comprimento especificado de bytes a cada vez;

2) Use o retorno de carro e a alimentação de linha para distinguir mensagens, como o protocolo HTTP. Essa forma de distinguir mensagens é usada principalmente em protocolos de texto;

3) Distinguir todo o pacote de mensagens por um separador específico;

4) Identifique toda a mensagem do pacote, definindo o campo de comprimento no cabeçalho do protocolo / cabeçalho da mensagem.

MessageToByteEncoder encoder abstrato

Como o decodificador, há uma classe abstrata chamada MessageToByteEncoder no codificador, que define o método de esqueleto do codificador, e a lógica de codificação específica é entregue à subclasse para implementação. O decodificador também é um manipulador, que intercepta os dados gravados. Quando aprendemos o Pipeline, sabíamos que o evento de gravação seria passado ao gravar os dados e o método de gravação do manipulador seria chamado durante o processo de transferência, para que o código do codificador pudesse ser reescrito. Escreva o método de gravação, codifique os dados em um fluxo de bytes binário e continue a transmitir o evento de gravação. Primeiro, olhe para a declaração de classe de MessageToByteEncoder: MessageToByteEncoder é responsável por codificar objetos POJO em ByteBuf, e o codificador do usuário herda Message ToByteEncoder e implementa a interface de codificação void (ChannelHandlerContextctx, I msg, ByteBuf out) interface

Seu princípio de implementação é o seguinte: ao chamar a operação de gravação, primeiro determine se o codificador atual suporta a mensagem que precisa ser enviada, se não for compatível, então passe-a diretamente; se for, ele determina o tipo de buffer e aloca ioBuffer (memória fora de heap) para memória direta. , A memória heap é alocada por meio do método heapBuffer.

Depois de concluída a alocação do buffer usado para a codificação, o método encode abstract é chamado para encode.O método é definido da seguinte forma: é implementado pela subclasse.

codificar void abstrato protegido (ChannelHandlerContext ctx, I msg, ByteBuf out) lança Exception;

Depois que a codificação for concluída, chame o método de liberação de ReferenceCountUtil para liberar o objeto de codificação msg. Os seguintes julgamentos são feitos no ByteBuf codificado: 1) Se o buffer contém bytes que podem ser enviados, chame o método write de ChannelHandlerContext para enviar o ByteBuf; 2) Se o buffer não contém bytes que podem ser gravados, você precisa liberar o ByteBuf codificado , Grave um ByteBuf vazio em ChannelHandlerContext.

Depois que a operação de envio for concluída, o objeto ByteBuf do buffer de codificação é liberado antes da saída do método.

MessageToMessageDecoder decodificador abstrato

MessageToMessageDecoder é, na verdade, o decodificador secundário do Netty, e sua responsabilidade é decodificar um objeto em outros objetos. Por que é chamado de decodificador secundário? Sabemos que o datagrama TCP lido de SocketChannel é ByteBuffer, que na verdade é uma matriz de bytes. Primeiro, precisamos ler o datagrama no buffer ByteBuffer e decodificá-lo em um objeto Java; em seguida, decodificar o objeto Java uma segunda vez de acordo com certas regras e decodificá-lo em outro objeto POJO. Como MessageToMessageDecoder vem depois de ByteToMessageDecoder, ele é chamado de decodificador secundário.

Codificador abstrato MessageToMessageEncoder

Para codificar um objeto POJO em outro objeto, use o protocolo HTTP + XML como exemplo.Uma maneira de implementá-lo é primeiro codificar o objeto POJO em uma string XML e, em seguida, codificar a string em uma mensagem de solicitação ou resposta HTTP. Para protocolos complexos, muitas vezes é necessário passar por várias codificações.Para facilitar a expansão da função, vários codificadores podem ser combinados para atingir funções relacionadas.

O decodificador do usuário herda o decodificador MessageToMessageEncoder e implementa o método de codificação void (Channel HandlerContext ctx, I msg, List out). Observe que a diferença entre ele e MessageToByteEncoder é que a saída é uma lista de objetos em vez de ByteBuf,

O princípio de implementação do codificador MessageToMessageEncoder é semelhante ao MessageToByteEncoder analisado anteriormente, a única diferença é que sua saída codificada é um objeto intermediário, não o ByteBuf final que pode ser transmitido.

Codificador serializado ObjectEncoder

ObjectEncoder é um codificador de serialização Java, que é responsável por serializar objetos que implementam a interface Serializable em byte [] e, em seguida, gravá-los em ByteBuf para transmissão de mensagens entre redes. Vamos analisar sua implementação juntos. Primeiro, descobrimos que ele herda de MessageToByteEncoder e sua função é codificar objetos em ByteBuf

Se você deseja usar a serialização Java, o objeto deve implementar a interface Serializable, portanto, seu tipo genérico é Serializable. A subclasse de MessageToByteEncoder só precisa implementar o método de codificação (ChannelHandlerContext ctx, I msg, ByteBuf out)

Primeiro crie ByteBufOutputStream e ObjectOutputStream para serializar objetos Object em ByteBuf. É importante observar que o campo de comprimento (4 bytes) precisa ser reservado antes de gravarObject para a atualização subsequente do campo de comprimento. Escreva o marcador de posição de comprimento (4 bytes), o objeto Object serializado por sua vez, depois calcule o comprimento do fluxo de código serializado de acordo com writeIndex de ByteBuf e, finalmente, chame setInt (índice int, valor int) de ByteBuf para atualizar o marcador de posição de comprimento O símbolo é o comprimento real do fluxo de código. Há um detalhe que precisa ser observado. O método setInt é usado para atualizar o campo de comprimento do fluxo de código em vez de writeInt. O motivo é que o método setInt apenas atualiza o conteúdo e não modifica o readerIndex e o writerIndex.

Codificador universal LengthFieldPrepender

Se o primeiro campo no protocolo for um campo de comprimento, Netty fornece o codificador LengthFieldPrepender, que pode calcular o comprimento de byte binário da mensagem atual a ser enviada e adicionar o comprimento ao cabeçalho do buffer ByteBuf

Através do LengthFieldPrepender, o comprimento da mensagem a ser enviada pode ser escrito nos primeiros 2 bytes de ByteBuf, e a mensagem codificada é composta de campo de comprimento + mensagem original.

Ao definir LengthFieldPrepender como true, o comprimento da mensagem incluirá o número de bytes ocupados pelo próprio comprimento, após a abertura de LengthFieldPrepender

O princípio de funcionamento de LengthFieldPrepender é analisado da seguinte maneira: Primeiro, defina o campo de comprimento.Se o próprio comprimento da mensagem precisar ser incluído, adicione o comprimento de lengthFieldLength ao comprimento original. Se o comprimento da mensagem ajustado for menor que 0, uma exceção de parâmetro ilegal será lançada. Avalie o número de bytes ocupados pelo próprio comprimento da mensagem, de modo a usar o método correto para escrever o campo de comprimento em ByteBuf. Existem seis possibilidades como segue: 1) O byte ocupado pelo campo de comprimento é 1: Se 1 byte for usado A seção representa o comprimento da mensagem e o comprimento máximo deve ser inferior a 256 bytes. Verifique o comprimento. Se a verificação falhar, uma exceção de parâmetro ilegal será lançada; se a verificação for aprovada, um novo ByteBuf é criado e o valor do comprimento é gravado no ByteBuf por meio de writeByte; 2) O campo de comprimento ocupa 2 bytes : Se 2 bytes forem usados ​​para representar o comprimento da mensagem, o comprimento máximo precisa ser inferior a 65536 bytes. O comprimento é verificado. Se a verificação falhar, uma exceção de parâmetro ilegal será lançada; se a verificação for aprovada, um novo ByteBuf será criado E escreva o valor do comprimento em ByteBuf por meio de writeShort; 3) O campo de comprimento ocupa 3 bytes: se 3 bytes de bytes são usados ​​para representar o comprimento da mensagem, o comprimento máximo precisa ser menor que 16777216 bytes, e o comprimento é verificado, Se a verificação falhar, uma exceção de parâmetro ilegal será lançada; se a verificação for aprovada, um novo ByteBuf é criado e o valor do comprimento é gravado no ByteBuf por meio de writeMedium; 4) O campo de comprimento ocupa 4 bytes: Crie um novo ByteBuf, E escreva o valor do comprimento em ByteBuf por meio de writeInt; 5) O campo de comprimento ocupa 8 bytes: crie um novo ByteBuf e escreva o valor de comprimento em ByteBuf por meio de writeLong; 6) Outros valores de comprimento: lance Error diretamente .

Acho que você gosta

Origin blog.csdn.net/madongyu1259892936/article/details/109669058
Recomendado
Clasificación