Websockets Alguma experiência em desenvolvimento e operação. Nós modificamos o cliente

Congratulo-me com todos os interessados ​​neste protocolo e peço desculpas antecipadamente por minha nota excessivamente emocional. Envolvido neste assunto às pressas (conforme necessário), mas por um longo tempo. Nesse sentido, uma certa prática de projetar e usar essa tecnologia se acumulou e se formou. A opção de implementação de serviço é descrita aqui . Desde então, muita água correu. Os princípios básicos permaneceram os mesmos, mas o código do aplicativo em si sofreu modificações naturais. Em alguns lugares, erros não críticos foram encontrados e corrigidos; em algum lugar, o controle dos fluxos de programas, listas de conexões abertas etc. foi otimizado.

Mas, além do lado do servidor, como você sabe, há um lado do cliente. E aqui eu gostaria de parar mais detalhadamente e descrever o que eu tinha que enfrentar e o que poderia ser influenciado. Obviamente, ao usar o JavaScript, você não poderá "se divertir" especialmente, porque tudo está pronto e fechado, mas você pode dizer algo sobre o cliente Java.

Não importa o quão bom você seja um programador, é sempre difícil desenvolver, inventar algo único e depois depurar seus próprios versos. Portanto, certa vez, sucumbi à tentação de encontrar algo já pronto, permitindo que você o usasse imediatamente em meus projetos. Os critérios de seleção para o módulo finalizado eram simples. Eu queria obter um código de trabalho completo em Java com sobrecarga mínima.

Como o selecionado, decidi por um módulo usando as bibliotecas Apache. Em particular, estes:

  • apache-mime4j-core-0.7.2.jar;
  • httpclient-4.2.1.jar;
  • httpcore-4.2.1.jar;
  • httpmime-4.2.1.jar.

O que se pode dizer sobre o uso deles? O software Apache sempre foi famoso por sua confiabilidade, sofisticação e otimização. O cliente para Android funcionou com sucesso. O tamanho do arquivo * .apk finalizado não era criticamente grande. Não houve queixas especiais sobre o trabalho dessas bibliotecas. Mas a vida é sempre mais inteligente que nós. E o tempo (e esse período é de quatro a cinco anos) faz seus próprios ajustes. O aplicativo foi escrito quando havia uma versão do Android 4.2 - 4.4. E a necessidade de novas soluções já surgiu este ano, quando os dispositivos com a versão 10 já estavam em pleno andamento.

O desenvolvimento foi realizado no momento no Eclipse para Windows 7. A atualização do SDK do Android para o nível desejado levou ao fato de que o pequeno disco rígido SSD de 128 GB estava cheio. Eu tive que mudar para o Android Studio. Além disso, eu tive que mudar o sistema operacional básico. Tentei instalar o Ubuntu (não me lembro do número da versão) e usar o Studio já neste ambiente. Mas, novamente, falha, o Andriod Studio teimosamente não queria instalar.

Por que - já esquecido. No final, seguindo o conselho de amigos, instalei a versão mais recente do Linux-Mint e, e eis que o kit de ferramentas foi disponibilizado sem queixas. Então o que realmente aconteceu, exatamente por causa do qual todos esses detalhes foram descritos, a saber, a rotina de escrever testes.

Então, o que era esperado nesta tyagomotin? Vamos começar com o fato de que, no site oficial, o Apache copiou as versões mais atuais das bibliotecas acima. Adicionamos ao projeto e ... E erros de compilação caíram. O tempo passou, as interfaces de classe foram alteradas. Então, eu tive que (devido à falta de tempo para estudar novas bibliotecas) retornar às versões antigas. Mas ...

Mas, novamente, não estamos procurando maneiras fáceis. Eu pensei: por que preciso dessas bibliotecas completamente? Os textos desses pacotes são. E se você pegar e retirar apenas as aulas necessárias? Além disso, ao considerar os textos do módulo para trabalhar com soquetes da Web, você pode ver apenas duas classes dessas bibliotecas.

Então, criei um novo projeto e comecei a adicionar as classes necessárias. E, no final, descobriu-se que, para uma compilação bem-sucedida, é necessário retirar 32 classes. E, no entanto, sim, o projeto funcionou. Tudo estava respirando. A conexão com o serviço de soquete da web foi bem-sucedida. E tudo ficaria bem, mas notei o seguinte evento, incompreensível para mim. Ao fechar a conexão, o módulo responsável pela conexão lançou uma exceção:

java.io.EOFException
em java.io.DataInputStream.readByte (DataInputStream.java:77)
em com.example.wsci.HybiParser.start (HybiParser.java:112)
em com.example.wsci.WebSocketClient $ 1.run (WebSocketClient.java:144)
em java.lang.Thread.run (Thread.java:818)

Eu estava perdido. O seguinte confuso. A conexão com o servidor foi bem sucedida. Pacotes foram e vieram com sucesso. Mas, por que exatamente o fechamento estava lançando uma exceção? Além disso, tudo era padrão no servidor. Obviamente, em algum lugar do texto, os clientes tiveram alguma ninharia que afetou o fechamento. Além disso, os textos mostraram essa característica. De acordo com a cláusula 7.1.1 do documento, o fechamento no lado do cliente consiste não apenas em chamar o método close (), mas em formar e enviar um pacote com o código de operação 8 (operação de fechamento). Nesse caso, o servidor enviaria seu pacote de fechamento, após o qual o cliente fecharia a conexão. Mas, no nosso caso, essa sequência de chamadas não foi observada. Apenas chamou a função fechar e é isso. Em geral, havia algo em que pensar. E quanto mais eu olhava e estudava os textos deste módulo com o analisador de pacotes, menos eu gostava, mais havia um desejo de reescrevê-los com minha visão do protocolo. No final, foi decidido realizar esse "feito labor".

O que realmente não serviu, o que causou "protesto civil" nesses módulos? Primeiramente, a organização da interação entre o módulo de conexão direta com o servidor e o analisador de pacotes. Aconteceu que o módulo de conexão interagiu com o servidor, gerou um analisador para o qual passou um link para si mesmo como parâmetro. Como resultado, foi delegado ao analisador a autoridade para tomar decisões sobre os próximos eventos da rede. A este respeito, surgiu a questão, mas é bom? Não seria melhor se o módulo analisador cumprisse sua respectiva missão, retornasse o resultado de seu trabalho, mas a decisão de controle de eventos seria executada pelo objeto que gerou o analisador? Nesse caso, uma hierarquia estrita de interação entre objetos seria determinada. (Aqui, é claro, você pode debater o que é melhor - uma hierarquia ou uma rede, mas depois nos afastamos do tópico.)

A segunda coisa que me fez querer reescrever tudo foi a estrutura do analisador. Esse objeto (classe) deve cumprir duas funções principais, a saber, formar um pacote de dados para transmissão ao servidor e analisar os pacotes recebidos do servidor. Então, foram essas duas funções que não se adequaram, em geral. E aqui está a coisa.

Imagine que um evento de rede ocorreu, um pacote chegou. O que o HybiParser fez nesse caso? Este objeto leu os dois primeiros bytes do fluxo de entrada do soquete por byte e determinou suas próximas ações: analisar o tamanho dos dados, a máscara etc. Como resultado, isso foi implementado em várias operações de leitura do fluxo de entrada do soquete. Além disso, a análise foi complicada pelos estágios de leitura, o que complicou ainda mais o algoritmo. E, novamente, surgiu a questão, por que tais dificuldades? Não é melhor considerar um pacote como uma operação, principalmente porque o tamanho dos dados recebidos pode ser determinado?

O terceiro. Parece que mais um aspecto controverso do trabalho do analisador é o ciclo de aceitação de pacotes "eterno". O ciclo é executado em um fluxo de programa separado. Em algum momento, o soquete fecha. O que vem a seguir, o tratamento usual de exceções? Ou o que fazer? Não, não sou contra o mecanismo de exceções, mas seria bom prever antecipadamente essa circunstância e a reação a ela. Por exemplo, foi possível propor um mecanismo de sincronização como uma solução, durante a qual ocorrerá a conclusão regular do ciclo e, consequentemente, o fluxo do programa.

Como resultado da avaliação de todas essas nuances, foram determinados os seguintes requisitos para o design dos módulos necessários:

  • Os módulos devem ser independentes das bibliotecas de terceiros;
  • Os módulos devem ser simples e fáceis de integrar em outros projetos;
  • Os módulos devem estar prontos para futura expansão funcional.

Bem, e para não ser responsabilizado por críticas excessivas à solução pré-fabricada anterior, acrescentamos a isso adicionalmente que parte das funções pré-fabricadas e não críticas podem ser transferidas com segurança para uma nova implementação. Bem, isso é tudo, e como eles disseram no XXII Congresso da CPSU: “Nossos objetivos são claros, as tarefas são definidas. Para trabalhar, camaradas! Pelas novas vitórias do comunismo! ”

Em geral, para não sobrecarregar você, caro leitor, para não gastar seu tempo precioso, descreverei brevemente minha proposta e focarei apenas em pontos-chave.
Portanto, a implementação proposta contém apenas quatro módulos (de acordo com os requisitos acima da biblioteca Apache ou classes individuais deles não foram incluídas no projeto):

  • Módulo WebSocket protocolo 07 constantes globais;
  • Classe de exceção auxiliar;
  • Cliente de soquete da Web;
  • Módulo para analisar pacotes do nível 07 do protocolo WebSocket.

Nos dois primeiros módulos, a implementação é trivial, não há nada a considerar. O módulo cliente implementa o controle sobre a conexão com o servidor e eu gostaria de abordar os seguintes pontos. A função de conexão aberta possui um loop de cabeçalhos vindos do servidor. Aqui, a análise de chave Sec-WebSocket-Accept é realmente implementada e, no nosso caso, a análise é realizada sem o uso das bibliotecas Apache.

Em seguida, preste atenção às funções de controle de loop de pacotes. A implementação é trivial, através de um objeto de sincronização.

O próximo ponto a ser respeitado é a função do loop. O ciclo não é "eterno", mas com a saída sob a condição de verificar o objeto de sincronização. Em um ciclo, um pacote que chega é lido em uma operação. O pacote é analisado pelo objeto correspondente para análise. Em seguida, é tomada uma decisão de gerenciamento no próximo evento de rede.

Para concluir a descrição, observamos a função de encerrar a conexão com segurança. Isso é feito da seguinte maneira. Um pacote de terminação de conexão é enviado ao servidor e uma variável da classe Runnable é gerada, que é colocada no processador de filas para execução através da função postDelayed, um dos parâmetros do atraso na operação em milissegundos. Na variável Runnable, a função run contém uma sequência de chamadas quando o fluxo do programa termina e a conexão com o servidor é fechada. No caso de o pacote de resposta de fechamento chegar mais cedo, essa posição na lista de processamento é excluída.

A classe que implementa a análise de pacotes WebSocket contém dois métodos que requerem atenção: analisar a si próprio e a formação de um pacote para transmissão de acordo com os parâmetros relevantes. Ao analisar, todos os sinalizadores e dados do pacote recebido são armazenados em variáveis ​​públicas de classe. Por que público? Sim, por simplicidade, para não criar funções adicionais de obtenção / configuração para elas.

Bem, na verdade, caro leitor. O arquivo com o projeto para o Android Studio está anexado . Não farei nenhuma reivindicação ao uso desses textos em seus projetos. Críticas construtivas são aceitas. Responda às perguntas o máximo possível.

Source: https://habr.com/ru/post/pt478546/


All Articles