Tema 5. Comunicacion entre procesos I. + Comunicación entre procesos. Como se ha visto en los temas anteriores, UNIX proporciona la capacidad de tener un sistema multitarea, donde los procesos pueden accesar a los diferentes sistemas de cómputo utilizando el sistema de Entrada/Salida. Sin embargo, en muchos casos, es necesario que varios procesos se coordinen entre si para indicar que ya concluyeron una actividad o comunicarse datos del resultado de procesamiento de uno de ellos. Cuando se estudiaron los procesos, el unico esquema de coordinacion era con la llamada wait, waitpid y exit, que permitia que el proceso padre esperar a su hijo y en un momento, con ayuda de la llamada exit, se podian comunicar padre e hijo un valor. En este tema se va a estudiar esquemas de comunicación entre procesos que permiten comunicar datos entre procesos locales (es decir ejecutandose en la misma computadora) y eventos entre procesos. Aun se supone que no hay concurrencia entre procesos, o si se llega a presentar, por el momento se ignora. + Tipos de procesamiento. Cuando varios procesos estan trabajando en conjunto, puede ser que algunos de ellos invoquen a otro proceso. Cuando un proceso A invoca a otro proceso B, y dicho proceso A espera que B entrego todos los datos, se esta realizando un procesamiento síncrono. Un procesamiento por entubamiento consiste en que varios procesos se encadenen y cada uno realiza una parte del procesamiento, como las lineas de ensamblado de carros. UNIX implanta este tipo de procesamiento utilizando pipes y FIFOS. Cuando un proceso A invoca a otro proceso B, pero no espera a que concluya el proceso B y, recibe datos de éste cuando le notifica un evento, se conoce como procesamiento asíncrono. En UNIX, los eventos entre procesos se conocen como señales, y son eventos generados por uno o mas procesos y que son atendidos por procesos que se interesaron en estas señales. UNIX utiliza el concepto de señales o signals para definir "interrupciones de software". + Entubamientos o Pipes. A partir de este punto, se manejara el concepto de entubamiento como pipe. Un pipe para UNIX es un esquema que permite comunicar a dos procesos. Como se estudio en el tema 4, los comandos UNIX están diseñados para que en el caso de que no se proporcione un argumento de entrada, entonces la entrada la toma de la entrada estándar. Y los resultados, cuando son correctos, los envia a la salida estándar. Es típica esta forma de trabajar. Redireccionar la entrada estándar de un archivo y enviar el resultado a la salida estándar. cat < /etc/passwd > archivo Luego, aplicar una busqueda de un patrón sobre el archivo. grep root archivo root:x:0:1:Super-User:/:/sbin/sh Como se observa, este procesamiento implico dos pasos, y la generación de un archivo temporal. Se puede optimizar este procesamiento utilizando un pipe. cat < /etc/passwd | grep root Al utilizar el metacaracter |, se indica al shell de UNIX que la salida estándar se redireccione no a pantalla y tampoco a un archivo, sino que la direccione a la entrada estándar de otro proceso. Dicho proceso es el comando grep, que tendrá definida su entrada estándar a partir de la salida estándar del proceso cat. En este caso, el proceso cat y grep estan ejecutandose en paralelo. solo que el comando grep se bloquea hasta que obtenga algo de la entrada estándar, es decir, de la salida del comando cat. El número de procesos que se pueden encadenar en un pipe no tiene límite, se podria realizar lo siguiente: cat < /etc/passwd | grep root | cut -d: -f3 0 este comando proporciona como salida el userid de root. En resument, un pipe es un mecanismo de comunicación entre procesos que permite que varios procesos trabajen en conjunto y cada uno de ellos se comunican datos utilizando como medio de comunicacion la entrada y salida estándar. Ejercicios. 1. Ilustrar graficamente el funcionamiento de pipes, incluyendo en la ilustración, las tablas de descriptores de archivos de los procesos. 2. Explicar el funcionamiento del comando zcat < tema5.taz | tar xvf - que diferencia hay con: mv tema5.taz tema5.tar.Z uncompress tema5.tar.Z tar xvf tema5 ? (nota, el comando zcat sirve para desplegar el contenido de un archivo que se comprimió, uncompress aplica descompresion de un archivo y el comando tar xvf extrae de un archivo un conjunto de archivos ). 3. Que hace el comando tar cvf - apuntes src | compress > tema5.taz ? Proporcionar el conjunto de comandos que harian una tarea similar. + Llamadas al sistema para uso de pipes. pipe(2) Para poder crear una tuberia o pipe entre procesos se debe utilizar la llamada al sistema pipe. #include int pipe(int fildes[2]) El argumento es un arreglo de descriptores de archivos, de tamaño 2. En la celda 0 del arreglo, se retorna el descriptor de archivos del cual se van a leer los datos y en la celda 1 del arreglo, se retorna el descriptor de archivos al cual se escriben los datos. Si la llamada tiene exito, retorna 0, de lo contrario 1. El siguiente ejemplo crea un pipe entre la entrada y salida estándar de un mismo proceso. /*Ejemplo1, Crear un pipe con la entrada y salida estandar del proceso actual. */ #include main(){ int fildes[2]; /*arreglo de descriptores de archivos*/ int rtn; char mensaje[]="hola pipe\n"; char buffer[128]; int nbytes; /*Crear el pipe*/ rtn=pipe(fildes); if (rtn < 0) { perror("No se puede crear el pipe"); exit(1); } /*Checar los descriptores de archivos del pipe*/ printf("Pipe read %d \n Pipe write %d\n",fildes[0],fildes[1]); /*Escribir algo en el pipe*/ if ( write(fildes[1],mensaje,strlen(mensaje) ) != strlen(mensaje) ) { perror("Error al escribir al pipe"); exit(2); } if ( (nbytes=read(fildes[0],buffer,128)) <= 0 ) { perror("Error al leer el pipe\n"); exit(3); } write(1,buffer,nbytes); exit(0); } Para que dos procesos se comuniquen con ayuda de un pipe, dichos procesos deben ser creados por un proceso padre que generar el pipe. El primer esquema de comunicación es cuando un proceso padre crea a dos procesos hijos. Un proceso hijo (P1) realiza la escritura sobre el descriptor de archivos de escritura del pipe, y el otro proceso (P2) realiza la lectura sobre el descriptor de archivos de lectura del pipe. -- ------ -- P1 --->| Pipe |----> P2 -- ------ -- Esta forma de procesamiento es unidireccional, en donde el proceso servidor no retorna la respuesta al cliente. Un análogo es a un procedimiento. En resumen, para hacer un "procedimiento cliente/servidor" con pipe, se deben realizar los siguiente pasos: 1. El proceso padre crea un pipe. 2. El proceso padre crea dos hijos. 3. El proceso hijo cliente, debe cerrar el extremo de lectura del pipe. 4. El proceso padre servidor, debe cerrar el extremo de escritura del pipe. 5. El proceso hijo cliente, genera la información y la envia por medio de un write al extremo de escritura del pipe. 6. El proceso padre servidor, lee el extremo de lectura del pipe (se queda bloqueado hasta que alguien le escribe) y procesa la información que recibe. 7. El proceso cliente no espera respuesta del servidor. 8. El proceso servidor no genera respuesta al cliente. 9. Ambos procesos cierran los extremos restantes del pipe. En ejercicios posteriores se desarrolla esta idea para escribir una función genérica para invocar procedimientos. El siguiente programa muestra este concepto. #include main() { int pids[2]; int pipefd[2]; int rtn,i; /* 1. El proceso padre crea un pipe. Crear el pipe*/ rtn= pipe(pipefd); if (rtn< 0) { perror("No se puede generar el pipe"); exit(1); } /*generar procesos hijos*/ for (i=0;i<=1;i++) { /*2. El proceso padre crea dos hijos.*/ printf("Proceso %d Bifurcando..\n",getpid()); rtn=fork(); if (rtn<0) { perror("no puedo generar un proceso hijo"); exit(2); } if (rtn==0) { if (i==0) { /*proceso que genera informacion*/ /*Este proceso se conoce como el proceso cliente */ char buffer[]="hola mundo\n"; char respuesta[128]; int nbytes; printf("Proceso %d genera mensaje %s\n", getpid(),buffer); /* 3. El proceso hijo cliente, debe cerrar el extremo de lectura del pipe. */ close(pipefd[0]); /* 5. El proceso hijo cliente, genera la información y la envia por medio de un write al extremo de escritura del pipe. */ write(pipefd[1],buffer,strlen(buffer)); /*9. Ambos procesos cierran los extremos restantes del pipe.*/ close(pipefd[1]); exit(0); } else { /*Proceso servidor */ char mensaje[128]; int nbytes; int j; printf("Proceso %d esperando mensaje\n", getpid()); /*4. El proceso padre servidor, debe cerrar el extremo de escritura del pipe.*/ close(pipefd[1]); /* 6. El proceso padre servidor, lee el extremo de lectura del pipe (se queda bloqueado hasta que alguien le escribe) y procesa la información que recibe.*/ nbytes= read(pipefd[0],mensaje,128); /*Poner caracter nulo al final*/ mensaje[nbytes]=0; printf("proceso %d procesando mensaje %s de %d bytes\n", getpid(),mensaje,nbytes); /*Poner el mensaje a mayusculas*/ for(j=0;j| Pipe1 |----> P2 ------ ----- <--| Pipe2 |<---- -- ------ -- Esta es una forma bidireccional, de tal manera que el cliente puede enviar un mensaje a procesar y recibe respuests. Para poder lograr esto con pipes, se deben realizar los siguientes pasos: 1. El proceso padre crea dos pipes. 2. El proceso padre crea dos hijos. 3. El proceso hijo cliente, debe cerrar el extremo de lectura del pipe1 y el extremo de escritura del pipe2. 4. El proceso padre servidor, debe cerrar el extremo de escritura del pipe1 y el extremo de lectura del pipe2. 5. El proceso hijo cliente, genera la información y la envia por medio de un write al extremo de escritura del pipe1. 6. El proceso padre servidor, lee el extremo de lectura del pipe1 (se queda bloqueado hasta que alguien le escribe) y procesa la información que recibe. 7. El proceso cliente espera respuesta del servidor leyendo del extremo de lectura del pipe2. 8. El proceso servidor escribe la respuesta al cliente, escribiendo al extremo de escritrua del pipe2. 9. Ambos procesos cierran los extremos restantes del pipe. Este ejemplo muestra lo anterior: #include main() { int pids[2]; int pipefd1[2]; int pipefd2[2]; int rtn,i; /*1. El proceso padre crea dos pipes. */ rtn= pipe(pipefd1); if (rtn< 0) { perror("No se puede generar el pipe 1"); exit(1); } /*Crear el pipe*/ rtn= pipe(pipefd2); if (rtn< 0) { perror("No se puede generar el pipe 2"); exit(1); } /*generar procesos hijos*/ for (i=0;i<=1;i++) { /*2. El proceso padre crea dos hijos.*/ printf("Proceso %d Bifurcando..\n",getpid()); rtn=fork(); if (rtn<0) { perror("no puedo generar un proceso hijo"); exit(2); } if (rtn==0) { if (i==0) { /*proceso que genera informacion*/ char buffer[]="hola mundo\n"; char respuesta[128]; int nbytes; printf("Proceso %d genera mensaje %s\n", getpid(),buffer); /*3. El proceso hijo cliente, debe cerrar el extremo de lectura del pipe1 y el extremo de escritura del pipe2. */ close(pipefd1[0]); close(pipefd2[1]); /* 5. El proceso hijo cliente, genera la información y la envia por medio de un write al extremo de escritura del pipe1. */ write(pipefd1[1],buffer,strlen(buffer)); printf("Proceso %d esperando respuesta\n", getpid()); /*7. El proceso cliente espera respuesta del servidor leyendo del extremo de lectura del pipe2. */ nbytes=read(pipefd2[0],respuesta,128); /*se pone el caracter nulo en la ultima posicion*/ respuesta[nbytes]=0; printf("proceso %d leyendo respuesta %s de %d bytes\n", getpid(),respuesta,nbytes); /*9. Ambos procesos cierran los extremos restantes del pipe.*/ close(pipefd1[1]); close(pipefd2[0]); exit(0); } else { /*Proceso servidor, se arranca primero */ char mensaje[128]; int nbytes; int j; printf("Proceso %d esperando mensaje\n", getpid()); /* 4. El proceso padre servidor, debe cerrar el extremo de escritura del pipe1 y el extremo de lectura del pipe2. */ close(pipefd1[1]); close(pipefd2[0]); /*6. El proceso padre servidor, lee el extremo de lectura del pipe1 (se queda bloqueado hasta que alguien le escribe) y procesa la información que recibe.*/ nbytes= read(pipefd1[0],mensaje,128); /*Poner caracter nulo al final*/ mensaje[nbytes]=0; printf("proceso %d procesando mensaje %s de %d bytes\n", getpid(),mensaje,nbytes); /*Poner el mensaje a mayusculas*/ for(j=0;j | Pipe1|--> P2---->| Pipe2 | -- ------ -- ------- El siguiente programa muestra esta forma de procesamiento: #include /*Procesamiento utilizando varios pipes encadenados*/ main() { int pids[2]; int pipefd1[2]; int pipefd2[2]; int rtn,i; /*1. El proceso padre crea dos pipes. */ rtn= pipe(pipefd1); if (rtn< 0) { perror("No se puede generar el pipe 1"); exit(1); } /*Crear el pipe*/ rtn= pipe(pipefd2); if (rtn< 0) { perror("No se puede generar el pipe 2"); exit(1); } for (i=0;i<3;i++) { rtn=fork(); if (rtn<0) { perror("no puedo generar un proceso hijo"); exit(2); } if (rtn==0) { if (i==0) { /*este proceso cierra extremo de lectura del pipe1*/ close(pipefd1[0]); /*se cierran los extremos del otro pipe*/ close(pipefd2[0]); close(pipefd2[1]); /*y escribe al extremo de escritura del pipe1*/ rtn= write(pipefd1[1],&i,sizeof(i)); if (rtn< 0) { perror("Error al escribir al pipe1"); exit(1); } printf("Proceso %d escribe %d bytes con valor %d\n", getpid(),rtn,i); /*cierra extremo de escritura*/ close(pipefd1[1]); exit(0); } else if ( i==1) { int j; /*este proceso cierra extremo de escritura del pipe1*/ close(pipefd1[1]); /*cierra el extremo de lectura del pipe2*/ close(pipefd2[0]); /*y lee el mensaje del cliente*/ rtn= read(pipefd1[0], &j,sizeof(j)); if (rtn< 0) { perror("Error al leer el pipe1"); exit(1); } printf("Proceso %d lee %d bytes con valor %d\n", getpid(),rtn,j); /*se incrementa en 2 el valor que se recibe*/ j=j+2; /*se escribe al pipe2 el valor de j*/ rtn= write(pipefd2[1],&j,sizeof(j)); if (rtn< 0) { perror("Error al escribir al pipe2"); exit(1); } printf("Proceso %d escribe %d bytes con valor %d\n", getpid(),rtn,j); close(pipefd1[0]); close(pipefd2[1]); exit(0); } else if (i==2) { int k; /*este proceso cierra los extremos del pipe1*/ close(pipefd1[0]); close(pipefd1[1]); /*y el extremo de escritura del pipe2*/ close(pipefd2[1]); /*y lee el mensaje del cliente*/ rtn= read(pipefd2[0], &k,sizeof(k)); if (rtn< 0) { perror("Error al leer el pipe1"); exit(1); } k+=2; printf("Proceso %d lee %d bytes con valor %d\n", getpid(),rtn,k); /*cierra los extremos*/ close(pipefd2[1]); exit(0); } } else { printf("Proceso %d genero hijo %d\n",getpid(),rtn); } } /*Esperar a los hijos*/ for (i=0;i<=2;i++) { printf("Esperando a mis hijos...\n"); wait((int *)0); } exit(0); } Este programa, genera la siguiente salida: Ejemplo4 Proceso 574 escribe 4 bytes con valor 0 Proceso 573 genero hijo 574 Proceso 575 lee 4 bytes con valor 0 Proceso 575 escribe 4 bytes con valor 2 Proceso 573 genero hijo 575 Proceso 576 lee 4 bytes con valor 4 Proceso 573 genero hijo 576 Esperando a mis hijos... Esperando a mis hijos... Esperando a mis hijos... Es posible que se quieran encadenar comandos ya creados utilizando entubamientos. Para esto, se debe combinar redireccionamiento con pipes. El siguiente ejemplo crea un archivo y lo comprime, enviando la salida a un archivo taz. (esto es equivalente a: tar cvf - apuntes src | compress > tema5.taz ) /*Ejemplo5 Pone en un solo archivo varios archivos y directorios y los comprime*/ #include /*este es el comando tar cvf - */ char comando1[]= "/bin/tar"; char arcmd1[]="-cvf"; char arcmd2[]="-"; char comando2[]= "/bin/compress"; /*esto tambien se hubiera realizado con directivas define y sin usar variables globales*/ void juntar(char ** lista,int numArchs) { char * args[1024]; int limit=numArchs+2; int i,rtn; args[0]=comando1; args[1]=arcmd1; args[2]=arcmd2; for (i=0;i< numArchs; i++) { args[i+3]= lista[i]; if ( (i+1 ) >= 1023 ) { limit=1022; break; } } args[limit+1]= (char *) 0; rtn=execv(comando1,args); if (rtn < 0) { perror("Error en execv tar"); exit(5); } } void comprimir( char * archivo) { int rtn; int fd; fd= creat(archivo,0640); if (fd < 0) { perror("Error en creat"); exit(5); } rtn= dup2(fd,1); if (rtn<0) { perror("Error en dup2"); exit(5); } close(fd); rtn= execl(comando2,comando2, (char * )0); if (rtn < 0) { perror("Error en execl compress"); exit(5); } } int main(int argc, char * argv[]) { int rtn,i; int pipefd[2]; char * destino; char ** listaArchivos; if (argc <= 3) { printf("Ejemplo5 destino lista-archivos ...\n"); exit(1); } destino=argv[1]; listaArchivos= &argv[2]; rtn= pipe(pipefd); if (rtn < 0) { perror("Error al crear pipe"); exit(2); } for (i=0;i<=1;i++) { rtn= fork(); if (rtn < 0) { perror("Error en fork"); exit(3); } if (rtn == 0) { if (i==0) { /*direccionar la salida estandar al pipe de escritura*/ rtn= dup2(pipefd[1],1 ) ; if (rtn < 0) { perror("Error al redireccionar pipe"); exit(4); } /*cerrar pipe de escritura*/ /*cerrar el extremo de lectura del pipe*/ close(pipefd[0]); close(pipefd[1]); /*invocar comando tar*/ juntar(listaArchivos,argc-2); exit(0); } else if (i==1) { /*direccionar la entrada estandar al extremo de lectura del pipe*/ rtn= dup2(pipefd[0],0 ) ; if (rtn < 0) { perror("Error al redireccionar pipe"); exit(4); } close(pipefd[0]); /*cerrar el extremo de escritura del pipe*/ close(pipefd[1]); comprimir(destino); exit(0); } } } /*el proceso padre debe liberar los extremos del pipe*/ close(pipefd[0]); /*cerrar el extremo de escritura del pipe*/ close(pipefd[1]); for (i=0;i<2;i++){ wait((int *) 0); } exit(0); } Ejercicios: 1. En base al ejemplo 5, construir un programa que efectue lo equivalente a la siguiente linea de comandos. who | grep usuario | wc -l Es válido utilizar llamadas exec para invocar al comando who, grep y wc. Proporcionar el nombre del usuario como el primer argumento del programa. 2. Dado el ejemplo4, extenderlo para que el valor de la variable i que se escribe en el pipe1, sea retornado con su valor de 4 al proceso hijo 0. Analizar bien este problema ya que implica la creación de 4 pipes. 3. Escribir, basado en el ejemplo5, un programa que descomprima un archivo en formato taz. 4. Investigar las funciones popen y pclose y dar un programa ejemplo de estas funciones. 5. Investigar para que sirve el comando tee. 6. Por qué es tan importante cerrar las entradas del pipe. + Entubamientos con nombre (named pipes) o FIFOs. Con tuberías o pipes, es necesario que la tuberia que se comperte entre procesos sea generada por un proceso padre y después los hijos lo manipulen. Pero es necesario que en algunos casos un proceso diferente, pueda enviar datos a otro proceso, sin que sea necesario que ambos procesos sean del mismo proceso padre. Para esto UNIX proporciona un IPC denominado entubamiento con nombre (named pipe) que permite que un proceso introduzca datos a una tuberia que fue creada por un proceso diferente. Para lograr esto, se debe crear un named pipe o FIFO dentro de un directorio. Cuando algun proceso necesite realizar escritura o lectura de datos, debe abrir este FIFO, proporcionando la ruta del FIFO. La llamada del kernel que permite crear un FIFO es mknod(2). Esta llamada tiene el siguiente formato: #include int mknod(const char * path, mode_t mode, dev_t dev) el primer argumento es una cadena de caracteres que representa la ruta del FIFO, el segundo argumento debe tener un valor entero y normalmente se puede utilizar S_IFIFO combinado con el esquema de permisos, y el tercer argumento se pone con un valor de 0. El siguiente programa crea un FIFO, con permisos de lectura y escritura para el propietario, y lectura para los demas usuarios. #include /*Ejemplo6, crea un named PIPE */ main(int argc, char * argv[]) { int rtn; if (argc <= 1) { printf("Ejemplo6 rutaFIFO \n"); exit(1); } /*Indica al kernel que debe crear un archivo de tipo PIPE El segundo parametro es una mascar de bits. Se efectua una operacion OR de bits para indicar que es un FIFO y que los permisos son de lectura/escritura propietario, lectura para grupo y lectura para otros */ rtn= mknod(argv[1],S_IFIFO | 0644, 0); if (rtn < 0) { perror("Error al crear el named pipe"); exit(1); } exit(0); } Este programa se ejecuta como Ejemplo6 /tmp/namedpipe Es necesario notar que el Named pipe no desaparece, a diferencia de un PIPE, su referencia queda en el sistema de archivos. Checar al correr el Ejemplo6 el contenido de /tmp, debe aperecer un archivo namedpipe con la siguiente mascar de permisos: prw-r-r- la letra p indica que es un named pipe. Un comando del sistema operativo que efectua el mismo procedimiento que el Ejemplo6 es mknod(1M). Por ejemplo: mknod /tmp/namedpipe1 Dado que el esquema de tuberias sirve para efectuar procesamiento de tipo cliente/servidor, es necesario indicar como asociar un proceso servidor y cliente a un named pipe. Para que un proceso sea servidor, según la filosofía adoptada en este capítulo, puede ser un servidor que lea un mensaje y no regrese respuesta o uno que si regrese respuesta. En caso de que se un proceso servidor que regrese respuesta se deben seguir los siguientes pasos: 1. El proceso servidor debe crear el named pipe con la llamada mknod(2). 2. El procesos servidor debe abrir de solo lectura al named pipe. 3. El proceso servidor se queda bloqueado en la llamada open hasta que un proceso cliente abra al named pipe para escritura. 4. El proceso servidor, al desbloquearse, lee el mensaje del named pipe. 5. Procesa el mensaje. 6. Cierra el named pipe con una llamada close. 7. Destruye al named pipe con la llamada unlink. El siguiente ejemplo muestra este tipo de logica. #include #include #include main(int argc, char * argv[]) { int rtn; int fd; int nbytes,j; char mensaje[128]; if (argc <= 1) { printf("Ejemplo7 rutaFIFO \n"); exit(1); } /*Indica al kernel que debe crear un archivo de tipo PIPE El segundo parametro es una mascar de bits. Se efectua una operacion OR de bits para indicar que es un FIFO y que los permisos son de lectura/escritura propietario, lectura para grupo y lectura para otros */ rtn= mknod(argv[1],S_IFIFO | 0644, 0); if (rtn < 0) { perror("Error al crear el named pipe"); exit(1); } /*obtener el descriptor de archivo de lectura*/ printf("PID %d Esperando el mensaje\n", getpid()); fd= open(argv[1],O_RDONLY); if (fd < 0) { perror("Error al abrir extremo de lectura del pipe"); } printf("PID %d un proceso cliente tiene un mensaje, a leer\n", getpid()); /*leer el named pipe*/ nbytes= read(fd,mensaje,128); if (nbytes<=0) { perror("Error al leer el named pipe"); exit(2); } mensaje[nbytes]=0; printf("Leyendo %d bytes %s\n",nbytes,mensaje); for(j=0;j /tmp/gus y entonces, la llamada open se desbloquea, ya que un proceso abre para escritura al named pipe, dando este mensaje. PID 463 un proceso cliente tiene un mensaje, a leer cuando en el comando cat se pone el mensaje hola el Ejemplo7 contesta Leyendo 5 bytes hola proceso 463 convierte mensaje HOLA Para codificar el proceso cliente del Ejemplo7. Este procesos debe abrir unicamente el named pipe de lectura, como si fuera un archivo normal. #include #include #include main(int argc, char * argv[]) { int fd; int nbytes,j; char mensaje[]="hola\n"; if (argc <= 1) { printf("Ejemplo7 rutaFIFO \n"); exit(1); } /*Abrir de escritura al pipe*/ fd=open(argv[1],O_WRONLY); if (fd< 0) { perror("Error al abrir el named pipe"); exit(2); } nbytes=write(fd,mensaje,strlen(mensaje)); if (nbytes < 0) { } printf("PID %d escribiendo %d bytes",getpid(), nbytes); close(fd); exit(0); } Para probar este ejemplo, es necesario ejecutar el programa Ejemplo7 primero Ejemplo7 /tmp/gus PID 502 Esperando el mensaje y luego ejecutar el Ejemplo8 Ejemplo8 /tmp/gus PID 507 escribiendo 5 bytes lo cual lleva que el Ejemplo7 responda: PID 502 un proceso cliente tiene un mensaje, a leer Leyendo 5 bytes hola proceso 502 convierte mensaje HOLA Se puede ejectuar el Ejemplo8 sin necesidad del Ejemplo7 y utilizando el comando cat y direccionamiento, para esto, ejecutar los siguientes pasos: mknod /tmp/gus p Ejemplo8 /tmp/gus & cat < /tmp/gus el primer comando crea un named pipe. el segundo comando manda a segundo plano al Ejemplo8, este se queda bloqueado en llamada open hasta que otro proceso abra el archivo para lectura. luego entonces, el tercer comando que es un cat que lee su entradad del named pipe, desbloquea al Ejemplo8. Es válido que múltiples procesos Ejemplo8 traten de escribir información, por ejemplo, teclear 5 veces Ejemplo8 /tmp/gus & y luego cat < /tmp/gus esto lleva a que el comando cat mande a imprimir 5 veces hola. De lo anterior se desprende que: 1. Si un proceso abre un named pipe de lectura, este se queda bloqueado en la llamada open hasta que otro proceso abra al named pipe de escritura. 2. Si un proceso abre un named pipe de escritura, este se queda bloquedo en la llamada open hasta que otro proceso efectua un open de lectura sobre el named pipe. 3. Se puede anular el comportamiento 1 y 2 si la llamada open se invoca con la bandera O_NONBLOCK. El punto 3 se puede aplicar cuando un proceso no quiera esperar a que otro proceso escriba o lea el named pipe. Bajo estas condiciones, el proceso, cuando quiera leer o escribir un dato, debe realizaro con escrutinio o polling. #include #include #include #include /*Ejemplo9, realiza una lectura de un pipe, pero sin que se bloquee en un open, sino que utiliza escrutinio o polling con la llamada select */ main(int argc, char * argv[]) { int rtn; int fd; int nbytes,j; char mensaje[128]; fd_set readset; /*esta variable se utiliza para la llamada select*/ if (argc <= 1) { printf("Ejemplo9 rutaFIFO \n"); exit(1); } /*Indica al kernel que debe crear un archivo de tipo PIPE El segundo parametro es una mascar de bits. Se efectua una operacion OR de bits para indicar que es un FIFO y que los permisos son de lectura/escritura propietario, lectura para grupo y lectura para otros */ rtn= mknod(argv[1],S_IFIFO | 0644, 0); if (rtn < 0) { perror("Error al crear el named pipe"); exit(1); } printf("PID %d Abriendo el named PIPE\n", getpid()); /*obtener el descriptor de archivo de lectura y no bloquerase*/ fd= open(argv[1],O_RDONLY|O_NONBLOCK); if (fd < 0) { perror("Error al abrir extremo de lectura del pipe"); exit(2); } /*Realizar escrutinio o polling*/ printf("PID %d Esperando el mensaje con un select...\n", getpid()); FD_ZERO(&readset); FD_SET(fd,&readset); rtn=select(fd+1,&readset,0,0,0); if (rtn< 0) { perror("Error en escrutinio"); exit(2); } printf("PID %d un proceso cliente tiene un mensaje, a leer\n", getpid()); /*leer el named pipe*/ nbytes= read(fd,mensaje,128); if (nbytes<=0) { perror("Error al leer el named pipe"); exit(2); } mensaje[nbytes]=0; printf("Leyendo %d bytes %s\n",nbytes,mensaje); for(j=0;j #include #include main(int argc, char * argv[]) { int rtn; int fd[2]; int nbytes,j; char mensaje[128]; if (argc <= 2) { printf("Ejemplo10 rutaFIFOIn rutaFIFOOut \n"); exit(1); } /*Indica al kernel que debe crear un archivo de tipo PIPE El segundo parametro es una mascara de bits. Se efectua una operacion OR de bits para indicar que es un FIFO y que los permisos son de lectura/escritura propietario, lectura para grupo y lectura para otros */ rtn= mknod(argv[1],S_IFIFO | 0644, 0); if (rtn < 0) { perror("Error al crear el named pipe de lectura"); exit(1); } /*Indica al kernel que debe crear un archivo de tipo PIPE El segundo parametro es una mascara de bits. Se efectua una operacion OR de bits para indicar que es un FIFO y que los permisos son de lectura/escritura propietario, lectura para grupo y lectura para otros */ rtn=mknod(argv[2],S_IFIFO | 0644,0); if (rtn < 0) { perror("Error al crear el named pipe de escritura"); exit(1); } /*obtener el descriptor de archivo de lectura*/ printf("PID %d Bloquado en open %s para lectura\n", getpid(),argv[1]); fd[0]= open(argv[1],O_RDONLY); if (fd < 0) { perror("Error al abrir extremo de lectura del pipe"); } printf("PID %d Bloquado en open %s para escritura\n", getpid(),argv[2]); fd[1]=open(argv[2],O_WRONLY); printf("PID %d un proceso cliente tiene un mensaje, a leer\n", getpid()); /*leer el named pipe*/ nbytes= read(fd[0],mensaje,128); if (nbytes<=0) { perror("Error al leer el named pipe"); exit(2); } mensaje[nbytes]=0; printf("Leyendo %d bytes %s\n",nbytes,mensaje); for(j=0;j #include #include main(int argc, char * argv[]) { int fd[2]; int nbytes,j; char mensaje[]="hola\n"; if (argc <= 2) { printf("Ejemplo11 rutaFIFOin rutaFIFOout \n"); exit(1); } /*Abrir de escritura al pipe*/ fd[0]=open(argv[1],O_WRONLY); if (fd[0]< 0) { perror("Error al abrir el named pipe de escritura"); exit(2); } fd[1]=open(argv[2],O_RDONLY); if (fd[1]< 0) { perror("Error al abrir el named pipe de lectura"); exit(2); } nbytes=write(fd[0],mensaje,strlen(mensaje)); if (nbytes < 0) { perror("Error al escribir el named pipe de escritura"); exit(3); } printf("PID %d escribiendo %d bytes\n",getpid(), nbytes); nbytes=read(fd[1],mensaje,128); if (nbytes< 0) { perror("Error al leer el named pipe de lectura"); exit(4); } mensaje[nbytes]=0; printf("PID %d leyendo %d bytes %s\n",getpid(), nbytes,mensaje); close(fd[0]); close(fd[1]); exit(0); } Es necesario notar que el orden con el que se abren los named pipes es importante, ya que si no se sigue el orden necesario, puede llevar a una condición de deadlock. Ejercicios. 1. Entender el punto 3.9 del libro UNIX programación práctica, especificamente en la discusión de apertura del named pipe con la llamada O_NONBLOCK. 2. Investigar que hace la funcion mkfifo(3C). 3. Investigar el comando mkfifo(1M) + Procesamiento asíncrono. Señales (Signals). Procesamiento asíncrono se refiere al hecho de que un proceso servidor recibe una petición de un cliente, pero el proceso servidor no tiene que estar a la espera de un mensaje, implicando esto que el proceso servidor puede realizar otro tipo de actividad. La forma como un proceso asíncrono se puede implantar es recurriendo a la idea de un evento. Un evento indica la ocurrencia de un hecho. Esto lleva a un modelo conocido como "publicar/suscribir" (publish & suscribe). En este modelo, el proceso que se encarga de efectuar una acción ante un evento, es el proceso servidor y se suscribe o interesa a la ocurrencia de un evento. Por otra parte, el o los procesos clientes generan un evento que publican a los interesados. UNIX permite implantar esto, utilizando el concepto de señales o signals. Una señal es una notificación por software a un proceso de la ocurrencia de un evento. Una señal es generada cuando ocurre el evento que causo la señal. Las señales son conocidas como interrupciones de software. En UNIX se puede dar una señal que es generada de un proceso a otro, o el kernel puede generar una señal a un proceso (por ejemplo para indicar que cierto dispositivo tiene información o que un error ha ocurrido dentro del kernel). + Llamada sigaction. En UNIX se puede implantar el modelo publish & suscribe. Con este fin, un proceso se debe inscribir o interesarse en la ocurrencia de una señal, indicando la acción que ejecuta ante una señal. Para poder realizar esto, se debe utilizar la llamada al kernel sigaction(2), que tiene la siguiente estructura: #include int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); Esta llamada tiene tres parámetros. El primer parámetro es el número de señal a esperar y que significa lo siguiente: Name Value Default Event ________________________________________________________________ SIGHUP 1 Exit Hangup (see termio(7I)) SIGINT 2 Exit Interrupt (see termio(7I)) SIGQUIT 3 Core Quit (see termio(7I)) SIGILL 4 Core Illegal Instruction SIGTRAP 5 Core Trace or Breakpoint Trap SIGABRT 6 Core Abort SIGEMT 7 Core Emulation Trap SIGFPE 8 Core Arithmetic Exception SIGKILL 9 Exit Killed SIGBUS 10 Core Bus Error SIGSEGV 11 Core Segmentation Fault SIGSYS 12 Core Bad System Call SIGPIPE 13 Exit Broken Pipe SIGALRM 14 Exit Alarm Clock SIGTERM 15 Exit Terminated SIGUSR1 16 Exit User Signal 1 SIGUSR2 17 Exit User Signal 2 SIGCHLD 18 Ignore Child Status Changed SIGPWR 19 Ignore Power Fail or Restart SIGWINCH 20 Ignore Window Size Change SIGURG 21 Ignore Urgent Socket Condition SIGPOLL 22 Exit Pollable Event (see streamio(7I)) SIGSTOP 23 Stop Stopped (signal) SIGTSTP 24 Stop Stopped (user) (see termio(7I)) SIGCONT 25 Ignore Continued SIGTTIN 26 Stop Stopped (tty input) (see termio(7I)) SIGTTOU 27 Stop Stopped (tty output) (see termio(7I)) SIGVTALRM 28 Exit Virtual Timer Expired SIGPROF 29 Exit Profiling Timer Expired SIGXCPU 30 Core CPU time limit exceeded (see getrlimit(2)) SIGXFSZ 31 Core File size limit exceeded (see getrlimit(2)) SIGWAITING 32 Ignore Concurrency signal reserved by threads library SIGLWP 33 Ignore Inter-LWP signal reserved by threads library SIGFREEZE 34 Ignore Check point Freeze SIGTHAW 35 Ignore Check point Thaw SIGCANCEL 36 Ignore Cancellation signal reserved by threads library SIGRTMIN * Exit First real time signal (SIGRTMIN+1) * Exit Second real time signal ... (SIGRTMAX-1) * Exit Second-to-last real time signal SIGRTMAX * Exit Last real time signal Cada señal tiene un significado diferente. Por ejemplo, SIGKILL significa que el proceso que la recibe debe concluir su procesamiento y salir inmediatamente. El segundo parámetro es es un apuntador a una estructura de datos denominada struct sigaction. En esta estructura de datos se debe indicar el comportamiento o accion a tomar ante la acción especificada por el primer parámetro. Esta estructura de datos tiene los siguientes campos void (*sa_handler)(); --> Apuntador a Funcion que maneja la señal sigset_t sa_mask; --> Señales a bloquear durante el procesamiento del evento int sa_flags; ---> Banderas El tercer parametro es una estructura de tipo sigaction y que se llena con los valores del anterior struct sigacion utilizado. Un ejemplo de como utilizar señales es un programa que al recibir un CTL-C no interrumpa su procesamiento. #include void anulaCtlC(int signo) { /*esta funcion se llama cuando se recibe un CTRL-C*/ printf("Recibo señal %d\n",signo); } main() { struct sigaction nuevaAccion; int rtn; /*Llenar la estructura sigaction*/ nuevaAccion.sa_handler= anulaCtlC; /*la funcion manejadora*/ sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/ nuevaAccion.sa_flags=0; /*no hay banderas*/ /*ante un CTRL-C no se muere el proceso*/ rtn=sigaction(SIGINT,&nuevaAccion,(struct sigaction *)0); if (rtn < 0) { perror("Error al registar manejador de signal"); exit(1); } /*se queda procesando para siempre*/ printf("Procesando \n"); while (1); } Probar Ejemplo12 PID 1020 Procesando ^CRecibo señal 2 ^CRecibo señal 2 ^CRecibo señal 2 ^CRecibo señal 2 Tambien se puede enviar la señal 2 utilizando el comando kill. Se debe conocer el PID de Ejemplo12. kill -2 1020 Las únicas señales que no pueden manipularse son SIGKILL y SIGSTOP. El siguiente ejemplo lo muestra. #include void anulaKill(int signo) { /*esta funcion se llama cuando se recibe un CTRL-C*/ printf("Recibo señal %d\n",signo); } main() { struct sigaction nuevaAccion; int rtn; /*Llenar la estructura sigaction*/ nuevaAccion.sa_handler= anulaKill; /*la funcion manejadora*/ sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/ nuevaAccion.sa_flags=0; /*no hay banderas*/ /*ante un kill -9 no se muere el proceso*/ rtn=sigaction(SIGKILL,&nuevaAccion,(struct sigaction *)0); if (rtn < 0) { perror("Error al registar manejador de signal"); exit(1); } /*se queda procesando para siempre*/ printf("PID %d Procesando \n",getpid()); while (1); } Este programa al ser ejecutado, arroja el siguiente error: Ejemplo13 Error al registar manejador de signal: Invalid argument ya que la funcion sigaction, si recibe un SIGKILL como primer parámetro, no tiene utilidad, ya que no se puede manejar. Una señal puede ser ignorada, implicando esto, que no se llama a un manejador y por tanto no se procesa. Esto se logra asignado al campo sa_handler el valor SIG_IGN, como lo muestra el siguiente ejemplo: #include void anulaCtrlC(int signo) { /*esta funcion se llama cuando se recibe un CTRL-C*/ printf("Recibo señal %d\n",signo); } main() { struct sigaction nuevaAccion; int rtn; /*Llenar la estructura sigaction*/ nuevaAccion.sa_handler= SIG_IGN; /*la funcion manejadora no existe, solo se ignora*/ sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/ nuevaAccion.sa_flags=0; /*no hay banderas*/ /*ante un Ctrl-C no se muere el proceso*/ rtn=sigaction(SIGINT,&nuevaAccion,(struct sigaction *)0); if (rtn < 0) { perror("Error al registar manejador de signal"); exit(1); } /*se queda procesando para siempre*/ printf("PID %d Procesando \n",getpid()); while (1); } Si se desea hacer un programa que envie una señal a un proceso, se debe utilizar la llamada kill. El siguiente ejemplo muestra como un programa realiza una división entre cero, lo que provoca que el kernel genere una señal SIGFPE, pero el programa cacha esta excepción y sale con un estado mayor que 0. #include void division(int signo) { printf("Division incorrecta signo %d\n",signo); exit(1); } main() { struct sigaction nuevaAccion; int rtn,x; char * ptr=0; /*Llenar la estructura sigaction*/ nuevaAccion.sa_handler= division; /*la funcion manejadora no existe, solo se ignora*/ sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/ nuevaAccion.sa_flags=0; /*no hay banderas*/ /*ante un Ctrl-C no se muere el proceso*/ rtn=sigaction(SIGFPE,&nuevaAccion,(struct sigaction *)0); if (rtn < 0) { perror("Error al registar manejador de signal"); exit(1); } x=1/0; } El siguiente ejemplo muestra un abanico de procesos, y como el proceso padre recibe señal de que un proceso hijo cambia de estado SIGCHLD. #include int contador=0; void cambioHijo(int sig) { printf("Cambio un hijo signo %d contador %d\n",sig,contador); contador++; } main() { int i; struct sigaction nuevaAccion; int rtn; /*Llenar la estructura sigaction*/ nuevaAccion.sa_handler=cambioHijo ; /*la funcion manejadora no existe, solo se ignora*/ sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/ nuevaAccion.sa_flags=0; /*no hay banderas*/ rtn=sigaction(SIGCHLD,&nuevaAccion,(struct sigaction *)0); if (rtn < 0) { perror("Error al registar manejador de signal"); exit(1); } for (i=0;i< 10; i++) { int pid; pid=fork(); if (pid < 0) { perror("Error en fork"); exit(1); } else if (pid == 0) { printf("PID %d valor de i=%d\n",getpid(),i); exit(0); } else { printf("Hijo con pid %d creado\n",pid); } } printf("Procesando...\n"); while (1) { if (contador>=10) break; } printf("Acabaron todos mis hijos\n"); exit(0); } Ejercicio. 1. Investigar la funcion signal. 2. Hacer un programa que cache la excepcion de accesar un apuntador nulo. + Publish & Suscribe. El modelo publish & suscribe consiste en una forma de procesamiento en la cual uno o mas procesos se muestran interesados en un evento o se suscriben y uno o mas procesos generan estos eventos. En este modelo, el proceso que genere un evento no sabe a quien va dirigido. Esto es a diferencia del punto anterior, en el cual se tiene que indicar un identificador de proceso. Además, este modelo plantea que el evento llega al proceso servidor, pero con la llamada sigaction no se obtiene ningún argumento de entrada para el proceso servidor. Para poder implantar este modelo, se debe recurrir a una combinación de señales y tuberias con nombre. Para esto, existirá un proceso servidor, el cual estará a la espera de que uno o más procesos se suscriban a un evento. Cuando otro conjunto de procesos desee enviar información a un grupo de procesos, debe referenciar al proceso servidor e indicar el evento y la información del evento. De ahi, el proceso servidor comunicará por medio de un pipe, la información a los procesos cliente. El Ejemplo17, Ejemplo18, Ejemplo19 y Ejemplo20 muestran este modelo. /*Proceso mediador para manipuolar comunicacion publish & suscribe*/ #include #include #include #include #include #include #define PIPESUSCRIBE "/tmp/psuscribe" #define PIPEPUBLISH "/tmp/ppublish" #define MAXLEN 1024 #define MAXSUSCRITOS 128 /*numero suscriptores*/ int nSuscriptores; /*variable global para definir extremos de comunicacion entre proceso suscriptor y publicador */ int internalPipe[2]; /*estructura de datos que define mensaje del suscriptor*/ struct mensajeSuscriptor { int pid; /*indica el PID del proceso suscriptor*/ }; struct mensajePublicador { char mensaje[MAXLEN]; /* se escribe un mensaje de cualquier tipo de datos*/ }; struct suscrito { int pid; /* identificador de proceso*/ char pipe[128]; /*pipe donde toma el mensaje del publicador*/ }; struct suscrito lista[MAXSUSCRITOS]; /*lista suscritos*/ void finalizaPublicador(int signo) { int i; printf("Recibiendo signal %d\n",signo); unlink(PIPEPUBLISH); for (i=0;i #include #include #include #define PIPESUSCRIBE "/tmp/psuscribe" struct mensajeSuscriptor { int pid; /*indica el PID del proceso suscriptor*/ }; void suscribe(char * argv[]) { int fd; int nbytes; int rtn; struct mensajeSuscriptor mensaje; fd=open(PIPESUSCRIBE,O_WRONLY); mensaje.pid=getpid(); nbytes= write(fd,&mensaje,sizeof(mensaje)); if (nbytes <0) { printf("error al suscribir el proceso\n"); } else { printf("Inscrito proceso %d",getpid()); } close(fd); printf(argv[0]); rtn=execvp(argv[0],&argv[0]); if (rtn< 0) { perror("error al ejecutar programa"); exit(2); } } main(int argc, char * argv[]) { if ( argc <=1) { printf("Ejemplo18 programa"); exit(1); } suscribe(&argv[1]); } #include #include #include #include int i=0; void procesarEvento(int signo) { int fd,nbytes; char buffer[128]; char nombrePipe[128]; printf("Signal Evento %d\n",signo); printf("Valor de i=%d\n",i); sprintf(nombrePipe,"/tmp/p%d",getpid()); fd= open(nombrePipe,O_RDONLY); if (fd < 0) { perror("No puedo abrir pipe de mensajes"); } else { nbytes=read(fd,buffer,128); if (nbytes < 0) { perror("No puedo abrir pipe de mensajes"); } else { buffer[nbytes]=0; printf("%s\n",buffer); } close(fd); } } main() { struct sigaction nuevaAccion; int rtn,x; char * ptr=0; /*Llenar la estructura sigaction*/ nuevaAccion.sa_handler= procesarEvento; sigemptyset(&nuevaAccion.sa_mask); /*no se bloque ninguna sennal*/ nuevaAccion.sa_flags=0; /*no hay banderas*/ rtn=sigaction(SIGTERM,&nuevaAccion,(struct sigaction *)0); if (rtn < 0) { perror("Error al registar manejador de signal"); exit(1); } while (1) { i++; } } #include #include #include #include #define PIPEPUBLISH "/tmp/ppublish" #define MAXLEN 1024 struct mensajePublicador { char mensaje[MAXLEN]; /* se escribe un mensaje de cualquier tipo de datos*/ }; main(int argc, char *argv[]) { int fd; struct mensajePublicador mensaje; int nbytes; if (argc <=1) { strcpy(mensaje.mensaje,"hola mundo\n"); } else { strcpy(mensaje.mensaje,argv[1]); } fd= open(PIPEPUBLISH,O_WRONLY); if (fd < 0) { perror("Error al abrir pipe publicacion"); exit(2); } nbytes=write(fd,&mensaje,sizeof(struct mensajePublicador)); if (nbytes< 0) { perror("no se puede enviar el mensaje"); } else { printf("Mensaje enviado\n"); } close(fd); exit(0); } Ejercicio: 1. Explicar el funcionamiento de los Ejemplos 17 a 20. 2. Estos ejemplos hacen uso de estructuras de datos comunes. Definir en un archivo de encabezado (include) estas estructuras de datos. 3. Pensar en un juego de gato, entre dos jugadores. Plantear como se tendria que rediseñar el Ejemplo17 al Ejemplo20. * 4. Implantar el ejemplo3. + Sockets. Los modelos anteriores se pueden considerar de muy bajo nivel. UNIX proporciona un mecanismo para facilitar la comunicacion entre procesos denominado sockets. Un socket es un mecanismo de comunicacion entre procesos que permite implantar procesos cliente y servidor. UNIX permite comunicar procesos que estan ejecutandose en la misma computadora, o entre computadoras remotas (esto se vera en el tema 7). Cuando la comunicacion se establece entre procesos que residen en la misma computadora, se conocen como un socket UNIX. Un socket es proporcionado por el kernel. Para crear un socket se debe utilizar la llamada al kernel socket(2) #include #include int socket(int domain, int type, int protocol); En el caso de un socket local los parametros son los siguiente: domain - AF_UNIX type - SOCK_STREAM , tratar el socket como un archivo protocol - 0 , se deja al kernel configurar el tipo de protocolo Esta llamada , si tiene exito, retorna un descriptor de archivo, de lo contrarioun numero negativo. Tanto un proceso cliente y servidor deben invocar la llamada anterior. En el caso de un proceso servidor se deben aplicar los siguientes pasos: + Aplicar la llamada bind sobre el socket. #include #include int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); El primer parametro es el descriptor obtenido por la llamada socket. El segundo parametro es un apuntador a una estructura generica para representar sockets. En el caso de un socket UNIX se debe incluir #include #define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ }; Esta estructura de datos se debe llenar de la siguiente manera: bzero( (char *) &serv_addr , sizeof(serv_addr) ); /*inicar a cero*/ serv_addr.sun_family = AF_UNIX; strcpy(serv_addr.sun_path, argv[1] ); /*indicar donde esta el socket dentro del sistema de archivos*/ El tercer parametro permite indicar la longitud de la estructura anterior, con valores indicados. Se debe aplicar de la siguiente forma addrlen = strlen(serv_addr.sun_path) + sizeof (serv_addr.sun_family); + Aplicar la llamada listen #include int listen(int s, int backlog); Permite indicar al servidor que debe escuchar peticiones de los clientes. El primer parametro es el descriptor de archivo El segundo parametro indica el numero maximo de clientes que puede aceptar simultaneamente + Esperar peticiones de clientes, usando la llamada accept #include #include int accept(int s, struct sockaddr *addr, socklen_t *addrlen); Esta llamada permite obtener un nuevo socket entre el servidor y el proceso cliente. El primer parametro es el descriptor retornado por la llamada socket. El segundo y tercer parametro se deben utilizar basandose en una estructura de datos de tipo sockaddr_un, como en el caso de la llamada bind. Si esta llamada tiene exito, se obtiene un descriptor de archivo que es el socket entre servidor y cliente. Sobre este socket se pueden aplicar llamadas relacionadas con archivos + Por cada cliente se crea un nuevo socket, aplicar operaciones sobre dicho socket y cerrarlo. + Cerrar el socket principal cuando se concluya la vida del servidor. Para cerrar un socket se debe utilizar la llamada close. El siguiente ejemplo muestra el proceso descrito: #include #include #include #define MAXBUF 1024 main(int argc, char *argv[]) { int sockfd; /*descriptor de socket*/ struct sockaddr_un serv_addr; /*estructura para configurar socket*/ struct sockaddr_un cli_addr; /*estructura del cliente del socket*/ int servlen; /*para calcuar la longitud de la estructura */ int clilen; int newsockfd; char buffer[MAXBUF]; int sz,i; if ( argc <= 1 ) { printf("Ejemplo21 rutasocket\n"); exit(1); } sockfd = socket(AF_UNIX,SOCK_STREAM,0); if (sockfd < 0 ) { perror("servidor no puede abrir socket"); exit(1); } printf("sockfd=%d\n",sockfd); bzero( (char *) &serv_addr , sizeof(serv_addr) ); /*inicar a cero*/ serv_addr.sun_family = AF_UNIX; strcpy(serv_addr.sun_path, argv[1] ); /*indicar donde esta el socket*/ servlen = strlen(serv_addr.sun_path) + sizeof (serv_addr.sun_family); if ( bind(sockfd, (struct sockaddr *) &serv_addr, servlen) < 0 ) { perror("servidor no aplica bind"); exit(2); } listen(sockfd,5); /*escuchar 5 peticiones*/ while (1) { clilen = sizeof(cli_addr); newsockfd=accept(sockfd, (struct sockaddr *) &cli_addr , &clilen ); if ( newsockfd < 0 ) { perror("servidor error al recibir peticion del cliente"); continue; } sz =read(newsockfd,buffer,MAXBUF); if (sz < 0 ) { perror("servidor error en lectura"); close(newsockfd); continue; } if ( sz > MAXBUF ) sz = MAXBUF - 1; buffer[sz] = (char) 0; printf("mensaje %s\n",buffer); for ( i=0; i #include int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); La estructura a utilizar para el segundo y tercer parametro debe ser de tipo sockaddr_un, indicando la ruta donde se localiza el socket del proceso servidor. + Aplicar operaciones de archivos sobre el socket. + Cerrar el socket. #include #include #include #define MAXBUF 1024 main(int argc, char *argv[]) { int sockfd; /*descriptor de socket*/ struct sockaddr_un serv_addr; /*estructura para configurar socket*/ int servlen; int sz; char msg[] = "hola mundo"; char buffer[MAXBUF]; if ( argc <= 1 ) { printf("Ejemplo21 rutasocket\n"); exit(1); } sockfd = socket(AF_UNIX,SOCK_STREAM,0); if (sockfd < 0 ) { perror("cliente no puede abrir socket"); exit(1); } printf("sockfd=%d\n",sockfd); bzero( (char *) &serv_addr , sizeof(serv_addr) ); /*inicar a cero*/ serv_addr.sun_family = AF_UNIX; strcpy(serv_addr.sun_path, argv[1] ); /*indicar donde esta el socket*/ servlen = strlen(serv_addr.sun_path) + sizeof (serv_addr.sun_family); if ( connect(sockfd,(struct sockaddr *) &serv_addr, servlen) < 0 ) { perror("cliente no puede abrir socket"); exit(2); } if ( write(sockfd,msg,strlen(msg)) < 0 ) { perror("cliente error al escribir el mensaje"); close(sockfd); exit(3); } sz = read(sockfd,buffer,MAXBUF); if ( sz < 0 ) { perror("cliente error al leer el mensaje"); close(sockfd); exit(4); } if ( sz > MAXBUF ) sz = MAXBUF - 1; buffer[sz] = (char) 0; printf("%s\n",buffer); close(sockfd); exit(0); } Ejercicios. 1. Implantar un proceso servidor utilizando llamadas fork. 2. Construir un servicio de "CHAT" local entre procesos UNIX. 3. Utilizando un servicio de base de datos disponible en el sistema UNIX, construir un programa cliente/servidor que permita aplicar consultas, modificaciones y borrado a la base de datos. El cliente debe ser ligero, es decir, no habla directamente con la base de datos, sino que utiliza el servidor que establece una conexion con la base de datos. 4. En cualquier sistema cliente/servidor se procura dar un conjunto de recursos preasignados. Por ejemplo, abrir un numero fijo de conexiones a bases de datos y administrar su uso. Esto se conoce como un pool de conexiones. Se podrian utilizar sockets con este fin. Ejercicios. + Investigar como se implante en Windows NT el modelo de comunicación de procesos