Websockets Alguna experiencia en desarrollo y operación. Modificamos al cliente

Doy la bienvenida a todos los interesados ​​en este protocolo, y de antemano me disculpo por mi nota demasiado emocional. Participó en este tema a toda prisa (según sea necesario), pero durante mucho tiempo. En este sentido, se ha acumulado y formado una práctica determinada de diseño y uso de esta tecnología. La opción de implementación del servicio se describe aquí . Desde entonces ha corrido mucha agua. Los principios básicos se han mantenido igual, pero el código de la aplicación en sí ha sufrido modificaciones naturales. En algunos lugares, se encontraron y corrigieron errores no críticos, en algún lugar se optimizó el control de los flujos del programa, las listas de conexiones abiertas, etc.

Pero además del lado del servidor, como saben, hay un lado del cliente. Y aquí me gustaría detenerme más a fondo y describir lo que tuve que enfrentar y lo que podría ser influenciado. Por supuesto, cuando use JavaScript, no podrá "divertirse" especialmente, porque todo está listo y cerrado, pero puede decir algo sobre el cliente en Java.

No importa cuán bueno sea un programador, siempre es difícil desarrollar, inventar algo único y luego depurar sus propios versos. Por lo tanto, en un momento sucumbí a la tentación de encontrar algo que ya estaba listo, permitiéndote usarlo de inmediato en mis proyectos. Los criterios de selección para el módulo terminado fueron simples. Quería obtener un código completo y funcional en Java con una sobrecarga mínima.

Como el seleccionado, me instalé en un módulo usando las bibliotecas de Apache. En particular, estos:

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

¿Qué se puede decir sobre su uso? El software Apache siempre ha sido famoso por su confiabilidad, sofisticación y optimización. El cliente para Android funcionó con éxito. El tamaño del archivo * .apk terminado no era críticamente grande. No hubo quejas especiales sobre el trabajo de estas bibliotecas. Pero la vida siempre es más inteligente que nosotros. Y el tiempo (y este período es de aproximadamente cuatro a cinco años) hace sus propios ajustes. La aplicación se escribió cuando había una versión de Android 4.2 - 4.4. Y la necesidad de nuevas soluciones surgió este año, cuando los dispositivos con la versión 10 ya estaban en pleno apogeo.

El desarrollo se realizó en ese momento en Eclipse para Windows 7. La actualización del SDK de Android al nivel deseado llevó al hecho de que el pequeño disco duro SSD de 128 GB estaba lleno. Tuve que cambiar a Android Studio. Además, tuve que cambiar el sistema operativo base. Traté de instalar Ubuntu (no recuerdo el número de versión) y ya uso Studio en este entorno. Pero, de nuevo, el fracaso, Andriod Studio tercamente no quería instalar.

Por qué, ya olvidado. Al final, siguiendo el consejo de mis amigos, instalé la última versión de Linux-Mint y, he aquí, el kit de herramientas lo dejó sin quejas. Luego, lo que realmente sucedió, precisamente por lo que se describieron todos estos detalles, es decir, la rutina de escribir pruebas.

Entonces, ¿qué se esperaba en esta tyagomotina? Comencemos con el hecho de que desde el sitio oficial Apache copió versiones más actuales de las bibliotecas anteriores. Los agregó al proyecto y ... Y los errores de compilación cayeron. El tiempo ha pasado, las interfaces de clase han cambiado. Así que (debido a la falta de tiempo para estudiar nuevas bibliotecas) tuve que volver a las versiones anteriores. Pero ...

Pero, de nuevo, no estamos buscando formas fáciles. Pensé, ¿por qué necesito estas bibliotecas por completo? Los textos de estos paquetes son. ¿Qué pasa si tomas y sacas solo las clases necesarias de ellos? Además, al considerar los textos del módulo para trabajar con sockets web, puede ver solo dos clases de estas bibliotecas.

Así que creé un nuevo proyecto y comencé a agregar las clases necesarias. Y al final resultó que para una compilación exitosa es necesario sacar 32 clases. Y sin embargo, sí, el proyecto funcionó. Todo respiraba. La conexión al servicio de socket web fue exitosa. Y todo estaría bien, pero noté el siguiente evento, incomprensible para mí. Al cerrar la conexión, el módulo responsable de la conexión lanzó una excepción:

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

Estaba perdido. Lo siguiente confundido. La conexión al servidor fue exitosa. Los paquetes llegaron y se fueron con éxito. Pero, ¿por qué exactamente el cierre arrojó una excepción? Además, todo era estándar en el servidor. Obviamente, en algún lugar del texto, los clientes tenían algunas cosas que afectaban el cierre. Además, los textos mostraron tal característica. De acuerdo con la cláusula 7.1.1 del documento, el cierre del lado del cliente consiste no solo en llamar al método close (), sino en formar y enviar un paquete con el código de operación 8 (operación de cierre). En este caso, el servidor enviaría su paquete de cierre, después de lo cual el cliente cerraría la conexión. Pero en nuestro caso, tal secuencia de llamadas no se observó. Simplemente llamó a la función de cierre y eso es todo. En general, había algo en lo que pensar. Y cuanto más miraba y estudiaba los textos de este módulo con el analizador de paquetes, menos me gustaba, más tenía el deseo de reescribirlos con mi visión de este protocolo. Al final, se decidió llevar a cabo esta "hazaña laboral".

¿Qué no encajaba realmente, qué causaba "protesta civil" en estos módulos? En primer lugar, la organización de la interacción entre el módulo de conexión directa con el servidor y el analizador de paquetes. Resultó que el módulo de conexión interactuaba con el servidor, generaba un analizador al que le pasaba un enlace como parámetro. Como resultado, se delegó al analizador la autoridad para tomar decisiones sobre los próximos eventos de la red. A este respecto, surgió la pregunta, pero ¿es buena? ¿No sería mejor si el módulo analizador cumpliera su misión correspondiente, devolviera el resultado de su trabajo, pero la decisión de control sobre los eventos sería ejecutada por el objeto que generó el analizador? En este caso, se determinaría una estricta jerarquía de interacción entre los objetos. (Aquí, por supuesto, puede debatir qué es mejor: una jerarquía o una red, pero luego nos alejamos del tema).

La segunda cosa que me hizo querer volver a escribir todo fue la estructura del analizador. Este objeto (clase) debe cumplir dos funciones principales, a saber, formar un paquete de datos para su transmisión al servidor y el análisis de los paquetes recibidos del servidor. Entonces, fueron estas dos funciones las que no se adaptaron, en general. Y con esto

Imagine que se ha producido un evento de red, ha llegado un paquete. ¿Qué hizo HybiParser en este caso? Este objeto leyó los primeros dos bytes del flujo de entrada del socket por byte y determinó sus siguientes acciones: analizar el tamaño de los datos, la máscara, etc. Como resultado, esto se implementó en varias operaciones de lectura desde el flujo de entrada del socket. Además, el análisis fue complicado por las etapas de lectura, lo que complicó aún más el algoritmo. Y nuevamente surgió la pregunta, ¿es correcto, por qué tantas dificultades? ¿No es mejor considerar un paquete como una operación, especialmente porque se puede determinar el tamaño de los datos entrantes?

El tercero Parece que un aspecto más controvertido del trabajo del analizador es el ciclo de aceptación de paquetes "eterno". El ciclo se ejecuta en una secuencia de programa separada. En algún momento, el zócalo se cierra. ¿Qué sigue, el manejo habitual de excepciones? O que hacer No, no estoy en contra del mecanismo de excepciones, pero sería bueno prever esta circunstancia y la reacción por adelantado. Por ejemplo, fue posible proponer un mecanismo de sincronización como solución, durante el cual se completará regularmente el ciclo y, en consecuencia, se producirá la secuencia del programa.

Como resultado de la evaluación de todos estos matices, se determinaron los siguientes requisitos para el diseño de los módulos necesarios:

  • Los módulos deben ser independientes de las bibliotecas de terceros;
  • Los módulos deben ser simples y fáciles de integrar en otros proyectos;
  • Los módulos deben estar listos para una futura expansión funcional.

Bueno, y para no ser culpado por las críticas excesivas a la solución preparada anterior, agregamos a esto además que parte de las funciones preparadas y no críticas podrían transferirse de manera segura a una nueva implementación. Bueno, eso es todo, y como dijeron en el XXII Congreso del PCUS: "Nuestros objetivos son claros, las tareas están definidas. ¡Al trabajo, camaradas! ¡Por las nuevas victorias del comunismo!

En general, para no sobrecargarlo, querido lector, para no quitarle su valioso tiempo, describiré brevemente mi propuesta y me centraré solo en los puntos clave.
Entonces, la implementación propuesta contiene solo cuatro módulos (de acuerdo con los requisitos anteriores de la biblioteca Apache o las clases individuales de ellos no se incluyeron en el proyecto):

  • WebSocket protocol 07 módulo de constantes globales;
  • Clase de excepción auxiliar;
  • Cliente de socket web;
  • Módulo para analizar paquetes del protocolo webSocket nivel 07.

En los primeros dos módulos, la implementación es trivial, no hay nada que considerar. El módulo del cliente implementa el control sobre la conexión al servidor, y me gustaría detenerme en los siguientes puntos. La función de conexión abierta tiene un bucle de encabezados que provienen del servidor. Aquí, el análisis de claves Sec-WebSocket-Accept se implementa realmente, y en nuestro caso, el análisis se realiza sin utilizar las bibliotecas de Apache.

A continuación, preste atención a las funciones de control de bucle de paquetes. La implementación es trivial, a través de un objeto de sincronización.

El siguiente punto a respetar es la función del bucle. El ciclo no es "eterno", sino con la salida de acuerdo con la condición de verificar el objeto de sincronización. En un ciclo, un paquete que llega se lee en una operación. El paquete es analizado por el objeto correspondiente para el análisis. A continuación, se toma una decisión de gestión sobre el próximo evento de red.

Para completar la descripción, observamos la función de terminar de forma segura la conexión. Esto se hace de la siguiente manera. Se envía un paquete de terminación de conexión al servidor y se genera una variable de la clase Runnable, que luego se coloca en el procesador de cola para su ejecución a través de la función postDelayed, uno de cuyos parámetros es el retraso en la operación en milisegundos. En la variable Ejecutable, la función de ejecución contiene una secuencia de llamadas cuando finaliza la secuencia del programa y se cierra la conexión al servidor. En el caso de que el paquete de respuesta de cierre llegue antes, esta posición en la lista de procesamiento se elimina.

La clase que implementa el análisis de los paquetes de WebSocket contiene dos métodos que requieren atención: el análisis y la formación de un paquete para la transmisión de acuerdo con los parámetros relevantes. Al analizar, todos los indicadores y datos del paquete recibido se almacenan en variables de clase públicas. ¿Por qué público? Sí, por simplicidad, para no crear funciones adicionales get / set para ellos.

Bueno, en realidad, querido lector. Se adjunta el archivo con el proyecto para Android Studio. No haré ningún reclamo sobre el uso de estos textos en sus proyectos. Se acepta la crítica constructiva. Responda preguntas lo más lejos posible.

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


All Articles