Programación de sockets TCP

Programación de sockets

       En el protocolo TCP / IP, "dirección IP + número de puerto TCP o UDP" identifica de forma única un proceso en la comunicación de red, y "dirección IP + número de puerto" se denomina socket.

       En el protocolo TCP, los dos procesos que establecen una conexión tienen cada uno un socket para identificar, luego el par de socket formado por los dos sockets identifica de manera única una conexión. "Socket" en sí mismo significa "socket", que se utiliza para describir la relación uno a uno en las conexiones de red.

       La interfaz de programación de la capa de aplicación diseñada para el protocolo TCP / IP se llama API de socket.

       La interfaz de función del protocolo TCP y el protocolo UDP se presenta a continuación.

 

socket TCP

Extremo grande y pequeño

       Los datos multibyte en la memoria se dividen en big endian y little endian en relación con la dirección de memoria, y la dirección de desplazamiento de los datos multibyte en el archivo de disco en relación con el archivo también es big endian y little endian. El flujo de datos de la red también se divide en big-endian y small-endian. La dirección del flujo de datos de la red está estipulada: los datos enviados primero son la dirección baja y los datos enviados después son la dirección alta.

       El protocolo TCP / IP estipula que el flujo de datos de la red debe estar en orden de byte big-endian, es decir, byte alto de dirección baja. Por ejemplo, en el formato de segmento UDP de la sección anterior, la dirección 0 ~ 1 es el número de puerto de origen de dieciséis dígitos. Si el número de puerto es 1000 (0x3e8), entonces la dirección 0 es 0x03, la dirección 1 es 0xe8, la primera dirección es 0, la segunda dirección de envío 1. Pero si el host de envío está en orden de bytes little-endian, estos 16 bits se interpretarán como 0x3e8 en lugar de 1000. Por lo tanto, la conversión de endianidad se requiere en el medio.

       Para convertir entre el orden de bytes de la red y el orden de bytes del host, puede llamar a las siguientes funciones de biblioteca:

 

       h significa anfitrión

       n significa red

       l representa un número entero de 32 bits

       s representa un número entero corto de 16 bits. Por ejemplo, htonl significa convertir un entero de 32 bits del orden de bytes del host al orden de bytes de la red.

 

     El tipo de datos de la dirección de socket y funciones relacionadas La API de socket es una interfaz de programación de red abstracta adecuada para varios protocolos de red subyacentes. Sin embargo, los formatos de dirección de varios protocolos de red no son los mismos.

estructura de datos sockaddr:

        

 

       El formato de dirección de IPv4 e IPv6 se define en netinet / in.h, la dirección IPv4 está representada por la estructura sockaddr_in y la dirección IPv6 está representada por la estructura sockaddr_in6. El formato de dirección de UNIX Domain Socket se define en sys / un.h, representado por la estructura sockaddr_un. Los tipos de direcciones de IPv4, IPv6 y UNIXDomain Socket se definen como constantes AF_INET, AF_INET6, AF_UNIX, respectivamente. Su estructura no es la misma, pero el comienzo es el mismo. De esta manera, siempre que se obtenga la primera dirección de una determinada estructura sockaddr, el contenido de la estructura se puede determinar de acuerdo con el campo de tipo de dirección sin conocer el tipo específico de estructura sockaddr. Por lo tanto, la API de socket puede aceptar varios tipos de punteros de estructura sockaddr como parámetros, como vincular, aceptar, conectar y otras funciones. Los parámetros de estas funciones deben diseñarse como tipos void * para aceptar varios tipos de punteros, pero el La implementación de la API sock está estandarizada tempranamente en ANSI C, no había ningún tipo void * en ese momento, por lo que los parámetros de estas funciones están representados por el tipo struct sockaddr *, y la conversión de tipo debe forzarse antes de pasar los parámetros:

         

 

 

       En la programación de redes de socket basada en IPv4, la estructura de miembros in_addr sin_addr en sockaddr_in representa una dirección IP de 32 bits. Pero usualmente usamos una cadena decimal con puntos para representar la dirección IP, la siguiente función puede convertir entre la representación de cadena y la representación in_addr.

La función de convertir una cadena en decimal con puntos:

         

 

Proceso de comunicación del protocolo TCP:

 

       Servidor: después de llamar a socket (), bind (), listen () para completar la inicialización, llame a accept () para bloquear y esperar, y está en el estado de puerto de escucha.

       Cliente: Después de llamar a socket () para inicializar, llame a connect () para enviar el segmento SYN y bloquee esperando que el servidor responda. El servidor responde con un segmento SYN-ACK y el cliente regresa de connect () después de recibirlo y responde con un segmento ACK al mismo tiempo.

       Servidor: Después de recibir el ACK recibido por el cliente, regresa de accept ().

 

Programa de red TCP simple

tcp_server.c

         

        

        

 

 

 

       Las API de socket utilizadas en el programa son:

       <1>

        

 

       socket () abre un puerto de comunicación de red. Si tiene éxito, devuelve un descriptor de archivo como open (). La aplicación puede usar lectura / escritura para enviar y recibir datos en la red, como leer y escribir archivos. Si el socket () la llamada falla, Devuelve -1. Para IPv4, el parámetro de familia se especifica como AF_INET. Para el protocolo TCP, el parámetro de tipo se especifica como SOCK_STREAM, que representa un protocolo de transmisión orientado a la transmisión.

 

        <2>

        

       La función de bind () es unir los parámetros sockfd y myaddr juntos, de modo que el descriptor de archivo usado para la comunicación de red, sockfd, monitorea la dirección y el número de puerto descritos por myaddr. Como se mencionó anteriormente, struct sockaddr * es un tipo de puntero general. El parámetro myaddr realmente puede aceptar estructuras sockaddr de múltiples protocolos, y sus longitudes son diferentes, por lo que se requiere el tercer parámetro addrlen para especificar la longitud de la estructura. El parámetro myaddr en nuestro programa se inicializa así:

local.sin_family = AF_INET;

local.sin_port = htons (_port);

local.sin_addr.s_addr = inet_addr (_ip);

 

       <3>

        

       Cuando un cliente inicia una conexión, el accept () llamado por el servidor regresa y acepta la conexión. Si una gran cantidad de clientes inician una conexión y el servidor es demasiado tarde para procesar, el cliente que aún no ha aceptado está en una conexión. esperando el estado y listen () declara que sockfd está en el estado Monitor y permite que la mayoría de los clientes atrasados ​​estén en un estado de espera de conexión, e ignora si se reciben más solicitudes de conexión. listen () devuelve 0 en caso de éxito y -1 en caso de error.

 

        <4>

        

       Después de que se completa el protocolo de enlace de tres partes, el servidor llama a accept () para aceptar la conexión. Si no hay una solicitud de conexión del cliente cuando el servidor llama a accept (), se bloqueará y esperará hasta que haya una conexión de cliente. addr es un parámetro saliente, y la dirección del cliente y el número de puerto se enviarán cuando accept () regrese. El parámetro addrlen es un parámetro entrante y saliente (argumento valor-resultado), la longitud de la dirección del búfer proporcionada por la persona que llama se pasa para evitar problemas de desbordamiento del búfer y la longitud real de la estructura de la dirección del cliente (es posible que no llenar el búfer proporcionado por la persona que llama). Si pasa NULL al parámetro addr, significa que no le importa la dirección del cliente.

 

tcp_client.c

        

        

 

 

       Dado que el cliente no necesita un número de puerto fijo, no se utiliza bind () y el kernel asigna automáticamente el número de puerto del cliente. El servidor no tiene que llamar a bind (), pero si el servidor no llama a bind (), el kernel asignará automáticamente un puerto de escucha al servidor. El número de puerto es diferente cada vez que se inicia el servidor, y el cliente tiene problemas para conectarse al servidor.

 

        

 

       El cliente necesita llamar a connect () para conectarse al servidor. Los parámetros de connect y bind están en la misma forma. La diferencia es que el parámetro de bind es su propia dirección, y el parámetro de connect es la dirección del otro. partido. connect () devuelve 0 si tiene éxito y devuelve -1 si falla.

 

       Primero compile y ejecute el servidor, luego ejecute el cliente y envíe un mensaje al servidor:

        

 

       Mensaje de respuesta del servidor:

        

 

       Ahora use Ctrl C para terminar el servidor, luego ejecute el servidor inmediatamente, el resultado es:

        bind error: dirección ya en uso

        
       Esto se debe a que, aunque el programa de aplicación del servidor finaliza, la conexión de la capa del protocolo TCP no se desconecta por completo, por lo que el mismo puerto del servidor no se puede monitorear nuevamente. Ahora use Ctrl C para terminar el cliente. El descriptor de socket se cierra automáticamente cuando el cliente termina y la conexión TCP del servidor está en el estado TIME_WAIT después de recibir el segmento FIN enviado por el cliente.

       El protocolo TCP estipula que la parte que cierra activamente la conexión debe estar en el estado TIME_WAIT y esperar dos MSL antes de regresar al estado CERRADO. Debido a que primero presionamos Ctrl-C para terminar el servidor, el servidor es la parte que cierra activamente la conexión. Aún no se puede usar durante TIME_WAIT. Escuche nuevamente el mismo puerto del servidor.

       Puede usar netstat -nltp para encontrar su PID, y luego usar el comando kill para matarlo, puede volver a enlazar:

        

 

 

Supongo que te gusta

Origin blog.csdn.net/wxt_hillwill/article/details/73723750
Recomendado
Clasificación