Programación de sockets basada en TCP/UDP

---- descripción general del enchufe:

Socket es una capa de abstracción entre la capa de aplicación y la capa de transporte. Abstrae las operaciones complejas de la capa TCP/IP en varias interfaces simples para que la capa de aplicación llame al proceso realizado para comunicarse en la red.

El socket se originó en UNIX. Según la filosofía de Unix de que todo es un archivo, el socket es una implementación del modo "abrir-leer/escribir-cerrar". El servidor y el cliente mantienen cada uno un "archivo". Después de establecer y abrir la conexión , puede escribir contenido en su propio archivo para que la otra parte lea o lea el contenido de la otra parte y cerrar el archivo cuando finalice la comunicación.

---- Introducción a la interfaz:

socket(): crea un socket

bind (): vincula el socket a la dirección y el puerto locales, generalmente llamados por el servidor

listen (): dedicado a TCP, habilitar el modo de escucha

accept (): dedicado a TCP, el servidor espera a que el cliente se conecte, generalmente en un estado de bloqueo

connect (): dedicado a TCP, el cliente se conecta activamente al servidor

enviar (): TCP dedicado, enviar datos

recv(): TCP dedicado, recibir datos

sendto (): UDP dedicado, envía datos a la dirección IP y el puerto especificados

recvfrom (): UDP dedicado, recibir datos, devolver datos, dirección IP remota y puerto

closesocket(): cierra el socket

---- El proceso es el siguiente:

La interfaz se explica en detalle y las llamadas al sistema comúnmente utilizadas son las siguientes:

>> socket() : creando un socket 

Un socket es una abstracción de un punto final de comunicación. Así como usarían descriptores de archivo para acceder a los archivos, las aplicaciones usan descriptores de socket para acceder a los sockets. Para crear un socket, llamamos a la función socket().

原型:int socket(dominio int, tipo int, protocolo int);

Valor de retorno: devuelve el descriptor de archivo (socket) si está bien, -1 en caso de error.

dominio:AF_INET, AF_INET6, AF_UNIX, AF_UNSPEC (formato de dirección)

tipo:SOCK_DGRAM, SOCK_RAW, SOCK_STREAM, SOCK_SEQPACKET

protocolo:IPPROTO_IP, IPPROTO_IPV6, IPPROTO_TCP, IPPROTO_UDP

El argumento del protocolo suele ser cero, para seleccionar el protocolo predeterminado para el dominio y el tipo de socket dados. El protocolo predeterminado para un socket SOCK_STREAM en el dominio de comunicación AF_INET es TCP (Protocolo de control de transmisión). El protocolo predeterminado para un socket SOCK_DGRAM en el dominio de comunicación AF_INET es UDP (Protocolo de datagramas de usuario).

NOTA: UDP: datagrama (datagrama), sin conexión, no existe una conexión lógica entre pares para que se comuniquen. Un datagrama es un mensaje autónomo (independiente). Similar al envío de correo electrónico (análogo), puede enviar varios correos electrónicos, pero no hay garantía de que lleguen los correos electrónicos y el orden en que llegarán los correos electrónicos. Cada correo electrónico contiene la dirección del destinatario.

TCP: flujo de bytes Un flujo de bytes (SOCK_STREAM), por el contrario, antes de transmitir datos, se debe establecer una conexión y la comunicación orientada a la conexión es similar a hacer una llamada telefónica. Una conexión punto a punto incluye origen y destino.

La comunicación en un socket es bidireccional. Podemos deshabilitar E/S en un socket con la función de apagado.

>> apagar()   

Prototipo: int shutdown(int sockfd, int how);

Valor devuelto: devuelve 0 si está bien, -1 en caso de error.

La llamada al sistema shutdown() cierra uno o ambos canales del socket sockfd, según el valor de how, que se especifica como uno de los siguientes:

cómo: SHUT_RD, entonces la lectura desde el socket está deshabilitada. SHUT_WR, entonces no podemos usar el socket para transmitir datos. Podemos usar SHUT_RDWR para deshabilitar tanto la transmisión como la recepción de datos.

shutdown() difiere de close() en otro aspecto importante: cierra los canales del socket independientemente de si hay otros descriptores de archivo que se refieran al socket. Por ejemplo, sockfd se refiere a un socket de flujo conectado. Si hacemos las siguientes llamadas, entonces la conexión permanece abierta y aún podemos realizar E/S en la conexión a través del descriptor de archivo fd2:

1. fd = dup(calcetínfd);

2. cerrar(calcetines);

Sin embargo, si hacemos la siguiente secuencia de llamadas, ambos canales de la conexión se cierran y ya no se puede realizar la E/S a través de fd2:

1. fd2 = dup(calcetínfd);

2. apagado (sockfd, SHUT_RDWR);

Tenga en cuenta que shutdown() no cierra el descriptor de archivo, incluso si se especifica como SHUT_RDWR. Para cerrar el descriptor de archivo, debemos llamar adicionalmente a close().

>> bind() : enlazar un socket a una dirección    

La llamada al sistema bind() vincula un socket a una dirección.

原型:int bind(int sockfd, const struct sockaddr * addr, socklen_t addrlen);

Valor devuelto: devuelve 0 en caso de éxito o -1 en caso de error.

El argumento sockfd es un descriptor de archivo obtenido de una llamada anterior a socket(). El argumento addr es un puntero a una estructura que especifica la dirección a la que se vinculará este socket. El tipo de estructura que se pasa en este argumento depende del dominio del socket. El argumento addrlen especifica el tamaño de la estructura de la dirección.

Por lo general, vinculamos el socket de un servidor a una dirección conocida, es decir, una dirección fija conocida de antemano por las aplicaciones cliente que necesitan comunicarse con ese servidor.

>> listen() : escuchando las conexiones entrantes    

原型:int listen(int sockfd, int backlog); // devuelve 0 en caso de éxito o -1 en caso de error.

La llamada al sistema listen() marca el socket de flujo al que hace referencia el descriptor de archivo sockfd como pasivo. Posteriormente, el socket se utilizará para aceptar conexiones de otros sockets (activos).

El cliente puede llamar a connect() antes de que el servidor llame a accept(). Esto podría suceder, por ejemplo, porque el servidor está ocupado manejando otros clientes. Esto da como resultado una conexión pendiente, como se ilustra en la Figura 56-2.

El kernel debe registrar cierta información sobre cada solicitud de conexión pendiente para que se pueda procesar una aceptación() posterior. El argumento de backlog nos permite limitar el número de tales conexiones pendientes. Las solicitudes de conexión adicionales se bloquean hasta que se acepta una conexión pendiente (a través de accept()) y, por lo tanto, se elimina de la cola de conexiones pendientes.

>> accept() : aceptando una conexión   

La llamada al sistema accept() acepta una conexión entrante en el socket de flujo de escucha al que hace referencia el descriptor de archivo sockfd. Si no hay conexiones pendientes cuando se llama a accept(), la llamada se bloquea hasta que llega una solicitud de conexión cuando sockfd está en modo de bloqueo. Si sockfd está en modo sin bloqueo, accept() devolverá -1 y establecerá errno en EAGAIN o EWOULDBLOCK.

原型:int accept(int sockfd, struct sockaddr * restrict addr, socklen_t * restrict len);

Valor devuelto: devolver descriptor de archivo (socket) si está bien, -1 en caso de error.

Esta función extrae la primera conexión de la cola de conexión en espera de s, crea un nuevo socket del mismo tipo que s y devuelve el identificador. Si no hay una conexión en espera en la cola y el socket está en modo de bloqueo, accept() bloquea el proceso de llamada hasta que aparece una nueva conexión. Si el socket no bloquea y no hay conexiones en espera en la cola, accept() devuelve un código de error WSAEWOULDBLOCK. Los sockets que han aceptado conexiones no se pueden usar para aceptar nuevas conexiones y el socket de escucha original permanece abierto.

El punto clave que hay que entender sobre accept() es que crea un nuevo socket, y este nuevo socket está conectado al socket par que realizó la conexión(). Este nuevo descriptor de socket tiene el mismo tipo de socket y familia de direcciones que el socket original (sockfd). Se devuelve un descriptor de archivo para el socket conectado como resultado de la función de la llamada accept(). El conector de escucha (sockfd) permanece abierto y se puede usar para aceptar más conexiones. Una aplicación de servidor típica crea un socket de escucha, lo vincula a una dirección conocida y luego maneja todas las solicitudes de los clientes aceptando conexiones a través de ese socket.

Los argumentos restantes para accept() devuelven la dirección del socket par (cliente)

Si no nos importa la identidad del cliente, podemos establecer los parámetros addr y len en NULL. De lo contrario, antes de llamar a accept, debemos establecer el parámetro addr (指向一个buffer) en un búfer lo suficientemente grande como para contener la dirección y establecer el número entero señalado por len en el tamaño del búfer en bytes. Al regresar, accept completará la dirección del cliente en el búfer y actualizará el número entero señalado por len para reflejar el tamaño de la dirección.

>> connect() : conexión a un socket par  

原型:int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);

Valor devuelto: devuelve 0 en caso de éxito o -1 en caso de error.

La llamada al sistema connect() conecta el socket activo al que hace referencia el descriptor de archivo sockfd con el socket de escucha cuya dirección se especifica mediante addr y addrlen.

>> enviar() : enviar datos de tipo TCP 

原型:int send(int sockfd, const void * msg, int len, int flags);

Cada socket TCP tiene un búfer de envío cuyo tamaño se puede cambiar con la opción SO_SNDBUF. El proceso de llamar a la función de envío es en realidad el proceso en el que el kernel copia los datos del usuario (msg) en el búfer de envío del socket TCP. Si len es mayor que el tamaño del búfer de envío, devuelva -1. De lo contrario, verifique si el espacio restante en el búfer puede acomodar la longitud de len que se enviará. Si no, copie una parte y devuelva la longitud de la copia (refiriéndose a envío sin bloqueo, si es un envío con bloqueo, debe esperar a que todos los datos se copien en el búfer antes de regresar, por lo que el valor de retorno del envío con bloqueo debe ser igual a len). Si el búfer está lleno, espera el envío y copia en el búfer una vez que queda espacio. Si se produce un error durante el proceso de copia, devuelve -1. Para conocer la causa del error, verifique el valor de errno.

Nota: La devolución exitosa del envío no significa que la otra parte haya recibido los datos. Si se produce un error de red durante la transmisión del protocolo posterior, el próximo envío devolverá un error de envío -1. Los datos enviados por TCP a la otra parte deben ser confirmados por la otra parte antes de que se puedan eliminar los datos en el búfer de envío. De lo contrario, se almacenará en caché en el búfer hasta que el envío sea exitoso.

Explicación de parámetros:

sockfd -- descriptor de socket del remitente (descriptor que no escucha)

msg: el búfer de los datos que se enviarán (copie la longitud de longitud de su contenido en el búfer de envío del socket)

len: la longitud en bytes de los datos que se enviarán.

banderas - generalmente se establece en 0.

>> recv() : recepción de datos de tipo TCP 

原型:int recv(int sockfd, void *buf, int len, banderas int sin firmar);

recv() copia datos del búfer de recepción. En caso de éxito, devuelve el número de bytes copiados, en caso de error, devuelve -1. En el modo de bloqueo, recv() se bloqueará hasta que haya al menos un byte en el búfer antes de regresar, y se suspenderá cuando no haya datos. Si no está bloqueado, volverá inmediatamente, si hay datos, devolverá el tamaño de los datos copiados, de lo contrario, devolverá un error -1.

Explicación de parámetros:

sockfd: descriptor de socket del receptor (descriptor que no escucha)

buf: la dirección base de los datos recibidos (copia el contenido del búfer de recepción del socket a buf)

len -- el número de bytes de datos recibidos

banderas - generalmente se establece en 0.

>> sendto() : enviar datos de tipo UDP 

原型:int sendto(int sockfd, const void * msg, int len, int flags sin firmar, const struct sockaddr * dst_addr, int addrlen);

Se utiliza para el envío de datos de conexión no confiable (UDP), porque el método UDP no establece un socket de conexión, por lo que se debe especificar la dirección del socket de destino.

Puede usar el mismo descriptor de socket UDP sockfd para comunicarse con diferentes direcciones de destino. Sin embargo, TCP necesita establecer una conexión por adelantado, y cada conexión generará un descriptor de socket diferente, lo que se refleja en: el cliente necesita usar un fd diferente para conectarse, y el servidor genera un fd diferente cada vez que acepta.

UDP no tiene un búfer de envío real, porque es una conexión poco confiable y no es necesario guardar la copia de datos del proceso de la aplicación.Cuando los datos del proceso de la aplicación pasan por la pila de protocolos, se copian en el kernel buffer de alguna forma Cuando la capa de enlace de datos Después de que se transmiten los datos, se elimina la copia de los datos en el kernel buffer. Por lo tanto, no necesita un búfer de envío.

Para sendto(), los argumentos dest_addr y addrlen especifican el socket al que se enviará el datagrama. Estos argumentos se emplean de la misma manera que los argumentos correspondientes de connect(). El argumento dest_addr es una estructura de dirección adecuada para este dominio de comunicación. Se inicializa con la dirección del socket de destino. El argumento addrlen especifica el tamaño de addr.

>> recvfrom() : recepción de datos tipo UDP 

原型:int recvfrom(int sockfd, void * buf, size_t len, int flags, struct sockaddr * src_addr, int * addrlen);

Explicación de parámetros:

sockfd -- la descripción del socket del extremo receptor;

buf: la dirección del búfer de la aplicación para recibir datos;

len: indica el tamaño del búfer;

banderas -- generalmente 0;

src_addr: la dirección de la fuente de datos (dirección IP, número de puerto).

fromlen: cuando se usa como entrada, fromlen generalmente se establece en sizeof (struct sockaddr).

Para recvfrom(), los argumentos src_addr y addrlen devuelven la dirección del socket remoto utilizado para enviar el datagrama. (Estos argumentos son análogos a los argumentos addr y addrlen de accept(), que devuelven la dirección de un socket par que se conecta). Antes de la llamada (在调用之前), addrlen debe inicializarse al tamaño de la estructura a la que apunta src_addr(结构的大小); al regresar (在返回时), contiene la cantidad de bytes realmente escritos en esta estructura

El Internet integrado de las cosas necesita aprender mucho. ¡No aprenda la ruta y el contenido equivocados, lo que hará que su salario aumente!

Comparte un paquete de datos con todos, unos 150 G. ¡El contenido de aprendizaje, las escrituras cara a cara y los proyectos son relativamente nuevos y completos! (Haga clic para encontrar un pequeño asistente para recibir)

Supongo que te gusta

Origin blog.csdn.net/m0_70911440/article/details/131548104
Recomendado
Clasificación