Programación del sistema Linux C (14) base de programación de red

1 concepto de enchufe

Linux usa sockets para comunicarse entre procesos; a través de sockets, la ubicación de otros procesos es transparente para la aplicación; los sockets representan puntos finales de comunicación, y se debe asegurar que cada uno de los dos puntos finales tenga un socket . El proceso de comunicación del socket es el siguiente:
 

Los sockets implementan una capa de abstracción, haciendo que los usuarios sientan que están trabajando en archivos. El proceso abstracto es el siguiente:


2 preparación

2.1 Endianness

En el entorno de red, la comunicación entre procesos es entre hosts, por lo que existe el problema de que el orden de los bytes no es uniforme. Para resolver este problema, el protocolo de red proporciona un orden de bytes. Cuando dos procesos a través del host se comunican, los datos a transmitir se convierten primero en un orden de bytes de red. Después de que el receptor recibe los datos, se convierte en este Endianness de la máquina. El proceso de conversión de secuencia de bytes es el siguiente:

En el entorno de Linux, se utilizan cuatro funciones para convertir el orden de bytes. El prototipo de la función es el siguiente:

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32);
uint16_t htons(uint16_t hostint16);
uint32_t ntohl(uint32_t netint32);
uint16_t ntohs(uint16_t netint16);

Consulte el manual de referencia de la función de Linux para más detalles . El orden de bytes de la red es un orden de bytes big-endian, pero la situación de la red es complicada. Para garantizar la portabilidad del código, sin importar qué tipo de orden de bytes, el host debe hacer el procesamiento de conversión de bytes.

2.2 Formato de dirección

Cada computadora en el entorno de red tiene una dirección IP. (Para el protocolo IPv4, es un entero sin signo de 32 bits; para el protocolo IPv6, es un entero sin signo de 128 bits). La estructura in_addr se usa en Linux para indicar una dirección IP, y la estructura se define de la siguiente manera:

#include <netinet/in.h>
struct in_addr{
     in_addr_t s_addr;     /*in_addr_t 被定义为无符号整型*/
}

Cuando se determina la máquina de destino, también es necesario determinar qué proceso en el host necesita comunicarse a través del número de puerto (cada proceso corresponde a un número de puerto de 16 bits). Por lo tanto, en la red, se pueden conectar una dirección IP y un número de puerto para determinar el proceso de un host. Cuando se confirman los dos únicos puntos, comienza la comunicación. La definición de la estructura de direcciones en Linux es la siguiente:

#include <netinet/in.h>

struct socketaddr_in{
     sa_family_t sin_family;     /*16位的地址族,根据套接字场合不同而不同,网络通信IPv4地址族为AF_NET*/
     in_port_t sin_port;          /*16位的端口号*/
     struct in_addr sin_addr;     /*32位的IP地址*/
     unsigned char sin_zero[8];/*填充区,8个字节填0,为保证socketaddr_in与socket_addr地址结构可以随意转换*/
}

struct socketaddr{
     sa_family_t sin_family;     /*16位的地址族,根据套接字场合不同而不同,网络通信IPv4地址族为AF_NET*/
     char sa_data[14];          /*14字节的填充区,可以看成sin_port、sin_addr、sin_zero三个成员变量组成*/
}

La estructura socketaddr_in tiene la misma longitud que socketaddr, por lo que se puede convertir fácilmente entre sí.

2.3 Conversión de formato de dirección

La dirección IP se almacena en la estructura de direcciones en forma binaria, lo cual es incómodo de observar directamente. Es intuitivo usar decimal con puntos (xxx.xxx.xxx.xxx). Las funciones de conversión de direcciones IP proporcionadas en Linux son las siguientes:

#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);/*将二进制数转换成点分十进制*/
int inet_pton(int af, const char *src, void *dst);/*将点分十进制转换成二进制*/

Consulte el manual de referencia de la función de Linux para más detalles .  

2.4 Obtención de información del host

Un host y la información relacionada con la red generalmente se almacenan en un archivo en el sistema (como / etc / hosts), el usuario puede leer el contenido del archivo a través de la función del sistema, usar la función gethostent en Linux para leer el host relacionado Información:

#include <netdb.h>
struct hostent *gethostent(void);     /*读取含有主机相关信息的文件*/
void endhostent(void);               /*关闭含有主机相关信息的文件*/

Consulte el manual de referencia de la función de Linux para más detalles . Entre ellos, la definición de estructura hospitalaria es la siguiente:

struct hostent {
    char *h_name;           /*正式主机名,每个主机只有一个*/
    char **h_aliases;     /*主机别名列表,可以有多个,以二位数组形式存储*/
    int h_addrtype;           /*IP地址类型,可以选择IPv4/IPv6*/
    int h_length;           /*IP地址长度,IPv4对应4字节的地址长度*/
    char **h_addr_list;      /*IP地址列表,h_addr_list[0]为主机的IP地址*/
};

Nota: Si llama a gethostent dos veces, el contenido del búfer señalado por el puntero del host se eliminará la primera vez.

2.5 Mapeo de direcciones

Para el usuario, la información de la estructura de direcciones del socket es innecesaria, el usuario solo necesita pasar una dirección de la estructura de direcciones sockaddr_in, y luego el sistema completará el contenido. El servidor en el entorno de red debe proporcionar una dirección IP y un nombre de host únicos (nombre de dominio); para la mayoría de los servidores, el cliente no conoce su dirección IP, pero sí su nombre de dominio. DNS puede convertir un nombre de dominio a una dirección IP. El proceso de conversión es el siguiente:

La dirección IP convertida y el número de puerto se almacenan en la estructura de información addr_info. Proporcione una función en Linux, es decir, puede obtener la dirección IP y el número de puerto del servidor de acuerdo con el nombre de dominio y el nombre del servicio del servidor; complételo en una estructura de dirección sockaddr_in, la función accede internamente al servidor DNS, para obtener la necesidad de acceder al host Número de IP y número de puerto, el prototipo de la función es el siguiente:

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *node, const char *service,const struct addrinfo *hints,struct addrinfo **res);

Consulte el manual de referencia de la función de Linux para más detalles .    


Programación básica de 3 sockets

La tecnología de socket oculta la mayoría de los detalles de la comunicación, haciendo operaciones similares a los archivos. Debido a esto, muchas funciones de manipulación de archivos también se pueden usar en sockets. (La estrategia de Linux de abstraer dispositivos en archivos hace que la programación sea mucho más fácil)

3.1 Crear y destruir descriptores de socket

El prototipo de una función para crear un socket y cancelar un socket en el entorno Linux es el siguiente:

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
#include <unistd.h>
int close(int fd);               /*关闭一个套接字和关闭一个文件是一样的操作*/

Consulte el manual de referencia de la función de Linux para más detalles .

3.2 Enlace de dirección

Después de crear un socket, debe vincular el socket de la dirección para comunicarse. Linux utiliza la función de vinculación para vincular un socket a una dirección. El prototipo de la función es el siguiente:

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

Consulte el manual de referencia de la función de Linux para más detalles . Nota: El protocolo en la estructura sockaddr_in no se puede especificar como IPv6, es decir, el dominio de comunicación no se puede especificar como AF_INET6. Entre ellos, el segundo parámetro debe inicializarse en la práctica, el proceso es el siguiente:

struct socketaddr_in *addr;
addr = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
addr->sin_family = AF_INET;               /*使用IPv4协议的地址列表*/
addr->sin_port = 8888;                    /*端口号,一般大于1024,因为只有root用户才能使用024以下的端口,通常这个端口由系统指派,因为可能被别的进程占用*/
addr->sin_addr=0x60ba8c0;               /*一般通过getaddrinfo来获取,如果希望可以接收网络中任意的数据包,则将此项设置为INADDR_ANY*/
bind(fd, (struct sockaddr_in) *addr,sizeof(struct sockaddr_in));

3.3 Establecer una conexión

Después de vincular un socket, el cliente puede establecer una conexión. Para el tipo de socket orientado a servicios, debe especificarse; para servicios sin conexión, este paso no es necesario. En el entorno Linux, use la función de conexión para establecer una conexión activa, el prototipo de la función es el siguiente:

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

Consulte el manual de referencia de la función de Linux para más detalles . Nota: Para la red, la aplicación debe ser capaz de manejar los errores que pueden ocurrir durante la conexión; hay muchas razones para la falla, y si falla, es necesario considerar volver a intentarlo, pero el intento generalmente requiere un período de retraso para garantizar que la red tenga tiempo para automáticamente Recuperarse

El mecanismo de uso de la función de conexión se muestra en la figura:

Cuando el cliente establece una conexión, el servidor debe monitorear y aceptar dicha conexión, y luego procesarla. La función de escucha utilizada en Linux monitorea la solicitud de conexión del cliente; use la función de aceptación para aceptar una solicitud de conexión. El prototipo de la función es el siguiente:

#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

Consulte el manual de referencia de la función de Linux para más detalles . Nota: Para los descriptores de socket, no puede usar la función lseek para reubicarlos.

3.4 Usar las funciones de lectura y escritura de archivos para leer y escribir sockets

El uso de funciones de lectura / escritura en la red es propenso a problemas por las siguientes razones:

  1. Problema de retraso: para las carpetas locales, el retraso de la transmisión de flujo de bytes localmente se puede ignorar, pero el tiempo de transmisión en la red puede ser muy largo; por lo tanto, provocará el bloqueo de E / S; la solución solo puede ser sin bloqueo / Use múltiples E / S.
  2. Las aplicaciones de red deben ser capaces de manejar el retorno anormal de las operaciones de lectura y escritura debido a problemas de interrupción / conexión de red, pero esto hará que el programa sea más complicado y difícil de controlar.

Nota: La causa del error de la función de cierre en el entorno de red no es el problema del archivo en sí, sino la anormalidad causada por la "salida lenta"; la función de escritura simplemente coloca el contenido del archivo en caché, y la escritura real en el almacenamiento externo es Lleva tiempo, para los archivos locales, casi no hay errores, pero en el entorno de red, la probabilidad de errores es grande; por lo tanto, en el entorno de red, llamar a la función de escritura no garantiza que el archivo haya llegado al extremo opuesto con precisión.

3.5 Transmisión de datos orientada a la conexión

Es fácil equivocarse con la función de lectura / escritura para la comunicación de red en el entorno Linux, pero en Linux hay funciones para sockets orientados a la conexión. Estas dos funciones son send y recv, y sus prototipos de funciones son los siguientes:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

Consulte el manual de referencia de la función de Linux para más detalles .

3.6 El proceso de servidor y cliente orientado a la conexión más simple

@ 1 El proceso de ejecución del lado del servidor (pseudocódigo) es el siguiente:

//地址结构初始化;
fd=socket();
bind(fd,...);
listen(fd,...);
while(1){
     accept_fd=accept(fd,...);
     //与客户端交互,处理来自客户端的请求;(recv/send);
     close(accept_fd);
}
close(fd);
close函数失败的处理。

@ 2 El proceso de ejecución del cliente (pseudocódigo) es el siguiente:

//地址结构初始化;
fd=socket();
connect(fd,...);
//与服务器交互,向服务器发出具体消息/接受来自服务器的消息;(send/recv)
close(fd);
close函数失败的处理。

Nota:

  1. En la práctica, si no conoce la dirección IP del servidor, puede usar la función getaddrinfo para convertir el nombre de dominio del servidor a la IP del servidor a través del servidor DNS; si ni siquiera conoce el nombre de dominio, no puede comunicarse.
  2. Para una LAN generalmente accesible, el servidor y el cliente pertenecen principalmente a un grupo de usuarios, y sus direcciones IP son mutuamente visibles; pero en el entorno de Internet, la IP del servidor a menudo está oculta para el cliente.

3.7 Transmisión de datos sin conexión orientada

Las funciones de lectura y escritura para tomas sin conexión son un poco más complicadas. Dado que no se establece una conexión, la dirección de destino del paquete de datos debe indicarse claramente cada vez que se envían los datos; al recibir el paquete de datos, el proceso de recepción puede obtener La dirección para enviar el paquete. En el entorno Linux, proporciona funciones específicas para leer y escribir sockets sin conexión, que son las funciones sendto y recvfrom. El prototipo de la función es el siguiente:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

Consulte el manual de referencia de la función de Linux para más detalles .

3.8 El proceso de servidor y cliente más simple para sin conexión

@ 1 El  proceso de ejecución del lado del servidor (pseudocódigo) es el siguiente:

//地址结构初始化;
fd=socket();
bind(fd,...);
while(1){
     //与客户端交互,处理来自客户端的请求;(recvfrom/sendto);
}
close(fd);
close函数失败的处理。

@ 2 El proceso de ejecución del cliente (pseudocódigo) es el siguiente:

//地址结构初始化;
fd=socket();
//与服务器交互,向服务器发出具体消息/接受来自服务器的消息;(sendto/recvfrom)
close(fd);

4 enchufes sin bloqueo

Cuando el proceso necesita leer y escribir en el socket, y los datos del socket no están listos, la función de lectura y escritura del socket se bloqueará, de modo que el proceso se suspenda y espere, y las operaciones posteriores no podrán Sí, las E / S sin bloqueo resolverán este problema. Como el socket pertenece a un archivo especial, puede modificar el estado de bloqueo del socket cambiando el método de bloqueo del archivo. El flujo de ejecución (pseudocódigo) en el lado del servidor es el siguiente:

//地址结构初始化;
fd=socket();
bind(fd,...);
     //服务器端在以往的流程上添加的3个逻辑控制语句。
     flag=fcntl(fd,F_GETFL);
     flag|=O_NONBLOCK;
     fcntl(fd,F_SETFL,flag);
listen(fd,...);
while(1){
     accept_fd=accept(fd,...);
     //与客户端交互,处理来自客户端的请求;(recv/send);
     close(accept_fd);
}
close(fd);
close函数失败的处理。

El proceso del cliente de una aplicación de red sin bloqueo puede ser el mismo que antes, o puede convertirse en un bloque de entrada para verificar la corrección del servidor.

Publicado 289 artículos originales · elogiados 47 · 30,000+ vistas

Supongo que te gusta

Origin blog.csdn.net/vviccc/article/details/105174823
Recomendado
Clasificación