Cómo protocolo TCP para resolver el paquete de palo, la mitad de un paquete de una reimpresión problema: https: //mp.weixin.qq.com/s/XqGCaX94hCvrYI_Tvfq_yQ

protocolo TCP es un protocolo de streaming

Dado que muchos lectores acceso al conocimiento de Internet, usted debe ha oído la frase: el protocolo TCP es un protocolo de flujo. Así que esta frase al final lo que significa? El llamado protocolo de streaming, que el acuerdo es como el agua en un flujo de bytes, no existe una clara demarcación entre el contenido y el signo de contenido, necesitamos que la gente va a estos acuerdos de demarcación.

Por ejemplo, con una comunicación TCP B, A ha enviado a 100 bytes y 200 bytes paquetes a B, entonces B es cómo recibirlo? B 100 puede recibir el primer byte, 200 byte recibir; también puede recibir los primeros 50 bytes, 250 bytes de recibir; o para recibir 100 bytes, 100 bytes de recibir, para recibir 200 bytes; 20 o byte, de 20 bytes primera recibido reciben, y luego recibe el 60 bytes, 100 bytes de recibir, reciben 50 bytes, 50 bytes reciben ......

Los lectores no conocen la ley no se ve? Una regla es un total de 300 bytes transmitidos a B, B a o más veces puede estar en cualquier forma de un número total de 300 bytes recibidos. Suponiendo 100 bytes y 200 bytes, respectivamente, de A a B se transmite un paquete de datos, para el envío de extremo A, este se puede distinguir, pero para B, si la longitud no es artificial y una pluralidad de paquetes de datos, cada B no debe saber cuántos bytes de los datos recibidos como un paquete válido. Y cada vez que los reglamentos cantidad de datos como una especificación de formato de protocolo es uno de los elementos del paquete.

A menudo hay un código de novato de escritura como la siguiente:

El remitente:

1//...省略创建socket,建立连接等部分不相关的逻辑...
2char buf[] = "the quick brown fox jumps over a lazy dog.";
3int n = send(socket, buf, strlen(buf), 0);
4//...省略出错处理逻辑...

El extremo receptor:

1//省略创建socket,建立连接等部分不相关的逻辑...
2char recvBuf[50] = { 0 };
3int n = recv(socket, recvBuf, 50, 0);
4//省略出错处理逻辑...
5printf("recvBuf: %s", recvBuf);

Con el fin de centrar el debate de la cuestión en sí, he omitido aquí y un poco de lógica para establecer un control de errores de conexión. El extremo de transmisión de código para el extremo receptor de la cadena de caracteres transmitido "el rápido zorro marrón salta sobre un perro perezoso.", Después de que el receptor recibe imprimirlo.

Este código es generalmente similar a la obra buena de esta máquina, imprima el extremo receptor como cadena esperada programada, sino en la red de área local o en un entorno de red pública en el problema, a saber, el extremo receptor no puede ser impresa completa la cadena de ; Si el emisor transmite continuamente una pluralidad de tiempos de la cadena, el lado de recepción de la cadena de caracteres impresa es incompleta o ilegible. razones incompletas entienden bien, es decir, el extremo de un particular datos recibidos más pequeñas que la longitud de la cadena completa, la matriz comienza a vaciarse en recvbuf 0, después de recibir la cadena de caracteres parcial, el final de la cadena todavía es 0, la función printf en cuanto a marcas cero caracteres del final de la final de la salida; ilegible razón es que si un dato particular, los rendimientos no sólo incluye una cadena completa, la cadena también contiene la siguiente parte, a continuación se llenará gama recvbuf, salida de la función printf cuando todavía será mirando a cero puntos de caracteres al final de la final de la salida, por lo que la memoria se lee en transfronterizo, y se ha encontrado hasta ahora, y la memoria después de la transfronteriza puede haber caracteres ilegibles aparecen después de que el hash.

Doy este ejemplo espero que entender que para tener una comprensión intuitiva del protocolo TCP es un protocolo de flujo. Debido a esto, hay que transmitir artificialmente y recibir los extremos en cada límite de flujo de bytes predeterminada, de modo que el extremo de recepción sabe lo que el número de bytes tomada a partir de una posición como para analizar el paquete de datos, que es un protocolo de comunicación de red diseñamos uno de los formatos de trabajo que hacer.

¿Cómo resolver dos problemas pegan paquete

El desarrollo real de programa de comunicación de red o entrevista técnica, el entrevistador por lo general más de un problema va a hacer es: cuando el tráfico de la red, la forma de resolver el paquete de palo?

Algunos entrevistadores may'm preguntar: cuando el tráfico de la red, la forma de resolver el paquete de palo, la pérdida de paquetes o problema reordenación de paquetes? De hecho, esta cuestión es la base del conocimiento en la entrevista estudio de la red del entrevistador, si el protocolo es TCP, en la mayoría de los escenarios, es que no hay pérdida de paquetes y paquetes de reordenamiento de problemas, comunicación TCP es una comunicación fiable, pila de protocolos TCP a través de una secuencia de mecanismos de números y retransmisión de paquetes para asegurar un paquete de confirmación ordenada y debe ser enviado correctamente a su destino; si el protocolo UDP, si una pequeña cantidad de pérdida de paquetes no puede ser aceptada, tendría que alcanzar su propia sobre la base de la UDP es similar a TCP para este fin y un mecanismo de transporte fiable (por ejemplo, el protocolo RTP, protocolo RUDP). Así, tras el desmantelamiento del problema, la forma de resolver el problema paquete sólo se adhieren.

En primer lugar explicar lo que es el paquete de palo , el llamado paquete de palo se envían continuamente a dos o más paquetes de datos a los pares, un par en carga puede recibir paquetes de más de 1, mayor que 1, puede ser varios (incluyendo a) del paquete más parte de un paquete, o simplemente unas pocas juntas paquete completo. Por supuesto, los datos sólo pueden recibir una parte del paquete, esto es generalmente también llamado medio paquete .

Ya sea la mitad de un paquete o paquetes palo de problemas, su causa raíz es el protocolo TCP se describe en el formato anterior es Flujo de datos. Las ideas o pensar en maneras de resolver el problema del paquete de datos recibido y el paquete de la frontera de distinguir. Entonces, ¿cómo distinguirlo? Hay tres métodos principales:

paquetes de longitud paquete fijo

Como su nombre indica, es decir, la longitud de cada paquete de protocolo es fija. Por ejemplo, podemos, por ejemplo, un tamaño predeterminado de cada paquete de protocolo es de 64 bytes, 64 bytes cada uno recibió completo, tomada a cabo para resolver (si no, sería primer depósito).

formato de protocolo de comunicación simple, pero esta flexibilidad es pobre. Si el número de bytes de los contenidos del paquete menor que el especificado, el espacio que queda lleno de necesidades especiales de información, tales como \ 0 (si no está lleno de contenido especial, cómo distinguir contenido normal en el interior del envase se llena con la información que?); Si el contenido del paquete supera los bytes especificados número, la fragmentación de paquetes obtuvo de nuevo, la necesidad de lógica de procesamiento adicional - fragmento subcontrato en el lado de transmisión, el extremo de recepción vuelve a montar paquetes ranuras (sub-contenido y el fragmento se detallará en la siguiente) en.

Para especificar el carácter (cadena) para marcar el final del paquete

Este paquete de protocolo más común, es decir, el flujo de bytes se considera para terminar cuando el paquete se encuentra con un valor especial símbolo. Por ejemplo, estamos familiarizados con el protocolo FTP, un protocolo de correo SMTP, un comando o un fragmento de datos, seguido de "\ r \ n" (el llamado  CRLF ) representa un paquete completado. Después del final de los datos recibidos antes de cada encuentro un "\ r \ n" como un paquete de venta.

Este protocolo se utiliza típicamente para algunas aplicaciones incluyen varios comandos controlados, sus deficiencias es que si el contenido del paquete de datos de protocolo requiere el carácter porción indicador de final de paquete, es necesario hacer la transcodificación de estos caracteres o operación de escape, para evitar ser recibido colmillo confundirse final de bandera de paquetes y la resolución de error.

Paquete de cabecera + formato del cuerpo

Este formato de paquete se divide generalmente en dos partes, a saber, una cabecera y un cuerpo, un encabezado de tamaño fijo, y debe contener un campo de cabecera del paquete para ilustrar cómo el siguiente cuerpo del paquete.

Por ejemplo:

1struct msg_header
2{
3  int32_t bodySize;
4  int32_t cmd;
5};

Se trata de un formato de cabecera típica, BODYSIZE especifica la inclusión de este paquete es la cantidad. Dado que el tamaño de la cabecera se fija (esto es el tamaño (int32_t) + sizeof (int32_t) = 8 bytes), el número de bytes del final del tamaño de la cabecera primera carga (por supuesto, si no se almacena en caché o la primera, hasta lo suficientemente cerca), entonces paquete de cabecera de análisis, según el tamaño recoger cuerpos de inclusión cabecera de paquete especificado, y otras inclusiones lo suficientemente cerca, que se ensambla en un paquete para completar el procesamiento. En algunas implementaciones, la cabecera de BODYSIZE podrá ser sustituido por otro llamado packageSize el campo, el significado de este campo es el tamaño de todo el paquete, esta vez, sólo tiene que utilizar packageSize menos el tamaño de la cabecera (en este caso sizeof (msg_header)) podrán calcular el tamaño del cuerpo del paquete, el principio anterior.

Con la mayor parte de la biblioteca de red, por lo general necesita ser dueño de los límites de los paquetes de datos y el análisis, la biblioteca general de la red no proporciona esta función de acuerdo con el formato del protocolo que está fuera de la necesidad de apoyar protocolos diferentes, debido a la incertidumbre del acuerdo, y por lo tanto no puede DETALLADA desembalar el código proporcionado por adelantado. Por supuesto, esto no es absoluta, hay algunas bibliotecas de red proporcionan esta funcionalidad. En Java marco red Netty proporcionado clase FixedLengthFrameDecoder a longitud de la manija se se proporciona paquete de protocolo de longitud fija clase DelimiterBasedFrameDecoder a paquetes de protocolo de mango de acuerdo con el carácter especial como un terminador, siempre ByteToMessageDecoder a paquetes de protocolo de formato personalizado proceso (usado para procesar el formato de encabezado y cuerpo de paquete del paquete), pero en la serie ByteToMessageDecoder subclase que necesita para decodificar anulación basada en el método () para descomprimir el paquete de datos a su formato de protocolo específico.

Estos tres formato de paquetes, los lectores pueden esperar comprensión en profundidad y comprender los principios básicos de sus ventajas y desventajas.

Tres procesamiento de desembalaje

Después de comprender los tres formatos de paquetes de datos descritos anteriormente, tenemos que explicar cómo se deben manejar estas tres tecnologías para formatos de paquetes. ¿Qué flujo del proceso es el mismo, aquí Encabezado + de inclusión  se describirán los paquetes de este formato. El proceso es como sigue:

Suponemos que el formato de cabecera es el siguiente:

1//强制一字节对齐
2#pragma pack(push, 1)
3//协议头
4struct msg
5{   
6    int32_t  bodysize;         //包体大小  
7};
8#pragma pack(pop)

A continuación, los códigos de procedimiento anteriores son como sigue:

 1//包最大字节数限制为10M
2#define MAX_PACKAGE_SIZE    10 * 1024 * 1024
3
4void ChatSession::OnRead(const std::shared_ptr<TcpConnection>& conn, Buffer* pBuffer, Timestamp receivTime)
5{
6    while (true)
7    {
8        //不够一个包头大小
9        if (pBuffer->readableBytes() < (size_t)sizeof(msg))
10        {
11            //LOGI << "buffer is not enough for a package header, pBuffer->readableBytes()=" << pBuffer->readableBytes() << ", sizeof(msg)=" << sizeof(msg);
12            return;
13        }
14
15        //取包头信息
16        msg header;
17        memcpy(&header, pBuffer->peek(), sizeof(msg));
18
19        //包头有错误,立即关闭连接
20        if (header.bodysize <= 0 || header.bodysize > MAX_PACKAGE_SIZE)
21        {
22            //客户端发非法数据包,服务器主动关闭之
23            LOGE("Illegal package, bodysize: %lld, close TcpConnection, client: %s", header.bodysize, conn->peerAddress().toIpPort().c_str());
24            conn->forceClose();
25            return;
26        }
27
28        //收到的数据不够一个完整的包
29        if (pBuffer->readableBytes() < (size_t)header.bodysize + sizeof(msg))
30            return;
31
32        pBuffer->retrieve(sizeof(msg));
33        //inbuf用来存放当前要处理的包
34        std::string inbuf;
35        inbuf.append(pBuffer->peek(), header.bodysize);
36        pBuffer->retrieve(header.bodysize);          
37        //解包和业务处理
38        if (!Process(conn, inbuf.c_str(), inbuf.length()))
39        {
40            //客户端发非法数据包,服务器主动关闭之
41            LOGE("Process package error, close TcpConnection, client: %s", conn->peerAddress().toIpPort().c_str());
42            conn->forceClose();
43            return;
44        }              
45    }// end while-loop
46}

Y el diagrama de flujo de proceso antes descrito que muestra el flujo del código es el mismo, un buffer de recepción, donde pBuffer código personalizado aquí, los datos se han recibido en esta memoria intermedia, por lo que los bytes que se han recibido se determina el método requiere el uso de sólo el número correspondiente al objeto. El código tengo que hacer hincapié algunos detalles:

  • Al tomar Baotou, debe copiar el tamaño de datos de una cabecera de paquete a cabo, en lugar de tomar los datos directamente desde la memoria intermedia pBuffer a cabo (es decir, sacado de los datos se elimina de la pBuffer), ya que si la cabecera de la siguiente paquete cuando el campo para obtener el tamaño de cuerpo del paquete, si los datos restantes no es un tamaño de cuerpo del paquete, pero usted tiene que poner esto de nuevo encabezado en el búfer de datos. A fin de evitar esta operación innecesaria, sólo el tamaño del búfer suficiente de todo el tamaño de paquete de datos (código: header.bodysize + sizeof (msg)) sólo tiene que quitar todo el tamaño del paquete de datos de la memoria intermedia, que es en el presente documento la palabra método peek pBuffer-> significa peek () (chino puede ser traducido como "vista" o "espiando").

  • Cuando la cabecera del paquete obtenido por el tamaño del cuerpo, se debe verificar la BODYSIZE numérica que BODYSIZE requerido aquí debe ser mayor que 0 y no mayor que 1024 * 1024 * 10 (es decir, 10 M). Por supuesto, el desarrollo real, es posible que desee para decidir sobre el límite de BODYSIZE (tamaño de la inclusión es 0 paquetes de bytes en algunos escenarios de negocios están permitidos) de acuerdo a sus propias necesidades. Recuerde, esto debe determinarse límites superior e inferior, porque se supone que un conjunto de datos ilegales enviados desde el cliente, que se proporciona BODYSIZE un valor relativamente grande, por ejemplo, 1 * 1024 * 1024 * 1024 (es decir, G 1), se la lógica le permitirá mantener una caché de los datos enviados por el cliente, entonces su memoria del servidor se agotará pronto, el sistema operativo cuando detecta la memoria del proceso alcanza un cierto umbral matará a su proceso, lo que resulta en el servicio ya no puede normal de los servicios externos. Si detecta un campo BODYSIZE se adapte a sus límites superior e inferior establecidos para el BODYSIZE ilegal, directamente desde esta conexión vial. También es un servicio de auto-protección, evitar la pérdida causada por paquetes ilegales.

  • No sé si usted ha notado la totalidad de Baotou juicio, la inclusión y la lógica de procesamiento de paquetes en un bucle while en el interior, es necesario. Sin este bucle while, cuando se recibe más de un paquete de una sola vez, usted sólo se ocupan de entonces tendría un próximo proceso que esperar hasta que un nuevo lote de datos viene esta lógica de disparo de nuevo. Los resultados de dicha causa es que los pares que envía múltiples peticiones, sólo puede responder a uno de los datos de los compañeros es enviado de nuevo a la parte posterior de su respuesta que esperar hasta. Esto es para paquete de palo de lógica de procesamiento correcto.

Y el código anterior es el básico más pegajosa y semi-paquete de paquetes mecanismo de procesamiento , las denominadas soluciones técnicas de la lógica de procesamiento de paquetes (procesamiento de la lógica descomprime el negocio posteriores capítulos reintroducidos). Espero que los lectores puedan entender, comprender su base, podemos ampliar para descomprimir una gran cantidad de funciones, por ejemplo, damos a nuestro paquete de acuerdo añade soporte para una función de compresión, nos convertimos en la siguiente cabecera de esta manera:

 1#pragma pack(push, 1)
2//协议头
3struct msg
4{
5    char     compressflag;     //压缩标志,如果为1,则启用压缩,反之不启用压缩
6    int32_t  originsize;       //包体压缩前大小
7    int32_t  compresssize;     //包体压缩后大小
8    char     reserved[16];       //保留字段,用于将来拓展
9};
10#pragma pack(pop)

El código modificado como sigue:

 1void ChatSession::OnRead(const std::shared_ptr<TcpConnection>& conn, Buffer* pBuffer, Timestamp receivTime)
2{
3    while (true)
4    {
5        //不够一个包头大小
6        if (pBuffer->readableBytes() < (size_t)sizeof(msg))
7        {
8            //LOGI << "buffer is not enough for a package header, pBuffer->readableBytes()=" << pBuffer->readableBytes() << ", sizeof(msg)=" << sizeof(msg);
9            return;
10        }
11
12        //取包头信息
13        msg header;
14        memcpy(&header, pBuffer->peek(), sizeof(msg));
15
16        //数据包压缩过
17        if (header.compressflag == PACKAGE_COMPRESSED)
18        {
19            //包头有错误,立即关闭连接
20            if (header.compresssize <= 0 || header.compresssize > MAX_PACKAGE_SIZE ||
21                header.originsize <= 0 || header.originsize > MAX_PACKAGE_SIZE)
22            {
23                //客户端发非法数据包,服务器主动关闭之
24                LOGE("Illegal package, compresssize: %lld, originsize: %lld, close TcpConnection, client: %s",  header.compresssize, header.originsize, conn->peerAddress().toIpPort().c_str());
25                conn->forceClose();
26                return;
27            }
28
29            //收到的数据不够一个完整的包
30            if (pBuffer->readableBytes() < (size_t)header.compresssize + sizeof(msg))
31                return;
32
33            pBuffer->retrieve(sizeof(msg));
34            std::string inbuf;
35            inbuf.append(pBuffer->peek(), header.compresssize);
36            pBuffer->retrieve(header.compresssize);
37            std::string destbuf;
38            if (!ZlibUtil::UncompressBuf(inbuf, destbuf, header.originsize))
39            {
40                LOGE("uncompress error, client: %s", conn->peerAddress().toIpPort().c_str());
41                conn->forceClose();
42                return;
43            }
44
45            //业务逻辑处理
46            if (!Process(conn, destbuf.c_str(), destbuf.length()))
47            {
48                //客户端发非法数据包,服务器主动关闭之
49                LOGE("Process error, close TcpConnection, client: %s", conn->peerAddress().toIpPort().c_str());
50                conn->forceClose();
51                return;
52            }
53        }
54        //数据包未压缩
55        else
56        {
57            //包头有错误,立即关闭连接
58            if (header.originsize <= 0 || header.originsize > MAX_PACKAGE_SIZE)
59            {
60                //客户端发非法数据包,服务器主动关闭之
61                LOGE("Illegal package, compresssize: %lld, originsize: %lld, close TcpConnection, client: %s", header.compresssize, header.originsize, conn->peerAddress().toIpPort().c_str());
62                conn->forceClose();
63                return;
64            }
65
66            //收到的数据不够一个完整的包
67            if (pBuffer->readableBytes() < (size_t)header.originsize + sizeof(msg))
68                return;
69
70            pBuffer->retrieve(sizeof(msg));
71            std::string inbuf;
72            inbuf.append(pBuffer->peek(), header.originsize);
73            pBuffer->retrieve(header.originsize);
74            //业务逻辑处理
75            if (!Process(conn, inbuf.c_str(), inbuf.length()))
76            {
77                //客户端发非法数据包,服务器主动关闭之
78                LOGE("Process error, close TcpConnection, client: %s", conn->peerAddress().toIpPort().c_str());
79                conn->forceClose();
80                return;
81            }
82        }// end else
83
84    }// end while-loop
85}

La primera bandera de compresión campo de cabecera código se determina si el cuerpo de la bolsa comprimido, si se comprime, a continuación, descomprime para eliminar el tamaño de cuerpo del paquete, los datos después de la descompresión es el tráfico de datos real. A lo largo del diagrama de flujo del programa es el siguiente:

Hay un código de búfer de recepción variable de pBuffer, el búfer de entrada en la forma de diseñar, que detallaremos en un artículo posterior.

Supongo que te gusta

Origin www.cnblogs.com/testzcy/p/12536650.html
Recomendado
Clasificación