2010-07-24 10 views
29

Me gustaría establecer una conexión IPC entre varios procesos en Linux. Nunca antes había usado conectores UNIX, y por lo tanto, no sé si este es el enfoque correcto para este problema.Socket de dominio Unix: uso de comunicación de datagramas entre un proceso de servidor y varios procesos de cliente

Un proceso recibe datos (no formateados, binarios) y distribuirá estos datos a través de un socket local AF_UNIX utilizando el protocolo de datagramas (es decir, similar a UDP con AF_INET). Los datos enviados desde este proceso a un socket Unix local deben ser recibidos por múltiples clientes que estén escuchando en el mismo socket. La cantidad de receptores puede variar.

Para lograr esto el siguiente código se utiliza para crear un socket y enviar datos a la misma (el proceso de servidor):

struct sockaddr_un ipcFile; 
memset(&ipcFile, 0, sizeof(ipcFile)); 
ipcFile.sun_family = AF_UNIX; 
strcpy(ipcFile.sun_path, filename.c_str()); 

int socket = socket(AF_UNIX, SOCK_DGRAM, 0); 
bind(socket, (struct sockaddr *) &ipcFile, sizeof(ipcFile)); 
... 
// buf contains the data, buflen contains the number of bytes 
int bytes = write(socket, buf, buflen); 
... 
close(socket); 
unlink(ipcFile.sun_path); 

rendimientos Esta escritura -1 con errno informes ENOTCONN ("punto final de transporte no está conectado "). Supongo que esto se debe a que ningún proceso de recepción está escuchando este conector local, ¿correcto?

Luego, traté de crear un cliente que se conecta a este socket.

struct sockaddr_un ipcFile; 
memset(&ipcFile, 0, sizeof(ipcFile)); 
ipcFile.sun_family = AF_UNIX; 
strcpy(ipcFile.sun_path, filename.c_str()); 

int socket = socket(AF_UNIX, SOCK_DGRAM, 0); 
bind(socket, (struct sockaddr *) &ipcFile, sizeof(ipcFile)); 
... 
char buf[1024]; 
int bytes = read(socket, buf, sizeof(buf)); 
... 
close(socket); 

Aquí, el enlace falla ("Dirección ya en uso"). Entonces, ¿tengo que establecer algunas opciones de socket, o este es generalmente el enfoque equivocado?

¡Gracias de antemano por sus comentarios/soluciones!

+0

Check también PHP como cliente y como servidor de C [aquí] (http://stackoverflow.com/a/43421610/4626775) –

Respuesta

-3

Debería tener en cuenta la multidifusión IP en lugar del dominio Unix. En este momento solo estás tratando de escribir en ninguna parte. Y si se conecta a un cliente, solo le estará escribiendo a ese cliente.

Esto no funciona de la forma en que parece que lo hace.

+0

multidifusión para los procesos? –

+0

Claro, ¿por qué no? De lo contrario, tiene que enviar cada mensaje N veces, y los clientes no todos reciben los mensajes al mismo tiempo, lo que plantea problemas de equidad. – EJP

+0

Me disculpo; Simplemente nunca he visto direcciones de multidifusión para sockets UNIX. ¿Tiene un ejemplo de trabajo o malinterpreté su publicación? –

-1

¿No sería más fácil usar memoria compartida o conductos con nombre? Un socket es una conexión entre dos procesos (en la misma máquina o en otra diferente). No es un método de comunicación masiva.

Si quiere dar algo a clientes múltiples, crea un servidor que espera conexiones y luego todos los clientes pueden conectarse y les da la información. Puede aceptar conexiones simultáneas haciendo que el programa tenga múltiples subprocesos o mediante procesos de bifurcación. El servidor establece múltiples conexiones basadas en socket con múltiples clientes, en lugar de tener un socket al que se conectan múltiples clientes.

7

La causa inmediata de su error es que write() no sabe dónde desea enviar los datos a. bind() establece el nombre de su lado del zócalo, es decir. donde están llegando los datos de. Para establecer el lado de destino del socket, puede usar connect(); o puede usar sendto() en lugar de write().

El otro error ("Dirección ya en uso") se debe a que solo un proceso puede bind() a una dirección.

Deberá cambiar su enfoque para tener esto en cuenta. Su servidor deberá escuchar en una dirección conocida, configurada con bind(). Sus clientes deberán enviar un mensaje al servidor en esta dirección para registrar su interés en recibir datagramas. El servidor recibirá los mensajes de registro de los clientes usando recvfrom() y registrará la dirección utilizada por cada cliente.Cuando quiera enviar un mensaje, tendrá que pasar por todos los clientes que conoce, utilizando sendto() para enviar el mensaje a cada uno por turno.

Como alternativa, podría usar multidifusión IP local en lugar de sockets de dominio UNIX (los sockets de dominio UNIX no admiten multidifusión).

-4

Puede resolver el error se unen con el siguiente código:

int use = yesno; 
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&use, sizeof(int)); 

Con el protocolo UDP, debe invocar connect() si desea utilizar o write()send(), de lo contrario se debe utilizar en lugar sendto().

para alcanzar sus requisitos, el siguiente pseudo código puede ser de ayuda:

sockfd = socket(AF_INET, SOCK_DGRAM, 0) 
set RESUSEADDR with setsockopt 
bind() 
while (1) { 
    recvfrom() 
    sendto() 
} 
+0

OP preguntó acerca de una solución para AF_UNIX no AF_INET – Flow

36

Hay un truco para el uso de los conectores de datagramas UNIX. A diferencia de los sockets de flujo (dominio tcp o unix), los sockets de datagramas necesitan puntos finales definidos tanto para el servidor como para el cliente. Cuando se establece una conexión en sockets de flujo, el sistema operativo crea implícitamente un punto final para el cliente. Si esto corresponde a un efímero puerto TCP/UDP, o un inodo temporal para el dominio Unix, el punto final para el cliente se crea para usted. Es por eso que normalmente no necesita emitir una llamada a bind() para sockets de flujo en el cliente.

La razón por la que está viendo "La dirección ya está en uso" es porque le está diciendo al cliente que se enlace a la misma dirección que el servidor. bind() se trata de afirmar la identidad externa. Dos enchufes normalmente no pueden tener el mismo nombre.

Con conectores de datagramas, específicamente UNIX sockets de datagramas de dominio, el cliente tiene que bind() a su propia punto final, a continuación, a connect() punto final del servidor . Aquí es su código de cliente, ligeramente modificada, con algunas otras golosinas tirado en:

char * server_filename = "/tmp/socket-server"; 
char * client_filename = "/tmp/socket-client"; 

struct sockaddr_un server_addr; 
struct sockaddr_un client_addr; 
memset(&server_addr, 0, sizeof(server_addr)); 
server_addr.sun_family = AF_UNIX; 
strncpy(server_addr.sun_path, server_filename, 104); // XXX: should be limited to about 104 characters, system dependent 

memset(&client_addr, 0, sizeof(client_addr)); 
client_addr.sun_family = AF_UNIX; 
strncpy(client_addr.sun_path, client_filename, 104); 

// get socket 
int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0); 

// bind client to client_filename 
bind(sockfd, (struct sockaddr *) &client_addr, sizeof(client_addr)); 

// connect client to server_filename 
connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)); 

... 
char buf[1024]; 
int bytes = read(sockfd, buf, sizeof(buf)); 
... 
close(sockfd); 

En este punto su toma debe estar plenamente configuración. Creo que teóricamente puedes usar read()/write(), pero generalmente usaría send()/recv() para conectores de datagramas.

Normalmente, querrá comprobar el error después de cada una de estas llamadas y emitir un perror() después. Te será de gran ayuda cuando las cosas vayan mal. En general, use un patrón como este:

if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { 
    perror("socket failed"); 
} 

Esto se aplica a casi todas las llamadas al sistema C.

La mejor referencia para esto es Steven "Programación de red Unix". En la tercera edición, la sección 15.4, páginas 415-419 muestran algunos ejemplos y enumeran muchas de las advertencias.

Por cierto, en referencia a

supongo que esto se debe a que hay un proceso de recepción está escuchando actualmente a este conector local, correcto?

Creo que tienes razón sobre el error ENOTCONN de write() en el servidor. Un socket UDP normalmente no se quejaría porque no tiene la capacidad de saber si el proceso del cliente está escuchando.Sin embargo, los sockets de datagramas de dominio de Unix son diferentes. De hecho, el write() realmente bloqueará si el búfer de recepción del cliente está lleno en lugar de soltar el paquete. Esto hace que los zócalos de datagramas de dominio de Unix sean muy superiores a UDP para IPC, ya que con toda seguridad UDP abandonará los paquetes cuando esté bajo carga, incluso en el host local. Por otro lado, significa que debes tener cuidado con los escritores rápidos y los lectores lentos.

+0

Pude conectar los puntos usando esta respuesta, la de @caf, y los últimos 2 archivos fuente en http://www.thomasstover.com/uds.html (nota que hay un par de errores menores en ese código). El código del servidor en la pregunta no funcionará sin obtener las direcciones del cliente, que me di cuenta cuando leí la respuesta de caf. – hBrent

+1

Las oraciones que comienzan 'Un zócalo UDP normalmente no se quejaría' y 'Esto hace que los zócalos de datagrama de dominio de Unix sean muy superiores a UDP' sean ambos incorrectos. La mayoría, si no todo, de lo que has dicho sobre los sockets de datagramas de dominio Unix se aplica igualmente a los sockets UDP de dominio de IP: específicamente, que tienen que estar conectados para usar 'write(),' y que bloquean en 'write() 'o' send() 'mientras el buffer de envío está lleno. -1 por desinformación. – EJP

+0

@EJP: Creo que malinterpretaste algunas de mis respuestas. No quise dar a entender que los sockets podrían estar desconectados durante 'write()', y no mencioné nada acerca de un búfer de envío completo (solo un buffer de recepción completo). Moví el párrafo que mencionaste a continuación porque analiza una pregunta auxiliar, que puede ser parte de la confusión. – adamlamar

1

Si la pregunta la intención de estar a punto de radiodifusión (como yo lo entiendo), entonces de acuerdo con unix(4) - UNIX-domain protocol family, la difusión no está disponible con conectores de dominio UNIX:

El Unix Ns -domain familia de protocolos no admite direccionamiento de difusión o cualquier forma de "comodín" que coincida con en los mensajes entrantes. Todas las direcciones son absolutas o nombres de rutas relativas de otros sockets de dominio Ns de Unix.

Puede ser multidifusión podría ser una opción, pero me siento al saber que no está disponible con POSIX, aunque Linux supports UNIX Domain Socket multicast.

Ver también: Introducing multicast Unix sockets.

-1

Ocurrirá porque el servidor o el cliente mueren antes de desvincular/eliminar para el asociado de archivo bind(). cualquiera de los clientes/servidores que utilizan esta ruta de enlace, intente ejecutar el servidor de nuevo.

soluciones: cuando desee volver a enlazar, simplemente verifique que el archivo ya esté asociado y luego desenlace ese archivo. Cómo paso: primer acceso de comprobación de este archivo por el acceso (2); en caso afirmativo, desvinculalo (2). pon esta paz de código antes de la llamada bind(), la posición es independiente.

if(!access(filename.c_str())) 
    unlink(filename.c_str()); 

para más referencia leer UNIX (7)

Cuestiones relacionadas