Ejemplo de uso de socket del método de comunicación entre procesos de Linux

Análisis de video de comunicación entre procesos de Linux:


Cómo implementar el kernel de Linux y los componentes de comunicación entre procesos para implementar su propio protocolo de comunicación de servidor para
implementar la pila de protocolos de red a mano, prepare el entorno y escriban el código juntos

Socket es un mecanismo de comunicación, en virtud de este mecanismo, el desarrollo de sistemas cliente / servidor se puede llevar a cabo en una computadora local autónoma o en la red.
Las características del socket están determinadas por tres atributos, estos son: dominio (dominio), tipo (tipo) y protocolo (protocolo). El socket también usa la dirección como su nombre. El formato de la dirección varía según el dominio (también conocido como familia de protocolos). Cada familia de protocolos puede utilizar una o más familias de direcciones para definir el formato de la dirección.

1. Dominio de socket

El dominio especifica el medio de red utilizado en la comunicación por socket. El dominio de socket más común es AF_INET, que se refiere a la red de Internet. Muchas LAN de Linux utilizan esta red. Por supuesto, la propia Internet la utiliza. El protocolo subyacente-Protocolo de Internet (IP) tiene solo una familia de direcciones, que utiliza una forma específica de especificar computadoras en la red, es decir, direcciones IP.

Dentro del sistema informático, el puerto se representa mediante la asignación de un entero único de 16 bits. Fuera del sistema, debe determinarse mediante la combinación de la dirección IP y el número de puerto.

2. Tipo de enchufe

Los sockets de flujo (de alguna manera similares a los flujos de entrada / salida estándar de dominio) proporcionan una conexión de flujo de bytes bidireccional ordenada y confiable.

Los sockets de flujo se especifican mediante el tipo SOCK_STREAM y se implementan a través de conexiones TCP / IP en el dominio AF_INET. También son tipos de conectores comunes en el dominio AF_UNIX.

Socket de paquete

A diferencia de los sockets de flujo, el socket de paquete especificado por el tipo SOCK_DGRAM no establece ni mantiene una conexión. Tiene un límite en la longitud de los paquetes de datos que se pueden enviar. El datagrama se transmite como un solo mensaje de red y puede perderse, duplicarse o llegar fuera de servicio.

El socket de datagrama se implementa en el dominio AF_INET a través de una conexión UDP / IP y proporciona un servicio no confiable que es innecesario.

3. Protocolo de socket

Siempre que el mecanismo de transporte subyacente permita que más de un protocolo proporcione el tipo de socket requerido, podemos elegir un protocolo específico para el socket.

Código anterior

Servidor:

//s_unix.c 
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/un.h>  
#define UNIX_DOMAIN "/tmp/UNIX.domain" 
int main(void) 
{
    
     
  socklen_t clt_addr_len; 
  int listen_fd; 
  int com_fd; 
  int ret; 
  int i; 
  static char recv_buf[1024];  
  int len; 
  struct sockaddr_un clt_addr; 
  struct sockaddr_un srv_addr; 
  listen_fd=socket(PF_UNIX,SOCK_STREAM,0); 
  if(listen_fd<0) 
  {
    
     
    perror("cannot create communication socket"); 
    return 1; 
  }  
  //set server addr_param 
  srv_addr.sun_family=AF_UNIX; 
  strncpy(srv_addr.sun_path,UNIX_DOMAIN,sizeof(srv_addr.sun_path)-1); 
  unlink(UNIX_DOMAIN); 
  //bind sockfd & addr 
  ret=bind(listen_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr)); 
  if(ret==-1) 
  {
    
     
    perror("cannot bind server socket"); 
    close(listen_fd); 
    unlink(UNIX_DOMAIN); 
    return 1; 
  } 
  //listen sockfd  
  ret=listen(listen_fd,1); 
  if(ret==-1) 
  {
    
     
    perror("cannot listen the client connect request"); 
    close(listen_fd); 
    unlink(UNIX_DOMAIN); 
    return 1; 
  } 
  //have connect request use accept 
  len=sizeof(clt_addr); 
  com_fd=accept(listen_fd,(struct sockaddr*)&clt_addr,&len); 
  if(com_fd<0) 
  {
    
     
    perror("cannot accept client connect request"); 
    close(listen_fd); 
    unlink(UNIX_DOMAIN); 
    return 1; 
  } 
  //read and printf sent client info 
  printf("/n=====info=====/n"); 
  for(i=0;i<4;i++) 
  {
    
     
    memset(recv_buf,0,1024); 
    int num=read(com_fd,recv_buf,sizeof(recv_buf)); 
    printf("Message from client (%d)) :%s/n",num,recv_buf);  
  } 
  close(com_fd); 
  close(listen_fd); 
  unlink(UNIX_DOMAIN); 
  return 0; 
} 

Cliente:

//c_unix.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIX_DOMAIN "/tmp/UNIX.domain"
int main(void)
{
    
    
  int connect_fd;
  int ret;
  char snd_buf[1024];
  int i;
  static struct sockaddr_un srv_addr;
//creat unix socket
  connect_fd=socket(PF_UNIX,SOCK_STREAM,0);
  if(connect_fd<0)
  {
    
    
    perror("cannot create communication socket");
    return 1;
  }  
  srv_addr.sun_family=AF_UNIX;
  strcpy(srv_addr.sun_path,UNIX_DOMAIN);
//connect server
  ret=connect(connect_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
  if(ret==-1)
  {
    
    
    perror("cannot connect to the server");
    close(connect_fd);
    return 1;
  }
  memset(snd_buf,0,1024);
  strcpy(snd_buf,"message from client");
//send info server
  for(i=0;i<4;i++)
    write(connect_fd,snd_buf,sizeof(snd_buf));
  close(connect_fd);
  return 0;
}

El uso de sockets no solo puede realizar la comunicación entre diferentes hosts en la red, sino también realizar la comunicación entre diferentes procesos del mismo host, y la comunicación establecida es una comunicación bidireccional. La comunicación de proceso de socket y la comunicación de red utilizan un socket unificado, pero la estructura de la dirección es diferente de algunos parámetros.

[Beneficios del artículo] Materiales de aprendizaje para arquitectos de servidores C / C ++ Linux más el grupo 812855908 (datos que incluyen C / C ++, Linux, tecnología golang, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, medios de transmisión, CDN, P2P, K8S, Docker, TCP / IP, corrutina, DPDK, ffmpeg, etc.)
Inserte la descripción de la imagen aquí

Uno, crear proceso de socket

(1) Cree un conector, el tipo es AF_LOCAL o AF_UNIX, lo que significa que se utiliza para la comunicación de procesos:

Para crear un socket, debe usar la llamada al sistema de socket, y su prototipo es el siguiente:

int socket(int domain, int type, int protocol);

Entre ellos, el parámetro de dominio especifica la familia de protocolos. Para sockets locales, su valor debe establecerse en el valor de enumeración AF_UNIX; el parámetro de tipo especifica el tipo de socket y el parámetro de protocolo especifica el protocolo específico; el parámetro de tipo se puede establecer en SOCK_STREAM (socket de tipo de flujo) o SOCK_DGRAM (socket de tipo de datagrama), el campo de protocolo debe establecerse en 0; el valor de retorno es el descriptor de socket generado.

Para sockets locales, el socket de transmisión (SOCK_STREAM) es un flujo de bytes bidireccional secuencial y confiable, lo que equivale a establecer un canal de datos entre procesos locales; el socket de datagrama (SOCK_DGRAM)) es equivalente a simplemente enviar un mensaje. En el proceso de comunicación del proceso, puede haber situaciones en las que la información se pierde, copia o llega desordenada en teoría, pero debido a que se comunica localmente y no pasa por la red externa, la probabilidad de que ocurran estas situaciones es muy alta. .

2. Nombra el enchufe.

Ambas partes de la comunicación de socket local de tipo SOCK_STREAM deben tener una dirección local, y la dirección local del lado del servidor debe especificarse claramente El método de especificación es usar una variable de tipo struct sockaddr_un.

struct sockaddr_un {
    
    
  sa_family_t   sun_family;   /* AF_UNIX */
  char  sun_path[UNIX_PATH_MAX];    /* 路径名 */
};

Hay algo muy importante aquí, hay dos formas de nombrar la comunicación del proceso de socket. Una es la denominación ordinaria. El socket creará un archivo de socket con el mismo nombre basado en esta denominación. Cuando el cliente se conecta, se conecta al servidor de socket leyendo el archivo de socket. La desventaja de este método es que el servidor debe tener permiso de escritura en la ruta del archivo de socket, el cliente debe conocer la ruta del archivo de socket y debe tener permiso de lectura en la ruta.

Otra forma de nombrar es el espacio de nombres abstracto, de esta manera no es necesario crear un archivo de socket, solo necesita nombrar un nombre global para que el cliente pueda conectarse de acuerdo con este nombre. La diferencia entre el proceso de implementación de este último y el primero es que cuando este último asigna la matriz sun_path del miembro de la estructura de direcciones, el primer byte debe establecerse en 0, es decir, sun_path [0] = 0. El siguiente código ilustra:

La primera forma:

//name the server socket 
	server_addr.sun_family = AF_UNIX;
	strcpy(server_addr.sun_path,"/tmp/UNIX.domain");
	server_len = sizeof(struct sockaddr_un);
	client_len = server_len;

La segunda forma:


#define SERVER_NAME @socket_server 
  server_addr.sun_family = AF_UNIX; 
  strcpy(server_addr.sun_path, SERVER_NAME); 
  server_addr.sun_path[0]=0; 
  //server_len = sizeof(server_addr); 
  server_len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);

Entre ellos, la función offsetof se define en el archivo de encabezado #include <stddef.h>. Debido a que el primer byte del segundo método se establece en 0, podemos agregar una cadena de marcador de posición antes de la cadena con nombre SERVER_NAME, por ejemplo:

#define SERVER_NAME @socket_server  

El símbolo @ delante de él representa un marcador de posición y no se cuenta como un nombre real.

Consejo: cuando el cliente se conecta al servidor, el método de denominación debe ser el mismo que el del servidor, es decir, si el servidor es un método de denominación normal, la dirección del cliente también debe ser un método de denominación normal; si el servidor es un método de nomenclatura abstracto, la dirección del cliente también debe ser un método de nomenclatura abstracta.

Tres, vinculante

Ambas partes de la comunicación de socket local de tipo SOCK_STREAM deben tener una dirección local, y la dirección local del lado del servidor debe especificarse claramente. El método de especificación es usar una variable de tipo struct sockaddr_un, asignar el campo correspondiente y luego vincularlo al socket del servidor creado Literalmente, el vínculo usa la llamada al sistema de vinculación, y su prototipo es el siguiente:

int bind(int socket, const struct sockaddr *address, size_t address_len);

Donde socket representa el descriptor de socket en el lado del servidor, address representa la dirección local que se enlazará, es una variable de tipo struct sockaddr_un y address_len representa la longitud en bytes de la dirección local. El código para implementar la función de especificación de dirección del lado del servidor es el siguiente (asumiendo que el servidor ha creado un socket a través de la llamada al sistema de socket descrita anteriormente, y server_sockfd es su descriptor de socket):

struct sockaddr_un server_address;
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "Server Socket");
bind(server_sockfd, (struct sockaddr*)&server_address, sizeof(server_address));

No es necesario especificar explícitamente la dirección local del cliente, solo debe poder conectarse al servidor. Por lo tanto, la variable struct sockaddr_un del cliente debe configurarse de acuerdo con la configuración del servidor. El código es de la siguiente manera (asumiendo que el cliente ha pasado el socket descrito anteriormente La llamada al sistema crea un socket, y client_sockfd es su descriptor de socket):

struct sockaddr_un client_address;
client_address.sun_family = AF_UNIX;
strcpy(client_address.sun_path, "Server Socket");

Cuatro, monitor

Después de que se crea el socket del lado del servidor y se le asigna el valor de la dirección local (nombre, en este caso, Server Socket), es necesario monitorearlo, esperar a que el cliente se conecte y procese la solicitud, monitorear usando la llamada del sistema de escucha y aceptar la conexión del cliente utilizando la llamada al sistema de aceptación, su prototipo es el siguiente:

int listen(int socket, int backlog);
int accept(int socket, struct sockaddr *address, size_t *address_len);

Donde socket representa el descriptor de socket en el lado del servidor; el backlog representa la longitud de la cola de conexión en cola (si varios clientes están conectados al mismo tiempo, debe estar en cola); la dirección representa la dirección local del cliente actualmente conectado, esto parámetro es un parámetro de salida, sí La información sobre sí mismo pasada por el cliente; address_len representa la longitud en bytes de la dirección local del cliente actualmente conectado Este parámetro es tanto un parámetro de entrada como un parámetro de salida. El código de seguimiento, recepción y procesamiento es el siguiente:

#define MAX_CONNECTION_NUMBER 10
int server_client_length, server_client_sockfd;
struct sockaddr_un server_client_address;
listen(server_sockfd, MAX_CONNECTION_NUMBER);
while(1)
{
    
    
  // ...... (some process code)
  server_client_length = sizeof(server_client_address);
  server_client_sockfd = accept(server_sockfd, (struct sockaddr*)&server_client_address, &server_client_length);
  // ...... (some process code)
}

La razón para usar el bucle infinito aquí es que el servidor es una entidad que brinda servicios continuamente. Necesita monitorear, aceptar y procesar conexiones continuamente. En este ejemplo, cada conexión solo se puede procesar en serie, es decir, después de una conexión se procesa, para continuar con el procesamiento de la conexión posterior. Si desea que se procesen varias conexiones al mismo tiempo, debe crear subprocesos y entregar cada conexión al subproceso correspondiente para su procesamiento simultáneo.

Una vez que se crea el socket del cliente y se le asigna un valor de dirección local, debe conectarse al servidor para la comunicación y dejar que el servidor le proporcione servicios de procesamiento. Para los sockets de transmisión de tipo SOCK_STREAM, se requiere una conexión entre el cliente y el servidor. Para conectarse para usar la llamada al sistema de conexión, su prototipo es

int connect(int socket, const struct sockaddr *address, size_t address_len);

Donde socket es el descriptor de socket del cliente, address representa la dirección local del cliente actual, que es una variable de tipo struct sockaddr_un, y address_len representa la longitud en bytes de la dirección local. El código para realizar la conexión es el siguiente:

connect(client_sockfd, (struct sockaddr*)&client_address, sizeof(client_address));

Tanto el cliente como el servidor deben interactuar entre sí sobre los datos, interacción que también es el tema de nuestro proceso de comunicación. Un proceso desempeña el papel del cliente y el otro el papel del servidor. Los dos procesos se envían y reciben datos entre sí. Esta es la comunicación del proceso basada en el socket local. Envíe y reciba datos para usar llamadas al sistema de escritura y lectura, su prototipo es:

int read(int socket, char *buffer, size_t len);
int write(int socket, char *buffer, size_t len);

Donde socket es el descriptor de socket; len es la longitud de los datos que se enviarán o recibirán; para la llamada al sistema de lectura, buffer es el búfer utilizado para almacenar los datos recibidos, es decir, los datos recibidos se almacenan en él y es un parámetro de salida; para la llamada al sistema de escritura, el búfer se utiliza para almacenar los datos que se deben enviar, es decir, se envían los datos en el búfer, que es un parámetro de entrada; el valor de retorno es la longitud de los datos que ha sido enviado o recibido. Por ejemplo, si el cliente desea enviar una cadena de "Hola" al servidor, el código es el siguiente:

char buffer[10] = "Hello";
write(client_sockfd, buffer, strlen(buffer));

Una vez finalizada la interacción, es necesario desconectar la conexión para ahorrar recursos. Se utiliza la llamada de cierre del sistema. El prototipo es:

int close(int socket);

No hay mucho que decir, solo úsalo directamente, todos lo sabrán, ¡ja, ja!

Cada llamada al sistema descrita anteriormente tiene un valor de retorno de -1. Cuando la llamada no tiene éxito, todas devolverán -1. Esta característica nos permite usar instrucciones de manejo if - else o excepciones para manejar errores, lo que nos brinda una gran comodidad.

Los sockets locales de estilo datagrama SOCK_DGRAM rara vez se utilizan. Debido a que el tiempo de conexión local de los sockets de transmisión se puede ignorar, la eficiencia no mejora, y el envío y la recepción deben llevar la dirección local de la otra parte, por lo que rara vez o casi No utilice.

Correspondiente al enchufe local está el enchufe de red, que se puede utilizar para transmitir datos en la red, es decir, puede realizar el proceso de comunicación entre diferentes máquinas. En el protocolo TCP / IP, el primer byte de la dirección IP es 127, lo que significa local, por lo que la comunicación de socket local se puede realizar utilizando un socket de red con una dirección IP de 127.xxx.

Lo anterior es todo el contenido de este artículo sobre el ejemplo de uso de sockets de la comunicación entre procesos de Linux, espero que sea útil para todos.

Supongo que te gusta

Origin blog.csdn.net/qq_40989769/article/details/112392736
Recomendado
Clasificación