Centro de Estudios NOVA – Cartagena 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Para poder realizar la conexión entre ambos programas son necesarias varias cosas: Dirección IP del servidor. Servicio que queremos crear / utilizar: Estos números son enteros normales y van de 1 a 65535. Los número bajos, desde 1 a 1023 están reservados para servicios Centro de Estudios NOVA – Cartagena 868-97-38-38 habituales de los sistemas operativos (www, ftp, mail, ping, etc). El resto están a disposición del programador EL SERVIDOR • Apertura de un socket, mediante la función socket(). Esta función devuelve un descriptor de fichero normal, como puede devolverlo open(). La función socket() no hace absolutamente nada, salvo devolvernos y preparar un descriptor de fichero que el sistema posteriormente asociará a una conexión en red. • Avisar al sistema operativo de que hemos abierto un socket y queremos que asocie nuestro programa a dicho socket. Se consigue mediante la función bind(). El sistema todavía no atenderá a las conexiones de clientes, simplemente anota que cuando empiece a hacerlo, tendrá que avisarnos a nosotros. Es en esta llamada cuando se debe indicar el número de servicio al que se quiere atender. • Avisar al sistema de que comience a atender dicha conexión de red. Se consigue mediante la función listen(). A partir de este momento el sistema operativo anotará la conexión de cualquier cliente para pasárnosla cuando se lo pidamos. Si llegan clientes más rápido de lo que somos capaces de atenderlos, el sistema operativo hace una "cola" con ellos y nos los irá pasando según vayamos pidiéndolo. • Pedir y aceptar las conexiones de clientes al sistema operativo. Para ello hacemos una llamada a la función accept(). Esta función le indica al sistema operativo que nos dé al siguiente cliente de la cola. Si no hay clientes se quedará bloqueada hasta que algún cliente se conecte. • Escribir y recibir datos del cliente, por medio de las funciones write() y read(), que son exactamente las mismas que usamos para escribir o leer de un fichero. Obviamente, tanto cliente como servidor deben saber qué datos esperan recibir, qué datos deben enviar y en qué formato. Puedes ver cómo se pueden poner de acuerdo en estos mensajes en el apartado de mensajes. • Cierre de la comunicación y del socket, por medio de la función close(), que es la misma que sirve para cerrar un fichero. EL CLIENTE Los pasos que debe seguir un programa cliente son los siguientes: • Apertura de un socket, como el servidor, por medio de la función socket() • Solicitar conexión con el servidor por medio de la función connect(). Dicha función quedará bloqueada hasta que el servidor acepte nuestra conexión o bien si no hay servidor en el sitio indicado, saldrá dando un error. En esta llamada se debe facilitar la dirección IP del servidor y el número de servicio que se desea. • Escribir y recibir datos del servidor por medio de las funciones write() y read(). • Cerrar la comunicación por medio de close(). Centro de Estudios NOVA – Cartagena 868-97-38-38 sockfd = socket ( int dominio, int tipo, int protocolo ); sockfd Es el descriptor de socket devuelto. Luego se utilizará para conectarse, recibir conexiones, enviar y recibir datos, etc. Retorna un descriptor de socket, que es tipo int. Si hubo algún error, socket() retorna -1 y la variable global errno se establece con un valor que indica el error que se produjo ( ver man 3 perror). Dominio El dominio de comunicación nos dice donde se encuentran los procesos que se van a intercomunicar. Si los procesos están en el mismo sistema, el dominio de comunicación será AF_UNIX, si los procesos están en distintos sistemas y estos se hallan unidos mediante una red TCP/IP, el dominio de comunicación será AF_INET. Cabe aclarar que existen otros dominios de comunicación. Los sockets no se han diseñado solamente para TCP/IP. La idea original fue que se usase la misma interfaz también para distintas familias de protocolos. En esta introducción solo trataremos el dominio AF_INET. Algunos dominios: • • • AF_INET ( unidos mediante una red TCP/IP). AF_UNIX (en el mismo sistema). Otros dominios. Para este tutor siempre será AF_INET. tipo Podrá ser SOCK_STREAM o SOCK_DGRAM o SOCK_RAW. protocolo 0 (cero, selecciona el protocolo más apropiado). int bind(int sockfd, struct sockaddr *my_addr, int addrlen) El socket se crea sin nombre, debemos asignarle uno para poder recibir conexiones. bind () se utiliza para darle un nombre al socket, osea una dirección IP y número de puerto del host local por donde escuchará, al especificar una IP del host local le estamos diciendo Centro de Estudios NOVA – Cartagena 868-97-38-38 por cual interfaz física escuchará (el sistema puede tener varias interfaces ethernet, ppp, etc). Es necesario llamar a bind() cuando se está programando un servidor. Cuando se está programando un cliente generalmente no se utiliza esta función, el kernel le asignará al socket la dirección IP y número de puerto disponible al llamar a ciertas funciones, por ejemplo cuando llamamos a connect() para conectarnos con un sistema remoto. En el servidor es necesario llamar a bind() debido a que el número de puerto debe ser conocido para que los clientes puedan conectarse. Por ejemplo si estamos programando un servidor de telnet debemos llamar a bind() para asignarle al socket el puerto 23. En la aplicación cliente se puede llamar a bind() y asignarle un número de puerto, pero no es necesario porque nadie va a tratar ni podra conectarse con él. • sockfd : Es el descriptor de socket devuelto por la función socket(). • my_addr : Es un puntero a una estuctura sockaddr que contiene la IP del host local y el número de puerto que se va a asignar al socket. (Mas abajo se detalla). • addrlen : Debe ser establecido al tamaño de la estuctura sockaddr. sizeof(struct sockaddr). Ejemplo : int Descriptor; struct sockaddr_in Direccion; struct servent* se = getservbyname("telnet", "tcp"); Descriptor = socket (AF_INET, SOCK_STREAM, 0); if (Descriptor == -1) printf ("Error\n"); Direccion.sin_family = AF_INET; Direccion.sin_port = se->s_port; Direccion.sin_addr.s_addr =INADDR_ANY; if (bind (Descriptor,(struct sockaddr *)&Direccion, sizeof(Direccion))==-1){ printf ("Error\n"); } La función getservbyname() recibe el nombre del servicio y si es "tcp" o "udp". Busca en el fichero /etc/services y nos devuelve una estructura servent con lo que ha encontrado (NULL en caso de no encontrar nada o de error). Una vez rellena la estructura servent, tenemos en el campo s_port el número del puerto. Basta asignarlo a Direccion.sin_port. Centro de Estudios NOVA – Cartagena 868-97-38-38 Estructuras. struct sockaddr{ unsigned short sa_family; // AF_* char sa_data[14]; // Direccion de protocolo. }; almacena la dirección de protocolo para muchos tipos de protocolos. sa_family puede ser AF_INET, AF_UNIX u otros dominios, para nuestro tutorial solo será AF_INET. sa_data contiene la dirección IP y número de puerto asignado al socket. struct in_addr { unsigned long int s_addr; // Dirección local a la que se asigna el socket } struct sockaddr_in { short int sin_family; // AF_INET unsigned short sin_port; // Numero de puerto. struct in_addr sin_addr; // Dirección IP. unsigned char sin_zero[8]; // Relleno. }; Se creó la esctuctura sockaddr_in para el caso de internet, para poder referenciar los elementos de forma más fácil. • • • • sin_family sera AF_INET. No debe convertirse a network byte order porque es solo usado por el kernel y no es enviado por la red. sin_port (número de puerto) deberá estar en network byte order, osea habrá que usar htons(). sin_addr (dirección IP) deberá estar en network byte order, osea habrá que usar htons(). sin_zero se utiliza para rellenar la estructura a la longuitud de sockaddr, debe estar inicializada a cero con la función bzero(). Ver la página del manual Los punteros a la estructura sockaddr_in deben ser precedidos con un cast tipo * struct sockaddr antes de pasarlos como parámetros a funciones. struct sockaddr_in mi_direc; mi_direc.sin_family = AF_INET; mi_direc.sin_port=htons(3490); mi_direc.sin_addr.s_addr= =inet_addr(“10.12.110.57); memset(&(mi_direc.sin_zero), ’\0’, 8 ); if (bind(Descriptor, (struct sockaddr *)&mi_direc, sizeof(struct sockaddr)) == -1) { perror(“Asociando socket”); exit(1); } Centro de Estudios NOVA – Cartagena 868-97-38-38 Ordenación de bytes „ Host byte order (little-endian): Byte menos significativo primero „ Network byte order (big-endian): Byte más significativo primero Todos los campos de socketaddr_in han de almacenarse en Network byte order Network Byte Order & Host Byte Order Cuando se estan enviando datos por algun tipo de red, estos deben de viajar en un orden tal, que puedan ser leidos, por lo regular se convierten al tipo Network Byte Order (Orden de Byte de Red), y cuando esos datos se procesan en un ordenador se deben de convertir a Host Byte Order (Orden de Byte de Red). Esto se puede hacer con funciones predefinidas de las API de Socket como son: • • • • htons() htonl() ntohs() ntohl() -> ``Nodo a variable corta de Red'' -> ``Nodo a variable larga de Red'' -> ``Red a variable corta de Nodo'' -> ``Red a variable larga de Nodo'' int inet_aton(const char *cp, struct in_addr *inp); inet_aton() convierte la dirección de Internet cp desde la notación estándar de números y puntos a la representación binaria, y la guarda en la estructura a la que apunte inp. Devuelve no-cero si la dirección es válida, cero si no. struct sockaddr_in antelope; inet_aton("10.0.0.1", &antelope.sin_addr); // store IP in antelope in_addr_t inet_addr(const char *cp); La función inet_addr() convierte la dirección de Internet cp desde la notación de números y puntos a la de datos binarios en orden de bytes del ordenador local. Si la entrada no es válida, devuelve INADDR_NONE (usualmente -1). Ésta es una interfaz obsoleta a inet_aton, descrita anteriormente; es obsoleta porque -1 es una dirección válida (255.255.255.255), e inet_aton porporciona una manera más clara para indicar que ha ocurrido un error. struct sockaddr_in destAddr; destAddr.sin_addr.s_addr = inet_addr("128.105.40.13"); Centro de Estudios NOVA – Cartagena 868-97-38-38 in_addr_t inet_network(const char *cp); La función inet_network() extrae el número de red en orden de bytes de red desde la dirección cp a la notación de números y puntos. Si la entrada es inválida, devuelve -1. char *inet_ntoa(struct in_addr in); La función inet_ntoa() convierte la dirección de Internet in dada en orden de bytes de red a una cadena de caracteres en la notación estándar de números y puntos. La cadena se devuelve en un búfer reservado estáticamente, que será sobreescrito en siguientes llamadas. // if added to the above inet_addr() code, this would print "128.105.40.13": char *s = inet_ntoa(destAddr.sin_addr); printf("dotted decimal address is %s\n", s); struct in_addr inet_makeaddr(int net, int host); La función inet_makeaddr() construye una dirección de Internet en orden de bytes de red combinando el número de red net con la dirección local host en la red net, ambas en orden de bytes de ordenador local. in_addr_t inet_lnaof(struct in_addr in); La función inet_lnaof() devuelve la parte del ordenador local de la dirección de Internet in. La dirección de ordenador local se devuelve en orden de bytes de ordenador local. in_addr_t inet_netof(struct in_addr in); La función inet_netof() devuelve la parte de número de red de la dirección de Internet in. El número de red se devuelve en orden de bytes de ordenador local. int listen ( int sockfd, int backlog) Se llama desde el servidor, habilita el socket para que pueda recibir conexiones. Solo se aplica a sockets tipo SOCK_STREAM. sockfd : Es el descriptor de socket devuelto por la función socket() que será utilizado para recibir conexiones. backlog : Es el número máximo de conexiones en la cola de entrada de conexiones. Las conexiones entrantes quedan en estado de espera en esta cola hasta que se aceptan ( accept () ). Así que la secuencia de funciones correctas seria la siguiente: socket(); bind(); listen(); Centro de Estudios NOVA – Cartagena 868-97-38-38 …accept()… int accept ( int sockfd, void *addr, int *addrlen) Se utiliza en el servidor, con un socket habilitado para recibir conexiones ( listen() ). Esta función retorna un nuevo descriptor de socket al recibir la conexión del cliente en el puerto configurado. La llamada a accept() no retornará hasta que se produce una conexión o es interrumpida por una señal. sockfd : Es el descriptor de socket habilitado para recibir conexiones. addr : Puntero a una estructura sockadd_in. Aquí se almacenará informacion de la conexión entrante. Se utiliza para determinar que host está llamando y desde qué número de puerto. addrlen : Debe ser establecido al tamaño de la estuctura sockaddr. struct sockaddr_in remote_addr; int addrlen; new_sockfd = accept ( sockfd, &remote_addr, &addrlen); close ( sockfd) Finalizan la conexión del descriptor de socket. Despues de utilizar close, el socket queda desabilitado para realizar lecturas o escrituras. read (int sockfd, void *buff, int len) Devuelve longitud de lo leído si lectura OK y -1 si no. sockfd: descriptor de socket. buff: direccionamiento al búfer de almacenamiento de la información. Len: tamaño del búfer. Para su medición funciones sizeof() y strlen(). Usada por clientes y servidores. write (int sockfd, void *buff, int len) Devuelve longitud de lo escrito si escritura OK y -1 si no. sockfd: descriptor de socket. buff: direccionamiento al búfer de lectura de la información. Len: tamaño del búfer. Para su medición funciones sizeof() y strlen(). Usada por clientes y servidores. int connect(int sockfd, const struct sockaddr *serv_addr, int addrlen); Centro de Estudios NOVA – Cartagena 868-97-38-38 La función connect solicita poder conectar el socket especificado por sockfd a un socket remoto cuya direccíon y puerto se especifica en serv_addr. Los parámetros son: sockfd: Descriptor del socket creado con anterioridad. serv_addr: Estructura de datos donde se especifica la dirección y puerto con el que deseamos establecer la conexión. addrlen: Longitud de la estructura de datos anterior. La función devuelve el valor -1 si error o 0 si su llamada tiene éxito. if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"; exit(1); } servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= htonl (INADDR_ANY); servaddr.sin_port=htons (SERVER_PORT); bzero(&servaddr, sizeof (servaddr)); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0){ printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el cliente. read (connfd, datos, strlen(datos)); //Se entiende que es lo primero que se hace. write (connfd, respuesta, strlen(respuesta)); // Resuelve close (connfd); close (listenfd). if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"; exit(1); } servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons (SERVER_PORT); bzero(&servaddr, sizeof (servaddr)); clilen=sizeof(servaddr); connfd =connect(listenfd, (struct_sockaddr *) &servaddr, clilen); //Se inicia la conexión. write (listenfd, peticion, strlen(peticion)); // Se realiza petición read (listenfd respuesta strlen(respuesta)); //Se entiende que se recibe respuesta close (listenfd). Centro de Estudios NOVA – Cartagena 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 EL SERVIDOR Los pasos que debe seguir un programa servidor son los siguientes: • • • • Abrir un socket con la función socket() Asociar el socket a un puerto con bind() Leer mensaje con recvfrom(). Responder mensaje con sendto() EL CLIENTE Los pasos que debe seguir un cliente son los siguientes: • • • Abrir un socket con socket() Enviar mensaje al servidor con sendto() Leer respuesta con recvfrom() int recvfrom ( int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen ) sockfd : Descriptor socket por donde se recibirán los datos. buf : Puntero a un buffer donde se almacenarán los datos recibidos. len : Longitud del buffer buf. flags : Ver la página del manual de recv(). Normalmente 0 from : Puntero a una estructura sockaddr que contiene la dirección IP y número de puerto del host origen de los datos. fromlen : Debe ser inicializado al tamaño de struct sockaddr ( sizeof (struct sockaddr) ). Centro de Estudios NOVA – Cartagena 868-97-38-38 Si no hay datos a recibir en el socket , la llamada a recvfrom() no retorna (bloquea) hasta que llegan datos, se puede establecer al socket como no bloqueante (ver: man 2 fcntl ) de manera que cuando no hay datos para recibir la función retorna -1 y establece la variable errno=EWOULDBLOCK. Más adelante se hablará de esto. recvfrom() retorna el numero de bytes recibidos, igual que recv(). int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen) sockfd : Descriptor socket por donde se enviarán los datos. msg : Puntero a los datos a ser enviados. len : Longitud de los datos en bytes. flags : Leer : man 2 sendto. Normalmente 0. to : Puntero a una estructura sockaddr que contiene la dirección IP y número de puerto destino. tolen : Debe ser inicializado al tamaño de struct sockaddr ( sizeof (struct sockaddr) ). sendto() retorna el número de bytes enviados, el cual puede ser menor que len, igual que en send(). #define MAXLINE 256 #define SERVER_PORT 3000 main ( ) { int sockfd; struct sockaddr_in, server_addr, cli_addr; int n; socklen_t len; char mess[MAXLINE]; servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= htonl (INADDR_ANY); servaddr.sin_port=htons (SERVER_PORT); bzero(&servaddr, sizeof (servaddr)); if ((listenfd = socket(AF_INET,SOCK_DGRAM,0))<= 0) { printf ("ERROR \n"; exit(1); } if ((bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr))<0) { printf ("ERROR \n"; exit(1); } for ( ; ; ) { clilen=sizeof(cliaddr); n = recvfrom(listenfd, mess, MAXLINE, 0, (struct sockaddr *) &cliaddr, &clilen); //Se recibe del cliente (sin establecimiento). sendto (listenfd, mess, n, 0, (struct sockaddr *) &cliaddr, len); //Se responde inmediatamente (ECO). }; Centro de Estudios NOVA – Cartagena 868-97-38-38 close (listenfd). }; #define MAXLINE 256 #define SERVER_PORT 3000 main (int argc, char* argv ) { int sockfd; struct sockaddr_in server_addr; int n; char sendline[MAXLINE], recvline[MAXLINE +1]; servaddr.sin_family= AF_INET; inet_pton (AF_INET, argv[1], &serveraddr.sin_addr.s_addr); servaddr.sin_port=htons (SERVER_PORT); bzero(&servaddr, sizeof (servaddr)); if ((sockfd = socket(AF_INET,SOCK_DGRAM,0))<= 0) { printf ("ERROR \n"; exit(1); } while (fgets (sendline, MAXLINE,stdin) != NULL) { sendto (sockfd, sendline, MAXLINE, 0, (struct_sockaddr *) &serverddr, sizeof(serveraddr)); //Se envía PAQUETE (ORIGINAL). n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); //Se recibe PAQUETE (ECO). recvline [n]=‘\0’; //Terminar texto. fputs (stdout, recvline, n+1); // Presentacion }; close (listenfd) }; int inet_pton(int af, const char *src, void *dst); inet_pton - Crea una estructura de dirección de red Esta función convierte la cadena de caracteres src en una estructura de dirección de red de la familia af , y copia la estructura de dirección de red a dst. Centro de Estudios NOVA – Cartagena 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Ejemplo 1: Conexión Cliente – Servidor. Envio de Mensaje de Bienvenida indicando ip del servidor en el cliente. SERVIDOR /* Estos son los ficheros de cabecera usuales */ #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 3550 /* El puerto que será abierto */ #define BACKLOG 2 /* El número de conexiones permitidas */ main() { int fd, fd2; /* los ficheros descriptores */ struct sockaddr_in server; /* para la información de la dirección del servidor */ struct sockaddr_in client; /* para la información de la dirección del cliente */ int sin_size; if ((fd=socket(AF_INET, SOCK_STREAM, 0)) == -1 ) { printf("error en socket()\n"); exit(-1); } server.sin_family = AF_INET; server.sin_port = htons(PORT); /* ?Recuerdas a htons() de la sección "Conversiones"? =) */ server.sin_addr.s_addr = INADDR_ANY; /* INADDR_ANY coloca nuestra dirección IP automáticamente bzero(&(server.sin_zero),8); /* escribimos ceros en el reto de la estructura */ if(bind(fd,(struct sockaddr*)&server, sizeof(struct sockaddr))==-1) { printf("error en bind() \n"); exit(-1); } if(listen(fd,BACKLOG) == -1) { printf("error en listen()\n"); exit(-1); } while(1) { sin_size=sizeof(struct sockaddr_in); if ((fd2 = accept(fd,(struct sockaddr *)&client, &sin_size))==-1) { printf("error en accept()\n"); exit(-1); } printf("Se obtuvo una conexión desde %s\n",inet_ntoa(client.sin_addr) ); /* mostrar la IP del cliente */ send(fd2,"Bienvenido a mi servidor.\n",22,0); /* que envia el mensaje de bienvenida al cliente */ close(fd2); /* cierra fd2 */ } } Centro de Estudios NOVA – Cartagena 868-97-38-38 CLIENTE #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> /* netbd.h es necesitada por la estructura hostent ;-) */ #define PORT 3550 /* El Puerto Abierto del nodo remoto */ #define MAXDATASIZE 100 /* El número máximo de datos en bytes */ int main(int argc, char *argv[]) { int fd, numbytes; /* ficheros descriptores */ char buf[MAXDATASIZE]; /* en donde es almacenará el texto recibido */ struct hostent *he; /* estructura que recibirá información sobre el nodo remoto */ struct sockaddr_in server; /* información sobre la dirección del servidor */ if (argc !=2) { /* esto es porque nuestro programa sólo necesitará Un argumento, (la IP) */ printf("Uso: %s <Dirección IP>\n",argv[0]); exit(-1); } if ((he=gethostbyname(argv[1]))==NULL){ printf("gethostbyname() error\n"); exit(-1); } /* llamada a gethostbyname() */ if ((fd=socket(AF_INET, SOCK_STREAM, 0))==-1){ printf("socket() error\n"); exit(-1); } /* llamada a socket() */ server.sin_family = AF_INET; server.sin_port = htons(PORT); /* htons() es necesaria nuevamente ;-o */ server.sin_addr = *((struct in_addr *)he->h_addr); /*he->h_addr pasa la información de ``*he'' a "h_addr" bzero(&(server.sin_zero),8); if(connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr))==-1){ printf("connect() error\n"); exit(-1); } if ((numbytes=recv(fd,buf,MAXDATASIZE,0)) == -1){ printf("Error en recv() \n"); exit(-1); } /* llamada a connect() */ /* llamada a recv() */ buf[numbytes]='\0'; printf("Mensaje del Servidor: %s\n",buf); /* muestra el mensaje de bienvenida del servidor =) */ close(fd); /* cerramos fd =) */ } struct hostent *gethostbyname(const char *name); struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; /* /* /* /* official name of host */ alias list */ host address type */ length of address */ Centro de Estudios NOVA – Cartagena char **h_addr_list; 868-97-38-38 /* list of addresses */ } struct hostent *hent; hent = gethostbyname("www.foobar.net"); Programa cliente - servidor mediante sockets en C que en la parte servidor me genera un numero aleatorio entre 1 y 100 y los clientes le van escribiendo numero y el server comprueba si es mayor, menor o han acertado... Cliente.c #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> int main() { int sockfd; int len; struct sockaddr_in address; int result; char numero; char respuesta ='1'; //Inicializamos a 1 para que entre en el while while(respuesta != '0') { printf("Escribe un número entre 1 y 100: "); scanf("%s",&numero); //Para capturar caracteres utiliza "%s" sockfd = socket(AF_INET, SOCK_STREAM, 0); address.sin_family = AF_INET; address.sin_addr.s_addr = inet_addr("127.0.0.1"); address.sin_port = htons(9734); len = sizeof(address); result = connect(sockfd, (struct sockaddr *)&address, len); if(result == -1) { perror("oops: client3"); exit(1); } write(sockfd, &numero, 1); read(sockfd, &respuesta, 1); //printf("El cliente envia: %c\n",numero); //printf("El cliente recibe: %c\n",respuesta); if (respuesta == '1') { printf("El número es mayor que %c\n", numero); } if (respuesta == '2') { printf("El número es menor que %c\n", numero); Centro de Estudios NOVA – Cartagena } close(sockfd); } printf("Has acertado!!! %c era el número!!!\n", numero); exit(0); } 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Servidor.c #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <math.h> int main() { int server_sockfd, client_sockfd; int server_len, client_len; struct sockaddr_in server_address; struct sockaddr_in client_address; server_sockfd = socket(AF_INET, SOCK_STREAM, 0); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_ANY); server_address.sin_port = htons(9734); server_len = sizeof(server_address); bind(server_sockfd, (struct sockaddr *)&server_address, server_len); int aleatorio; aleatorio = (1+rand()%100); listen(server_sockfd, 5); //Punto A while(1) { char numero; char respuesta; printf("Servidor esperando peticiones...\n"); client_len = sizeof(client_address); client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address, &client_len); read(client_sockfd, &numero, 1); //1 un numero de 1 cifra, 2 de 2 cifras int numeroacomp = atoi(&numero); if(aleatorio > numeroacomp) { respuesta = '1'; //Si retorna 1 es que el valor es menor al número del cliente }else{ if(aleatorio < numeroacomp){ respuesta = '2'; //Si retorna 2 es que el valor es mayor al núm del cliente }else{ respuesta = '0'; //Si retorna 0 es que el valor es el correcto. } } write(client_sockfd, &respuesta, 1); close(client_sockfd); } exit(0); } Centro de Estudios NOVA – Cartagena 868-97-38-38 #include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unistd.h> #include <sys/select.h> #define LISTENQ 5 // Tamaño de lista de clientes (4 también sería válido) #define MAXCLIENT 5 // Número máximo de clientes #define MSG_SIZE 1024 //Tamaño máximo del mensaje de texto #define SERVER_PORT 3000 int main (int argc, char **argv) { int i, maxi, maxfd, listenfd, connfd, sockfd, nready; int client[MAXCLIENT]; ssize_t n; fd_set rset, allset; char msg[MSG_SIZE]; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"; exit(1); } servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr=htonl(INADDR_ANY); servaddr.sin_port=htons(SERVER_PORT); bzero(&servaddr, sizeof (servaddr)); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de 5 clientes maxfd=listenfd; maxi=-1; for (i=0; i<MAX_CLIENT;i++) //Inicialización del array de clientes client[i]=-1; FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd,&allset); for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //CLIENTE NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el cliente for (i=0; i<MAX_CLIENT; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; if (i>maxi) maxi=i; } if (--nready<=0) continue; //Previsión para más de una interrupción /* ESPACIO RESERVADO PARA EL PROCESADO DEL SERVIDOR*/ Centro de Estudios NOVA – Cartagena } //Del bucle principal } //Del main. 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Centro de Estudios NOVA – Cartagena En la E/S síncrona, el proceso que realiza la petición entra en un estado de espera hasta que ésta ha finalizado. Un proceso que realiza una petición de E/S asíncrona, realizar una petición de E/S al núcleo. Si ésta es aceptada, el proceso continua realizando otras tareas hasta que el núcleo le avisa de que la operación ha finalizado. En ese momento el proceso interrumpe la tarea que estaba realizando y procesa la información obtenida de la operación de E/S. 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 #include <sys/select.h> #include <sys/time.h> int select (int numfds, fd_set* readfds, fd_set* writefds, fd_set* expectfds, struct timeval *timeout); Esta función permite controlar varios sockets (en general E/S) al mismo tiempo. Es capaz de indicar cuál de ellos está listo, tiene una condición de excepción o si ha expirado un cierto tiempo sin ningún nuevo evento. Select devuelve*: -1, en caso de error. N, el número de descriptores (sockets) que han saltado a la vez. Campos que incluye: Numfds: Es un entero que indica el número de E/S a observar, pero de una forma especial. Hay que indicar cuántos, pero contando desde el descriptor 0 e incluyendo todos los intermedios aunque no sean objeto de atención. Así, por ejemplo, si no interesa el socket con valor 5, aunque sólo sea ese, habrá que indicar que interesan el 0, 1, 2, 3, 4 y 5, en total 6. Como regla general, se indica que es el valor del descriptor mayor que es objeto de interés más uno. Por ejemplo, 5+1=6. Los conjuntos “fd_set”: Los conjuntos “fd_set” enumeran desde 0 hasta el valor máximo de descriptores que el sistema permite para una aplicación, todos los descriptores de E/S posibles de esa aplicación. El número máximo de descriptores varía según el sistema operativo pero en general 1024 es el número más utilizado. Los valores de este conjunto aunque sean enteros, sólo pueden tomar dos posibles valores 0 ó 1. La función select () utiliza tres conjuntos, readfds, writefds y exceptfds, para lectura entrada), escritura salida) y excepciones (errores) respectivamente. Manipulación de los conjuntos “fd_set”: Existen cuatro funciones: • void FD_ZERO(fd_set* set); //Pone todo el conjunto a 0. • void FD_SET(int fd, fd_set* set); //Pone el descriptor número fd a 1. • void FD_CLR(int fd, fd_set* set); //Pone el descriptor número fd a 0. • int FD_ISSET(int fd, fd_set* set); //Comprueba el valor del descriptor fd. //Si está activo devuelve 1, si no 0. NO es posible modificar un conjunto fd_set con expresiones como set[fd]. Si un conjunto no interesa, se pone a NULL su posición en select. Funcionamiento de los conjuntos “fd_set”: Los conjuntos fd_set son parámetros de entrada y salida en la función select. Por ello, poseen valores distintos antes y después de su ejecución. Al hacer una llamada a select hay que especificar además del número de descriptores a atender, cuáles de ellos son y por qué motivo (entrada, salida o excepción). Para ello, habrá que ACTIVAR (poner a 1) el descriptor correspondiente del conjunto correspondiente. Por ejemplo, si interesa el descriptor 8 para lectura (read) habrá que ejecutar: FD_SET(8, &readfds). Centro de Estudios NOVA – Cartagena 868-97-38-38 Después de la ejecución de select el conjunto “readfds” tendrá activado (puesto a 1) el descriptor correspondiente sólo si ha sido este descriptor el culpable del fin de la ejecución de select. En otro caso este descriptor se devolverá inactivo (puesto a 0). Si se quiere comprobar este valor, se utiliza la función FD_ISSET. Select devolvería como valor el número de descriptores que hubieran saltado. El parámetro ”timeout”: Es una estructura timeval (un tiempo descrito en seg. y usec.): struct timeval { long tv_sec; long tv_usec; }; Es un parámetro únicamente de entrada. Podemos hacer que select espere siempre, (como si fuera múltiples read, write,…) para lo que lo ponemos a NULL. Esperar un tiempo finito. Además, de activar descriptores, habrá que añadir un tiempo configurando “timeout” con segundos y microsegundos. Llamar a select pero de forma instantánea sin esperar a recibir nada (Ejecución tipo polling). En este caso, hay que configurar los valores de timeout, ambos a 0. *SELECT devuelve 0, si el tiempo configurado en timeout expira. Programa que espere 2,5 segundos para leer algo del teclado y mostrarlo por pantalla mensaje de acierto o fallo: #include <sys/unistd.h> #include <sys/select.h> #include <sys/time.h> #define STDIN 0 main () { struct timeval tv; fd_set conjunto; tv.tv_sec=2; tv.tv_usec=500000; //Configuración del tiempo FD_ZERO(&conjunto); FD_SET(STDIN, &conjunto); //Configuración del descriptor a observar select (STDIN+1, &conjunto, NULL,NULL,&tv); //Ejecución de select if (FD_ISSET (STDIN, &conjunto)) printf (“Se ha pulsado tecla\n”); //Comprobación else printf(“Tiempo expirado\n”); } Centro de Estudios NOVA – Cartagena 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Condiciones para la activación de fd(I) Ya se sabe que: • • • El descriptor fd hay que activarlo para que pueda ser atendido. Si ese descriptor vence se devuelve un número >0 en select, y el descriptor queda activo. Si ese descriptor no ha vencido, se quedará a 0. La función select devuelve: • • • • 0, si vence el timeout. N, un número mayor de 0 si vence algún descriptor e indica el número de descriptores vencidos vencidos. -1, en caso de error. Otra información relevante es la variable FD_SETSIZE que identifica el valor máximo que puede tomar un descriptor (normalmente 1024). siempre Un socket está listo para leer siem pre que se cumpla alguna de estas condiciones: • Exista algún dato a la entrada. • Se reciba un paquete de Fin de la Conexión en TCP. (Paquete FIN). • • Un socket escuchando por conexiones (Listening socket). Cuando se realiza accept, si llega un paquete SYN no se cerrará. Hay un error. No existe bloqueo y read o select no se bloquean devolviendo -1. Un socket está listo para escribir siempre que se cumpla alguna de estas condiciones: • • Exista posibilidad de mandar datos al destino. Cuando se quiere mandar un paquete de desconexión (Paquete FIN) en un cierre a dos bandas y el otro extremo era el que había comenzado la desconexión. Centro de Estudios NOVA – Cartagena • 868-97-38-38 Hay un error. No existe bloqueo y write o select no se bloquean devolviendo -1. Centro de Estudios NOVA – Cartagena 868-97-38-38 En Resumen Conclusiones para la activación de fd UN SOCKET (una E/S) NO SE PUEDE MARCAR A LA VEZ, SIMULTÁNEAMENTE, PARA ENTRADA Y SALIDA. La razones son: • En TCP, se realiza o bien read o bien write nunca los dos simultáneamente. • EN UDP, siempre es escribible (sendto es siempre posible), y no se realiza a la vez de recvfrom Centro de Estudios NOVA – Cartagena 868-97-38-38 Concurrencia con select. En TCP, el proceso consiste en crear múltiples procesos servidores hijos, individuales para cada uno de los clientes. En general, este proceso es muy ineficiente. Se deben duplicar pilas, recursos, etc. La concurrencia buscada se basa en los siguientes principios: • No se pueden multiplicar recursos (como con fork). • El funcionamiento ha de ser concurrente (al modo de multiplexación de procesos). • Se basa en el funcionamiento de select en donde: o Todos los descriptores se puede agrupar en conjuntos (fd_set). o Select encapsula el funcionamiento (E/S) de read, write, accept, close etc o Los descriptores se pueden agrupar no solo en conjuntos sino también en arrays (de enteros). Se definen: • Servidor concurrente, aquel que tiene un puerto abierto esperando por conexiones de clientes, (listening socket) y disponibilidad para aceptar n conexiones de clientes: • Conjunto de descriptores (fd_set) que indique en select los puertos a los que se presta atención: • Un array de descriptores que relacione la posición de conexión de un cliente con su descriptor: • • • • El descriptor correspondiente al descriptor del “listening socket” ha de estar siempre abierto, por lo que su descriptor (sockfd) estará siempre a 1 en el conjunto rset: Cada nuevo cliente produce dos entradas. Primero una modificación del conjunto de descritpres (rset) poniendo a 1 el descriptor correspondiente. Segundo una nuevo valor en client[ ] donde aparece una nueva valor que será el del descriptor, incorporándose en la primera posición libre: Por ejemplo un segundo nuevo cliente, se añade de forma secuencial: Un cierre de uno de los clientes, se transforma en un modificación, desactivandolo, en su posición del conjunto de descriptores (rset) e inicializando su posición dentro del array de descriptores (client[ ]): Centro de Estudios NOVA – Cartagena 868-97-38-38 #define LISTENQ N // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N a la vez. #define SERVER_PORT 5999 //Dato #define SERVIDOR “192.168.5.254” int main (int argc, char **argv) { int maxi, maxfd, listenfd, connfd, nready; int client [N]; // Array de descriptores de cada línea ssize_t len; fd_set rset, allset; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"; exit(1); } bzero(&servaddr, sizeof ( servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; //Mayor de todos los descriptores maxi=-1; //Máxima posición del array de descriptores ocupada for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset) { //cliente NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta usu. for (i=0; i<N; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD SET(connfd &allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; // Los operarios van después de las líneas if (i>maxi) maxi=i; if (--nready<=0) continue; //Previsión para más de una interrupción } /* ESPACIO RESERVADO PARA EL PROCESADO DEL SERVIDOR*/ } //Del bucle principal } //Del main Centro de Estudios NOVA – Cartagena 868-97-38-38 for (i=0; i<=maxi; i++){ if ((sockfd =client[i])<0) // Se van a comprobar todos los CLIENTES continue; if (FD_ISSET(sockfd, &rset)) //Para el caso de que uno de ellos escriba algo if ((len=read(sockfd, msg_rec,MSG_SIZE))==0){ //Proceso cierre en caso de FIN DEL CLIENTE. close(sockfd); FD_CLR(sockfd,&allset); client[i]=-1; if (maxi==i) --maxi; if (sockfd==maxfd) maxfd=sockfd;} if (--nready<=0) break; else continue; }else { //ESPACIO DE CÓDIGO DE ATENCIÓN A CLIENTE CONECTADO } } //Fin del bucle for de atención a los clients Centro de Estudios NOVA – Cartagena 868-97-38-38 Se desea desarrollar un servidor como el de la práctica 3 de LSC (Chat) pero sobre conexiones fiables. Por ello, deberá ofrecer: Multiplexación E/S para al menos 5 clientes simultáneos (Utilizar obligatoriamente la función select). Las conexiones serán fiables, previamente establecidas. Los mensajes que se pueden transmitir no superarán los 1024 bytes. El servidor se encargará de escuchar todos los clientes al mismo tiempo. A continuación de aquel que “interrumpa”, leerá el paquete, el cual retransmitirá al resto de los clientes. B) Escriba el código del servidor referente a la gestión del socket: apertura y admisión de conexiones de los clientes (30%). C) Escriba el código para conseguir el funcionamiento del servidor para dos clientes (25%). D) Extienda el código para que gestione los 5 clientes (Evitar usar código redundante) (20%). #include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unistd.h> #include <sys/select.h> #define LISTENQ 5 // Tamaño de lista de clientes (4 también sería válido) #define MAXCLIENT 5 // Número máximo de clientes #define MSG_SIZE 1024 //Tamaño máximo del mensaje de texto #define SERVER_PORT 3000 int main (int argc, char **argv) { int i, maxi, maxfd, listenfd, connfd, sockfd, nready; int client[MAXCLIENT]; ssize_t n; fd_set rset, allset; char msg[MSG_SIZE]; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"; exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr=htonl(INADDR_ANY); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de 5 clientes maxfd=listenfd; maxi=-1; for (i=0; i<MAX_CLIENT;i++) //Inicialización del array de clientes client[i]=-1; FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd,&allset); for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //CLIENTE NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta cli for (i=0; i<MAX_CLIENT; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) maxfd=connfd; if (i>maxi) maxi=i; Centro de Estudios NOVA – Cartagena 868-97-38-38 } if (--nready<=0) continue; //Previsión para más de una interrupción /* ESPACIO RESERVADO PARA EL PROCESADO DEL SERVIDOR*/ } //Del bucle principal } //Del main. Centro de Estudios NOVA – Cartagena 868-97-38-38 C) /* CÓDIGO SERVIDOR PARA DOS CLIENTES*/ i=0; while(i<=maxi && client[i]<0){ i++; } if(i<=maxi) sockfd1=client[i]; j=i; while(j<=maxi && client[j]<0){ j++; } if(j<=maxi) sockfd2=client[j]; if (FD_ISSET(sockfd1, &rset)) if ((n=read(sockfd1, msg,1024))==0){ //Para el caso de FIN del primero de ellos close(sockfd1); FD_CLR(sockfd1,&allset); client[i]=-1; } else write (sockfd2, msg, 1024); //Se transmite al segundo cliente if (FD_ISSET(sockfd2, &rset)) if ((n=read(sockfd2, msg,1024))==0){ //Para el caso de FIN del segundo close(sockfd1); FD_CLR(sockfd2,&allset); client[j]=-1; } else write (sockfd1, msg, 1024); //Se transmite al primero D) /*Código para n=5 clientes*/ for (i=0; i<=maxi; i++){ if ((sockfd1 =client[i])<0) // Se van a comprobar todos los clientes continue; if (FD_ISSET(sockfd1, &rset)) //Para el caso de que uno de ellos escriba algo if ((n=read(sockfd1, msg,1024))==0){ //Proceso de cierre en caso de FIN close(sockfd1); FD_CLR(sockfd1,&allset); client[i]=-1; }else{ j=0; while(j<=maxi){ //Envío al resto de clientes if(sockfd2=client[j]>0) if(client[j]!=sockfd1) //NO se transmite al origen write (sockfd2, msg, 1024); j++; } } } Centro de Estudios NOVA – Cartagena 868-97-38-38 Se desea desarrollar un servidor de un servicio de transmisión de datos bancarios. En concreto, el servicio consiste en recoger el número secreto que el cliente teclea en un cajero automático y comprobar que efectivamente es ese el número secreto de ese cliente. Así, el servidor recogerá la información de número de tarjeta y del número secreto de cada uno de los cajeros y enviará la confirmación con un “RIGHT” o “WRONG” según sea el número correcto o no. La lista de números secretos se pasa al servidor (no hay que programarla), donde en cada elemento de la lista se identifica el número de tarjeta y el número secreto según la estructura “struct tarjeta”. Las conexiones se realizan sobre comunicaciones UDP ya que la transmisión de información debe ser rápida. Sin embargo, se debe asegurar que el mensaje de respuesta “RIGHT” o “WRONG” sí que llegue por lo que se debe implementar un sistema de confirmación de recepción de dicho mensaje. Se debe esperar la confirmación no más de 5 segundos utilizando para ello la función “select”. Se supone que cada cajero es un único proceso que transmite los números de tarjeta y secreto en un único mensaje y que recibe el mensaje RIGHT o WRONG. Si recibe dicho mensaje envía un mensaje indicando “OK”. En el caso de no recibirlo en un periodo de tiempo de 8 segundos volverá a ejecutar la petición. #include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unistd.h> #include <time.h> #include <sys/select.h> #define MSG_SIZE 12 //Tamaño máximo del mensaje de texto #define SERVER_PORT 3000 #define RIGHT 1 #define WRONG 0 int main (int argc, char **argv) { struct tarjeta { long numero_tarjeta; int numero_secreto; struct tarjeta* siguiente; }; struct tarjeta2 { long numero_tarjeta2; int numero_secreto2; }; struct lista { struct tarjeta *primera; struct tarjeta *ultima; }; struct lista lista_tarjeta; struct tarjeta * comparativa; struct tarjeta2 * datos_cajero; int n, maxfd, sockfd, respuesta; char *confirmacion; int data, envio; fd_set rset; socklen_t clilen; struct sockaddr_in servaddr; struct sockaddr client; struct timeval tiempo_espera; if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= htonl (INADDR_ANY); servaddr.sin_port= htons (SERVER_PORT); if (bind (sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in))<0) { printf("ERROR \n"); exit(1); } Centro de Estudios NOVA – Cartagena // Código reservado para el funcionamiento del servidor close (sockfd); } 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 C) Funcionamiento del servidor. for (;;){ data=recvfrom (sockfd, datos_cajero, MSG_SIZE, 0, client, cli_len); //Preparado recibir datos if (data != sizeof (struct tarjeta)) { printf (“ERROR \n); continue; } //Se comprueban datos son correctos. Comparativa=lista_tarjeta->primera; //Inicialización de variables respuesta = WRONG; FD_SET(sockfd, &rset); tiempo_espera.tv_sec= 5; tiempo_espera.tv_usec=0; while (comparativa!= NULL) { //Comparar valores if ( comparativa->numero_tarjeta == datos_cajero->numero_tarjeta2){ if (comparativa ->numero_secreto==datos_cajero->numero_secreto2)respuesta =RIGHT; else respuesta = WRONG; break; } comparativa=comparativa->siguiente; } if (respuesta==RIGHT) //Se envía respuesta. envio= sendto (sockfd, “RIGHT”, strlen(“RIGHT”), 0, client, sizeof(&clilen)); else envio= sendto (sockfd, “WRONG”, strlen(“WRONG”), 0, client, sizeof(&clilen)); nready= select (maxfd+1, &rset, NULL, NULL, &tiempo_espera); // Proceso de confirmación . Se espera. if (nready==0) continue; // Se termina por Timeout if (FD_ISSET(sockfd, &rset)) { //Llega respuesta. Se confirma que es buena. recvfrom (sockfd, confirmacion, 2, 0, client, cli_len); if (strcmp(&confirmacion, “OK”)) continue; else{ printf (“ERROR”); continue; } } // Fin proceso de confirmación } // Fin del servidor Centro de Estudios NOVA – Cartagena 868-97-38-38 25 DE JUNIO DE 2004 Se desea desarrollar el software de comunicaciones de un cliente de un servicio de cajero automático bancario. En concreto, el servicio consiste en recoger el número de tarjeta que se obtiene al introducir la tarjeta del usuario y el número secreto que el cliente teclea en el teclado. Para cada evento se asigna estáticamente un descriptor que se usarán para leer los datos de entrada (número de tarjeta y número secreto). A continuación se deberá enviar al servidor esa información. La información se transmite a través de un socket UDP que conectará con el servidor y le transmitirá los datos a través de una estructura “struct tarjeta2”. El cajero automático deberá quedar bloqueado a través de la función “select” durante 8 segundos esperando la respuesta “RIGHT” o ”WRONG” dependiendo de si el número secreto es correcto o no. En caso de que no llegue dicha respuesta o se reciba errónea se volverá a realizar la petición. Otra información: #define teclado 0 #define numerotarjeta 3 #define servidor “192.168.5.254” */ #include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unistd.h> #include <time.h> #include <sys/select.h> #define TECLADO 0 //descriptor correspondiente a la entrada estándar. #define NUMEROTARJETA 3 //descriptor correspondiente a la entrada del lector de la tarjeta de crédito. #define SERVER_PORT 3000 //Número de puerto (indiferente). #define SERVIDOR “192.168.5.354” // Servidor del servicio de datos bancarios int main (int argc, char **argv) { struct tarjeta2 { long numero_tarjeta; int numero_secreto; }; struct tarjeta2 * datos_envio; char * respuesta; long *numero_tarjeta_in; int *numero_secreto_in; int n_ready; fd_set rset; socklen_t servlen; struct sockaddr_in servaddr; struct timeval tiempo_espera; if (read (NUMEROTARJETA, numero_tarjeta_in, sizeof(numero_tarjeta_in)) < sizeof (long)) { printf (“Error, tarjeta no válida\n “); exit(1); } if (read (TECLADO, numero_secreto_in, sizeof(numero_secreto_in)) < sizeof(int)) { printf (“Error, numero secreto incorrecto \n”); exit(1); } if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { printf ("ERROR \n"; exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port= htons (SERVER_PORT); datos_envio-> numero_tarjeta= *numero_tarjeta_in; datos_envio->numero_secreto= *numero_secreto_in; for (;;){ Centro de Estudios NOVA – Cartagena 868-97-38-38 if (sendto (sockfd, datos_envio, sizeof (struct tarjeta2),0, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) <0) { printf (“Servidor fuera de servicio, espere mejor ocasión \n”); exit (1); } tiempo_espera.tv_sec= 8; tiempo_espera.tv_usec=0; FD_ZERO (&rset); FD_SET(sockfd, &rset); nbytes=strlen(“RIGHT”); nready= select (maxfd+1, &rset, NULL, NULL, &tiempo_espera); if (n_ready<0) { printf (“Error de datos, no se puede atender su petición\n”); exit(1); } else if (n_ready==0) { printf (“Servidor ocupado. Espere una nueva petición \n”); continue; } if (FD_ISSET (sockfd, &rset)) { if (recvfrom (sockfd, respuesta, nbytes, 0, (struct sockaddr *) &servaddr, servlen) < nbytes){ printf (“Error, en datos de servidor\n”); continue; } if (strcmp (respuesta, “RIGHT”)==0 || strcmp (respuesta, “WRONG”)==0) { printf (“Servidor activo, llamada correcta. \n”); break; }else{ continue; } //Se recibe algo pero no es lo esperado } } } Centro de Estudios NOVA – Cartagena 868-97-38-38 18 DE DICIEMBRE DE 2004 Se diseña un servidor central concurrente TCP destinado al control automático de una central de envasado de zumos naturales. Como existen diversos tipos de zumos (N) existen N líneas de producción. El sistema se detalla en el gráfico: Cada operario tiene acceso al control sobre todas y cada una de las líneas de producción según le convenga en cada instante. Por tanto el servidor debe: − Gestionar las conexiones (sockets) permanentes con las líneas. Éstas se conectan inmediatamente al arrancar el sistema y no se desconectan salvo error o cierre. Habrá un total de n conexiones de ese tipo. Su conexión no forma parte del problema. Asignar el descriptor de cada línea a n+3 siendo n el número de línea, entre 1 y N (p. ej. Para la línea 1 su descriptor es 4, para la línea 2, el descriptor es 5, etc.). − Aceptar y gestionar las conexiones de los operarios, hasta un total de 4. − Recoger las órdenes de los operarios, transmitírselas a las líneas, esperar por las respuestas de las líneas y reenviarlas al operario que las solicitó (de forma concurrente). Se pide: A) Flujograma de funcionamiento del servidor. Visualiza la gestión de conexiones concurrentes y el servicio ofrecido a los operarios. Supóngase respuestas de las líneas inmediatas. (20%) B) Código del arranque del servidor concurrente y establecimiento de la conexión de los operarios. Recuerde que además hay N conexiones establecidas para las líneas (éstas no hay que conectarlas). (20%) C) Servicio a los usuarios. Código del programa necesario para que los usuarios puedan solicitar y recibir información de las líneas que le interesen (sólo de una por petición). Suponer que las respuestas de las líneas se producen de forma inmediata. (25%) D) Considerar ahora que el tiempo de respuestas de las líneas es elevado. Modificar el código para que el servidor siga funcionando de forma concurrente a pesar de este problema, es decir, que el servidor no se bloquee esperando las respuestas de las líneas. No se limita el tiempo de respuesta de las líneas y cada línea acepta sólo una petición simultánea. (25%) E) Código de desconexión de operarios propio de un servidor concurrente. (10%) Otra información: #define N 20 #define MSG_SIZE 1024 //Tamaño máximo de los mensajes #define PUERTO 3000 #define SERVIDOR “192.168.5.254” NOTAS IMPORTANTES: 1.- Los mensajes que transmite el operario, se pueden retransmitir a las líneas de productos tal y como llegan. Idem para las respuestas de las líneas. Se consideran mensajes de texto. 2.- Antes de transmitir una orden, el operario envía un “int” identificando la línea (n) a la que se está refiriendo. Mientras no cambie, todos los mensajes de ese operario se refieren a la última línea identificada. El envío del número de línea y del mensaje se realiza de forma consecutiva e inmediata (no es necesario ocuparse de la concurrencia en este período). 3.- NO es necesario incluir las macros y librerías habituales en el código de la solución. #include <sys/socket.h> #include <sys/types.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unistd.h> #include <sys/select.h> #define LISTENQ 4 // Tamaño de lista de entrada de conexiones. Como no se indica nada se asigna 4. #define MAXOPERARIO 4 // Número máximo de operarios #define MSG_SIZE 1024 // Tamaño máximo del mensaje de texto, válido para mensajes de operarios y de // líneas. #define SERVER_PORT 3000 #define SERVIDOR “192.168.5.254” #define N 20 int main (int argc, char **argv) { Centro de Estudios NOVA – Cartagena 868-97-38-38 int i, maxi, maxfd, listenfd, connfd, sockfd, nready; int *linea_solicitada; int client[MAXOPERARIO]; // Array de Operarios int client2[MAXOPERARIO]; //Sockets (o línea) con los que se comunica cada operario. int operario[N]; //Necesario para saber a cada línea quién le ha preguntado int linea[N]; // Necesario para asignar a cada línea un descriptor. Se podría hacer: (descriptor =n+3); ssize_t len; fd_set rset, allset; char msg_env[MSG_SIZE]; char * msg_rec; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"; exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr=htonl (INADDR_ANY); // o bien servaddr.sin_addr.s_addr = inet_addr(SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los 4 posibles operarios. maxfd=listenfd; maxi=-1; for (i=0; i<MAXOPERARIO;i++){ //Inicialización de los arrays de clientes client[i]=-1; client2[i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); for (i=0; i<N; i++) linea[i]=i+4; //Se asigna descriptor a cada línea, n+3 (como el array empieza en 0 => i+4) maxfd=N+3; //Se supone que las 20 líneas se han conectado ya, luego maxfd es igual al último descriptor. for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //OPERARIO NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el operario for (i=0; i<MAXOPERARIO; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; // Los operarios van después de las líneas if (i>maxi) maxi=i; } if (--nready<=0) continue; //Previsión para más de una interrupción /* ESPACIO RESERVADO PARA EL PROCESADO DEL SERVIDOR*/ } //Del bucle principal Centro de Estudios NOVA – Cartagena 868-97-38-38 ----------------------- for (i=0; i<=maxi; i++){ if ((sockfd1=client[i])<0) // Se van a comprobar todos los OPERARIOS continue; if (FD_ISSET(sockfd1, &rset)) //Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec,MSG_SIZE))==0){ //Proceso de cierre en caso de FIN DEL OPERARIO (APARTADO E) close(sockfd1); FD_CLR(sockfd1,&allset); client[i]=-1; client2[i]=-1; if (maxi==i) --maxi; } if (--nready<=0) continue; else break; else{ if (len==sizeof(int)) { // Se recibe el número de línea al que se quiere dirigir linea_solicitada=(int *) &msg_rec; // Se pasa a apuntar en formato int. Ahora ya se puede dirigir el mensaje. client2[i]=linea[*linea_solicitada-1]; // Socket de la línea que quiere el operario se guarda en client2. len=read(sockfd1, msg_rec, MSG_SIZE); // Se lee el mensaje del operario. Se puede hacer control } // de errores. write (client2[i], msg_rec, len+1); //Se envía el mensaje a la línea. /* Las dos próximas líneas no se aplican en el apartado D */ len=read(client2[i], msg_env, MSG_SIZE); //Esperamos recibir el mensaje de la línea. write(sockfd1, msg_env, len+1); //Envíamos mensaje al operario. } //FIN del servicio al operario if (--nready<=0) continue; //Previsión para más de un operario simultáneo. } // Fin del for de atención a los usuarios ------------------------------------for (i=0; i<=maxi; i++){ if ((sockfd1=client[i])<0) // Se van a comprobar todos los OPERARIOS continue; if (FD_ISSET(sockfd1, &rset)) //Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec,MSG_SIZE))==0){ //Proceso de cierre en caso de FIN DEL OPERARIO (APARTADO E) close(sockfd1); FD_CLR(sockfd1,&allset); client[i]=-1; client2[i]=-1; if (maxi==i) --maxi; } if (--nready<=0) continue; else break; else{ if (len==sizeof(int)) { // Se recibe el número de línea al que se quiere dirigir client2[i]=linea[*linea_solicitada-1]; // Socket de la línea que quiere el operario se guarda en client2. len=read(sockfd1, msg_rec, MSG_SIZE); // Se lee el mensaje del operario. Se puede hacer control } // de errores. write (client2[i], msg_rec, len+1); //Se envía el mensaje a la línea. if (operario[client2[i]] < 0) //Por si la línea estuviera ya ocupada. operario[client2[i]-4]=sockfd1; // actualización del array donde se guardan los operarios que solicitaron servicio para saber quién mandó el mensaje. Centro de Estudios NOVA – Cartagena 868-97-38-38 FD_SET(client2[i], &allset); // Para que el descriptor del socket de esa línea pase a ser atendido. /* ATENCIÓN A LAS LÍNEAS */ for (j=0; j<N; j++){ if ((sockfd1 =linea[j])<0) // Se van a comprobar todos las LÍNEAS continue; if (FD_ISSET(sockfd1, &rset)) //Para el caso de que uno de las líneas haya mandado algo if ((len=read(sockfd1, msg_rec, MSG_SIZE))==0){ //Se leen los datos close(sockfd1); FD_CLR(sockfd1,&allset); linea[j]=-1; //Proceso de cierre en caso de FIN DE LÍNEA (APARTADO E) }else{ write (operario[j], msg_rec, len+1); FD_CLR(sockfd1,&allset); } } } //FIN del servicio al operario if (--nready<=0) continue; //Previsión para más de un operario simultáneo. } // Fin del for de atención a los usuarios 18 DE DICIEMBRE DE 2004 Se diseña un único servidor central concurrente TCP (sin generar nuevos procesos) destinado al control de la automatización de una empresa química. Como existen diversas máquinas para su control (N) existen N conexiones, una para cada máquina. El sistema se detalla en el gráfico: Cada operario tiene acceso al control sobre todas y cada una de las máquinas según le convenga en cada instante. Por tanto el servidor debe: − Aceptar y gestionar las conexiones (sockets) con las máquinas. Éstas se conectan independientemente entre ellas y lo hacen una vez hayan arrancado y se hayan configurado por sí mismas. Habrá un total de N conexiones de ese tipo. Sus descriptores van desde 4 hasta N+3. − Aceptar y gestionar las conexiones de los operarios, hasta un total de P conexiones de operarios. A los operarios se le asigna descriptor desde N+4 en adelante. − Recoger las órdenes de los operarios, transmitírselas a las máquinas, esperar por las respuestas de éstas y reenviarlas al operario que las solicitó (de forma concurrente). − La transmisión de las órdenes a las máquinas se realiza inmediatamente recibidas desde el operario. Las respuestas de las máquinas se reciben también de forma inmediata y se envían a su vez a los operarios ordenantes. Se pide: A) Flujograma de funcionamiento del servidor. Visualiza la gestión de conexiones concurrentes tanto de máquinas como de operarios y el servicio ofrecido a los operarios. Se valorará el utilizar un cronograma de apoyo sobre el funcionamiento del servidor. El proceso se realiza “estáticamente” para el servidor, es decir, que primero se conectan todas las máquinas (N) y luego todos los operario (P). Suponer máquinas desocupadas cuando se les envía una petición. (20%) B) Código del arranque del servidor concurrente y establecimiento de la conexión de las máquinas y de los operarios.(20%) C) Código de servicio a los usuarios. Código del programa necesario para que los usuarios puedan enviar información al servidor y que éste lo reenvíe a las máquinas (lo referente a recibir el número de máquina y mensaje y transmitir dicho mensaje a la máquina). Incluye también el código para que las máquinas respondan de forma inmediata y reenvío de las respuestas a los operarios. Controlar el caso de que la máquina no esté conectada. Supóngase que las máquinas están desocupadas. (25%) D) Flujograma de funcionamiento en el caso de que la conexión de las líneas y operarios se haga de forma aleatoria. En este caso, el primer mensaje después de la conexión de cada uno de los extremos es OPERARIO o MAQUINA según sea operario o máquina seguido del número de operario o máquina correspondiente. De esta formas los descriptores de máquinas y operarios no son consecutivos y pasan a estar mezclados. Destaca claramente el control de este hecho. (15%) E) Modifica el código de los apartados B y C cuando la conexión de los operarios y máquinas se realice de forma aleatoria. Presta especial atención al establecimiento de la conexión, distinción entre máquinas y operarios y la existencia de las conexiones con operarios y máquinas cuando se realiza el servicio. (20%) Centro de Estudios NOVA – Cartagena 868-97-38-38 Otra información: #define MSG_SIZE 1024 //Tamaño máximo de los mensajes de operarios a máquinas y viceversa #define PUERTO 3000 //Puerto del servicio #define SERVIDOR “192.168.5.254” //Dirección IP de la máquina servidora NOTAS IMPORTANTES: 1.- Los mensajes que transmite el operario, se pueden retransmitir a las máquinas tal y como llegan. Idem para las respuestas de las máquinas. Se consideran mensajes de texto. 2.- Antes de transmitir una orden, el operario envía un “int” identificando la máquina (n) a la que se está refiriendo. Por simplicidad, el operario siempre envía el número de máquina antes de una orden. El envío del número de máquina y del mensaje se realiza de forma consecutiva e inmediata en mensajes distintos (no es necesario ocuparse de la concurrencia en este período). 3.- Cada operario sólo puede enviar un mensaje cada vez y esperar una sola respuesta. 4.- NO es necesario incluir las macros y librerías habituales en el código de la solución. 5.- P+N<1020. Centro de Estudios NOVA – Cartagena 868-97-38-38 #define P //Número máximo de operarios #define N // Número de máquinas #define LISTENQ P+N // Tamaño de lista de entrada de conexiones. Libre para aceptarlos todos a la vez #define MAXOPERARIO P // Número máximo de operarios #define MSG_SIZE 1024 // Tamaño máximo del mensaje de texto, válido para mensajes de operarios y de máquinas. #define SERVER_PORT 3000 #define SERVIDOR “192.168.5.254” int main (int argc, char **argv) { int i, maxi, maxfd, listenfd, connfd, sockfd, nready, sockfd1; int *maq_solicitada; int conexion_maquinas_completo=0; int client[MAXOPERARIO]; //Array de descriptores de los operarios. int machine[N]; //Array de descriptores de las máquinas. int client2[MAXOPERARIO]; //Sockets (o máquina) con los que se comunica cada operario. int operario[N]; //Necesario para saber a cada máquina quién le ha preguntado ssize_t len; fd_set rset, allset, writeset, writeallset; char msg_env[MSG_SIZE]; char * msg_rec[P]; //Array de mensajes recibidos de cada operario. socklen_t clilen; struct sockaddr_in cliaddr, servaddr; if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0 || (listenfd>2)) { printf ("ERROR \n"; exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr = inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in))<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los 4 posibles operarios. maxfd=listenfd; maxi=-1; for (i=0; i<MAXOPERARIO;i++){ //Inicialización de los arrays referentes a los operarios client[i]=-1; client2[i]=-1; } for (i=0; i<N;i++){ //Inicialización de los arrays de las maquinas machine[i]=-1; operario[i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); maxfd=listenfd; //La única conectada for(;;){ rset=allset; nready=select(maxfd+1,&rset,NULL,NULL,NULL); if (FD_ISSET(listenfd,&rset)){ clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); if (!conexion_maquina_completo) {//Si es máquina for (i=0; i<N; i++)//Actualización del array de máquinas if (machine[i]<0) { machine[i]=connfd; break; Centro de Estudios NOVA – Cartagena 868-97-38-38 }//No se actualiza el conjunto allset porque no es necesario. if (i==N) conexion_maquina_completo=1; //Actualización fin de conexión de máquinas if (i>maxi) maxi=i; }else {// Es un operario for (i=0; i<MAXOPERARIO; i++)//Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset);//Actualización del conjunto de descriptores if (connfd > maxfd)//Actualización de variables maxfd=connfd;// Los operarios van después de las máquinas if ((i+N)>maxi) maxi=i+N; } if (--nready<=0) continue;//Previsión para más de una interrupción } /* ESPACIO RESERVADO PARA EL PROCESADO DEL SERVIDOR*/ } //Del bucle principal } //Del main. C) /* CÓDIGO SERVIDOR PARA ATENDER LOS MENSAJES DE CADA OPERARIO*/ for (i=N+3; i<=maxi; i++){ // Se van a comprobar todos los OPERARIOS if ((sockfd1 =client[i-N-3])<0) continue; if (FD_ISSET(sockfd1, &rset)){ //Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec, MSG_SIZE))>0){ if (len==sizeof(int)) { maq_solicitada=(int *) &msg_rec; client2[i]=machine[*maq_solicitada-1]; if (machine[*maq_solicitada-1]<0) { --nready; continue; } len=read(sockfd1, msg_rec, MSG_SIZE); write (client2[i], msg_rec, len+1); //TX Mensaje. len=read(client2[i], msg_env, MSG_SIZE); write(sockfd1, msg_env, len+1); } if (--nready<=0) continue; } } } D) for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL , NULL, NULL); //Bloqueo en espera de entradas o salidas if ( FD_ISSET(listenfd, &rset){ //OPERARIO NUEVO o Máquina nueva clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta la conexión for (i=0; i<N; i++)//Actualización del array de máquinas if (conexiones[i]<0) { conexiones[i]=connfd; break; }//No se actualiza el conjunto allset porque no es necesario. if (i==N+P) conexion =1; //Actualización fin de conexión posibles if (i>maxi) maxi=i; FD_SET(connfd,&allset); if (connfd > maxfd) maxfd=connfd; Centro de Estudios NOVA – Cartagena 868-97-38-38 if (--nready<=0) continue; } for (i=0; i<=maxi; i++){ if ((sockfd1 =conexiones[i])<0) continue; //Actualización del conjunto de descriptores //Actualización de variables // Los operarios van después de las máquinas //Previsión para más de una interrupción // Se van a comprobar todos los OPERARIOS if (FD_ISSET(sockfd1, &rset)){//Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec, MSG_SIZE))==sizeof (“OPERARIO”)){ num_operario=(int *) &msg_rec; client[*num_operario-1]=sockfd1; Tipo_conexion[i]=1; }else if(len==sizeof(“MAQUINA”){ num_maquina=(int *) &msg_rec; maquina[*num_maquina-1]=sockfd1; Tipo_conexion[i]=0; FD_CLR(&allset, sockfd1); } //Las máquinas no hay que escucharlas else{ if (len==sizeof(int)) { client2[i]=maquina[*maq_solicitada-1 if (maquina[*maq_solicitada-1]<0) { --nready; continue; } sock_maquina=maquina[*maq_solicitada-1]; for (int k=0; k<N+P; k++){ sock_conexiones=conexiones[k]; if (sock_conexiones==sock_maquina) break; } } if(tipo_conexión[k]>0){ --ready; continue; } len=read(sockfd1, msg_rec, MSG_SIZE); write (client2[i], msg_rec, len+1); //TX Mensaje. len=read(client2[i], msg_env, MSG_SIZE); write(sockfd1, msg_env, len+1); } if (--nready<=0) continue; } } //Del bucle principal } //Del main. Centro de Estudios NOVA – Cartagena 868-97-38-38 28 DE JUNIO DE 2005 – EJERCICIO 1 Se diseña un servidor central concurrente TCP (puerto 5999) destinado al servicio de telefonía móvil. El servicio acepta la conexión de los distintos clientes y los mantiene conectados mientras no reciba la acción de desconexión. El gráfico representa el sistema una vez conectados los usuarios: Entre las tareas del servidor, se encuentran: − Gestionar las conexiones (sockets) permanentes con los usuarios. Éstos se conectan cuando desean tener acceso a la red. Habrá un total de N conexiones de este tipo. − Gestionar la conexión con el resto de la red a través del descriptor 500 (fijo y siempre activo). − Gestionar el establecimiento de las conexiones entre usuarios inscritos en el propio servidor y entre usuarios locales y externos. Cuando un usuario desea la conexión manda un número destino. Con el número, se comprueba si el destino pertenece a este servidor y, en caso negativo, se manda al resto de la red, estableciéndose, en su caso, la comunicación a través de un nuevo descriptor para el usuario destino. No se contempla el caso de peticiones desde usuarios externos. Se pide: A) Código para aceptar la conexión de los usuarios y proporcionar la concurrencia. El “resto de la red” está conectada desde el arranque del servidor. (20%) B) Código de establecimiento de las llamadas entre los usuarios locales. No incluya la gestión de las llamadas una vez establecidas, sólo lo referente a la recepción de los números, y petición al usuario destino (con el número origen) y espera de la respuesta (no hay que responder al usuario origen). La conexión se realiza si se recibe un “SI” y no se realiza si es un “NO”. (20%) C) Código de establecimiento de las llamadas con usuarios remotos. En este caso las peticiones hay que encaminarlas por el descriptor “resto de la red”. La respuesta en este caso puede ser un “NO” si es negativa con lo que se rechaza, y en caso afirmativo se recibe un paquete de conexión SYN a través del descriptor “resto de la red”. En este caso se procederá a la conexión del nuevo cliente y pasará a ser un usuario local más. No hay que responder al cliente origen.(20%) Otra información: #define N 20 #define SERVIDOR “192.168.5.254” #define N 20 #define LISTENQ N // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N a la vez. #define SERVER_PORT 5999 //Dato #define SERVIDOR “192.168.5.254” #define RESTO 500 //Descriptor del resto de la red #define MSG_SIZE 1024 //No se dice nada, mínimo mayor de 4 int main (int argc, char **argv) { int i, j, maxi, maxfd, listenfd, connfd, sockfd, sockfd1, nready,*numero, numero_resto; int client [N]; // Array de descriptores de cada línea int numeros[N]; //Posición-client<->Número int conexiones [N]; //Relacionales entre números int masclient [FD_SETSIZE]; //Para clientes externos que se conecten (apartado C) int conexiones2 [FD_SETSIZE];//Para los relacionales de los externos (apartado C) int numeros2 [FD_SETSIZE];// Para los números externos (apartado C) ssize_t len; fd_set rset, allset; char * msg_env; //Mensaje enviado de conexión char * msg_rec; // Mensaje recibido de conexión char *valor1, *valor2; //Auxiliares para formar mensaje socklen_t clilen; struct sockaddr_in cliaddr, servaddr; if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"; exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); Centro de Estudios NOVA – Cartagena 868-97-38-38 servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; numeros[i]=-1; conexiones [i]=-1; } FD_ZERO(&allset); FD_ZERO(&rset); //Inicialización de los conjuntos de descriptores //Preparado para aceptar la entrada de los posibles clientes. FD_SET(listenfd, &allset); for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //USUARIO NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario for (i=0; i<MAXOPERARIO; i++)//Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset);//Actualización del conjunto de descriptores if (connfd > maxfd)//Actualización de variables maxfd=connfd;// Los operarios van después de las líneas if (i>maxi) maxi=i; if (--nready<=0) continue;//Previsión para más de una interrupción } /* ESPACIO RESERVADO PARA EL PROCESADO DEL SERVIDOR*/ } //Del bucle principal } //Del main B) /* CÓDIGO SERVIDOR PARA ATENDER LAS PETICIONES DE CADA CLIENTE*/ for (i=0; i<=maxi; i++){ if ((sockfd1 =client[i])<0) continue; // Se van a comprobar todos los CLIENTES LOCALES if (FD_ISSET(sockfd1, &rset))//Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec,MSG_SIZE))==0){ close(sockfd1); FD_CLR(sockfd1,&allset); client[i]=-1; numeros[i]=-1; if (maxi==i) --maxi; } if (--nready<=0) break; else continue; else{ if (conexiones[i]<0) //Conexiones. No conversación. if (len==sizeof(int)){ // Se recibe un número de teléfono if (numeros[i]<0){ numero=(int *) &msg_rec; numeros[i]=*numero;// Actualización número propio Centro de Estudios NOVA – Cartagena 868-97-38-38 if (--nready<=0) break; else continue; }else{// No es el número propio, es el destino numero=(int *) &msg_rec; conexiones[i]=numero;// Actualización conexiones j=0; while (numeros[j]!=numero) j++; if (j<20) //Número local write(client[j], &numeros[i], sizeof(numeros[i])); if (j>=20){ //Número externo valor1=(char*)&numeros[i]; valor2=(char *)conexiones[i]; msg_env=strcat(valor1, “:”); msg_env=strcat(msg_env, valor2); write(RESTO, msg_env, strlen(msg_env)); //Envíamos mensaje de conexión . FD_SET(RESTO, &allset); //Se activa el descriptor del RESTO de la red. Solución óptima numero_resto=numero; } //Variable para actualizar la petición if (--nready<=0) break; else continue; } //Previsión para más de una interrupción else // (sizeof(int)) No es número de télefono, será respuesta... if (len=strlen(“SI”)) // Se puede comprobar que es lo esperado... if (strcmp (msg_rec,”SI”)==0){ // Es un SI j=0; while (conexiones[j]!=numeros[i]) j++; //Actualización de arrays de conexión conexiones[i] = numeros[j]; }else{ //Es un NO j=0; while (conexiones[j]!=numeros[i]) j++; //Actualización de arrays no conexión conexiones[j] = -1; } else break; //error else // De conexiones... ES TEXTO if (--nready<=0) continue;//Previsión para más de un operario simultáneo. } // Fin del for de atención a los usuarios C) Ahora las llamadas pueden ser externas... if (FD_ISSET(RESTO, &rset)) {//Para el caso de que uno de ellos escriba algo if ((len=read(RESTO, msg_rec,MSG_SIZE))!=0) // Va a ser un “NO” if (strcmp(msg_rec,“NO”)==0){ // Se puede comprobar que es lo esperado... FD_CLR (RESTO, &allset); j=0; while (conexiones[j]!=numero_resto) j++; //Actualización de arrays no conexión conexiones[j] = -1; }else continue; //del strcmp (caso de error), no es un “NO” y tampoco un SYN else{ //del read //Como se recibe len =0 y no puede ser un CLOSE, es el paquete SYN, con lo que deberemos hacer la //conexión. clilen=sizeof(cliaddr); connfd =accept(RESTO, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el operario for (i=0; i<(FD_SETSIZE); i++)//Actualización del array if (masclient[i]<0) {//Nuevo array, el viejo se queda pequeño masclient[i]=connfd; break; } FD_SET(connfd,&allset);//Actualización del conjunto de descriptores if (connfd > maxfd)//Actualización de variables maxfd=connfd;// Los operarios van después de las líneas if (i>maxi) maxi=i; Centro de Estudios NOVA – Cartagena 868-97-38-38 //Faltaría añadir la conexión y el número a los nuevos arrays j=0; while (conexiones[j]!=numero_resto) j++; conexiones2 [i] =numeros[j]; numeros2 [i]= numero_resto; FD_CLR (RESTO, &allset); if (--nready<=0) continue; } //Previsión para más de una interrupción que realmente no existe //En este último caso en la gestión de la comunicación (no se pide) debería incluir la gestión no //sólo de los arrays conexiones, numeros y client, sino que además habría que tener en cuenta los // arrays masclient, conexiones2 y numeros2. Centro de Estudios NOVA – Cartagena 868-97-38-38 Problema 2 (50% Ejercicios) Se pretende realizar un servidor NTP (servidor de tiempo de red) de forma que todas aquellas máquinas que ejecuten el cliente apropiado (que se puede descargar sin ningún tipo de restricción) se puedan sincronizar con esta máquina y se permita el mantenimiento de esta sincronización. La sincronización es necesaria por ejemplo para programación remota, en las que se evita generaciones de código posteriores en unas máquinas que en otras. En el funcionamiento de la aplicación una vez llega un nuevo cliente, éste no vuelven a transmitir nada más. Se pide: A) Explique razonadamente el tipo de protocolo a usar en esta aplicación. (10%) B) Realice el código necesario para que el servicio opere en el puerto 123 del servidor y sea capaz de mantener hasta 100 usuarios simultáneamente. Recuerde que el sistema será concurrente, por lo que prepárelo para ello. (10%) C) Realice el código necesario para que el servicio sea capaz de generar y enviar la información que ofrece esta aplicación, es decir, los valores temporales que sirvan de sincronización. Dicha información tiene esta estructura: struct timeval original | struct timeval actual El campo actual se obtiene de la ejecución del comando int gettimeofday(struct timeval *tv1, struct timezone *tv2) donde el parámetro tv1 devuelve la hora exacta, siendo original la hora exacta del momento en que llegó el nuevo cliente. (15%) D) Realice el código para que la aplicación sea capaz de mantener el servicio, reactualizando la información planteada cada 10 msec. de forma concurrente. (15%) Otra información: #define MSG_SIZE 32 //Tamaño de los mensajes #define Direccion_local “192.168.3.1” Considere los tiempos de ejecución de código despreciable. NOTA IMPORTANTE PARA LOS DOS PROBLEMAS: 1.- NO es necesario incluir las macros y librerías habituales en el código de la solución. #define MSG_SIZE 32 //Tamaño máximo de los mensajes #define Direccion_local “192.168.3.254” #define PUERTO_SERVIDOR 123 int main (int argc, char **argv) { int maxfd, listenfd, nready, i ; ssize_t len; fd_set rset; char msg_rec[MSG_SIZE]; // Mensajes recibidos socklen_t clilen; char *buff; struct timeval *original[100]; //Hora de funcionamiento original struct sockaddr_in direcciones[100]; //Los usuarios posibles… struct sockaddr_in servaddr, cliaddr; struct timeval *tv1,*tv2,*tvselect ; //La variables temporales que se harán necesarias // Inicializaciones temporales tv1->tv_sec=0; tv1->tv_usec=0; tv2->tv_sec=0; tv2->tv_usec=0; nready=0; //La preparación para 100 clientes consiste en abrir el puerto 123 y crear los arrays para 100 posiciones. for (i=0; i<100;i++){ //Inicialización de los arrays de clientes original[i]=NULL; bzero(&direcciones[i],sizeof(struct sockaddr_in)); } //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (Direccion_local); servaddr.sin_port=htons(PUERTO_SERVIDOR); Centro de Estudios NOVA – Cartagena 868-97-38-38 maxfd=-1 ; if((listenfd = socket(AF_INET,SOCK_DGRAM,0))<= 0) {printf ("ERROR \n"; exit(1);} if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))<0) {printf("ERROR \n"); exit (1);}; maxfd=listenfd; //Servidor UDP preparado msg_rec[0]=’\0’; gettimeofday(tv2, NULL); // Para el funcionamiento del timeout for(;;){ tvselect->tv_sec= 0; if (nready==0){ //se salio por timeout el valor son 10000 usecs tvselect->tv_usec=10000; tv1->tv_sec= tv2->tv_sec; tv1->tv_usec= tv2->tv_usec; } else //Si no es que expiró antes y hay que recalcular el timeout tvselect->tv_usec= 10000-(tv2->tv_usec-tv1->tv_usec); FD_SET(listenfd,&rset); nready=select (maxfd+1, &rset, NULL, NULL, tvselect); if (nready==0) //Salta timeout //reenviar información for (i=0, i<maxi, i++){ buff=NULL; buff=strcat(buff, (char *) original[i]); n=gettimeofday(tv2, NULL); buff=strcat(buff, (char *)tv2); sendto(listenfd, buff, strlen (buff), 0, (struct sockaddr*)&direcciones[i], sizeof (struct sockaddr_in)); }; if (FD_ISSET(listenfd, &rset)) { //Se recibe algo -> NUEVO CLIENTE recvfrom (listenfd, msg_rec, MSG_SIZE, 0, (struct sockaddr*)&cliaddr, sizeof (struct sockaddr_in)); gettimeofday(tv2, NULL); direcciones [maxi]=cliaddr; original[maxi]=tv2; maxi++; buff=NULL; buff=strcat(buff, (char *)tv2); buff=strcat(buff, (char *)tv2); sendto(listenfd, buff, strlen (buff), 0, (struct sockaddr*)&cliaddr, sizeof (struct sockaddr_in)); }; } //Fin for }//Fin main Centro de Estudios NOVA – Cartagena 868-97-38-38 Problema 1 (50% Ejercicios) 29 DE JUNIO DE 2010 Se diseña un servicio TCP concurrente en el que los clientes se conectan a este servicio para descargarse información de aplicaciones propias del servicio que se genera de forma aleatoria. El gráfico representa las conexiones de las aplicaciones: Entre las tareas del servicio, se encuentran: − Gestionar las conexiones permanentes con los clientes. Éstos se conectan cuando desean acceder a la información de las aplicaciones. Habrá un total de N conexiones de este tipo en el servidor. − Gestionar la conexión de las aplicaciones de lectura (independientes de la aplicación servidor) que generan la información aleatoria. Éstas se conectan en cualquier momento sin que exista ninguna situación predeterminada previa. Habrá un total de M conexiones de este tipo. − Gestionar la transmisión de la información al cliente. Se pide: A) Código de aceptación de conexiones de los clientes y aplicaciones. No olvide realizar el proceso de forma concurrente, utilizando un único select. Los clientes se encuentran necesariamente en máquinas remotas del servidor. (15%) B) Código para la lectura de la información de las aplicaciones y reenvío a los clientes de este contenido de forma concurrente. Una vez el servicio recibe la información desde las aplicaciones, se procede al envío a todos los clientes. La información reenviada estará compuesta por el número de la aplicación, que es tomado del número de puerto de ésta, seguida de la información recibida según formato “PUERTO:INFORMACION”. Se mantendrá el uso de un único select. (15%) C) Ahora suponga que los clientes quieren que el servidor les filtre la información de forma que no reciban la información de todas las aplicaciones, sino sólo de algunas que cada cliente determine. Por ello, realice el código que permite que el cliente determine las aplicaciones de las que requiere la información mediante un paquete del formato “FILTRAR:PUERTO1:PUERTO2… :PUERTOM”, donde PUERTOX es el número de puerto de las aplicaciones en las que está interesado. (20%) Otra información: #define N 20 #define M 30 #define SERVIDOR “192.168.3.254” //Suponga que el servidor no tiene ningún otro inferfaz #define SERVER_PORT 6999 #define MSG_SIZE 256 // Tamaño máximo para todos los mensajes #define N 20 #define M 30 #define SERVIDOR “192.168.3.254” //Suponga que el servidor no tiene ningún otro inferfaz #define SERVER_PORT 6999 #define MSG_SIZE 256 // Tamaño máximo para todos los mensajes int main (int argc, char **argv) { int i, j, maxi, maxj, maxfd, listenfd, connfd, sockfd1, nready, numero, valor; int client [N]; // Array de descriptores de los usuario int descriptores_aplicaciones[M]; //Se crea un array de “descriptores locales” para los servicios. int aplicaciones[M]; Array de los puertos de las aplicaciones. char* relaciones[N];//Array de relaciones de clientes, para cuando los clientes indiquen filtrar(apartado C) ssize_t len; buff[MSG_SIZE]; //Buffer para generar los mensajes fd_set rset, allset; char msg_rec[MSG_SIZE]; // Mensaje recibido socklen_t clilen; struct sockaddr_in cliaddr, servaddr; //Arranque del servidor concurrente if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) {printf ("ERROR \n"; exit(1);} bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); //htonl(INADDR_ANY); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) {printf("ERROR \n"); exit (1);} listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes Centro de Estudios NOVA – Cartagena 868-97-38-38 client[i]=-1; relaciones[i]=NULL; } for(i=0; i<M; i++){ aplicaciones [i]=-1; descritpres_aplicaciones[i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas //Ahora la conexiones de clientes y aplicaciones if ( FD_ISSET(listenfd, &rset){ // NUEVO no se puede distinguir si es cliente o aplicación clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario if (!strcmp(inet_ntoa(cliaddr.sin_addr), SERVIDOR)){ //Es un cliente no aplicación for (i=0; i<N; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; if (i>maxi) maxi=i; else{ // Es una aplicación no cliente for (i=0; j<M; j++) //Actualización del array if (descriptores_aplicaciones[j]<0) { descriptores_aplicaciones[j]=connfd; break; } aplicaciones[j]=ntohs(cliaddr.sin_port); //Guardamos el puerto; FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; if (i>maxj) maxj=j; }; if (--nready<=0) continue; //Previsión para más de una interrupción } for (j=0; j<maxj; j++){ if ((sockfd1 =descriptores_aplicaciones[j])<0) // Se van a comprobar todas los clientes continue; if (FD_ISSET(sockfd1, &rset)) //Se recibe algo if((len=read(sockfd1, msg_rec, MSG_SIZE-strlen((char *)&aplicaciones[j])-1)> 0) //Se lee. No es vacío msg_env[0]=’\0’; msg_env=strcat(msg_env, (char *)&aplicaciones[j]); msg_env=strcat(msg_env, “:”); msg_env=strcat(msg_env, msg_rec); for (i=0,i>maxi, i++) if (client[i]>0){ if(listado=strdup(relaciones[j])!=NULL); //HAY Filtrado for (k=0, k<maxj, k++){ if (strtol(listado, listado, 10)==aplicaciones [k]){ write(client[i], msg_env, strlen(msg_env)); break; } listado++; Centro de Estudios NOVA – Cartagena 868-97-38-38 if (listado==NULL) break; } }; }; //CÓDIGO atención clientes (filtrado). for (i=0; i<maxi; i++){ if ((sockfd1 =client[i])<0) // Se van a comprobar todas los clientes continue; if (FD_ISSET(sockfd1, &rset)) //Se recibe algo if((len=read(sockfd1, msg_rec, MSG_SIZE))> 0) //Se lee. No es vacío msg_rec=strchr(msr_rec, ‘:’)++; relaciones[i]=strdup(msg_rec); } // FIN ATENCIÓN CLIENTES if(listado=strdup(relaciones[j])!=NULL); //HAY Filtrado for (k=0, k<maxj, k++){ if (strtol(listado, listado, 10)==aplicaciones [k]){ break; } listado++; if (listado==NULL) break; } }// Fin for } // Fin main Centro de Estudios NOVA – Cartagena 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 28 DE JUNIO DE 2005 – EJERCICIO 1 Se diseña un servidor central concurrente TCP (puerto 5999) destinado al servicio de telefonía móvil. El servicio acepta la conexión de los distintos clientes y los mantiene conectados mientras no reciba la acción de desconexión. El gráfico representa el sistema una vez conectados los usuarios: Entre las tareas del servidor, se encuentran: − Gestionar las conexiones (sockets) permanentes con los usuarios. Éstos se conectan cuando desean tener acceso a la red. Habrá un total de N conexiones de este tipo. − Gestionar la conexión con el resto de la red a través del descriptor 500 (fijo y siempre activo). − Gestionar el establecimiento de las conexiones entre usuarios inscritos en el propio servidor y entre usuarios locales y externos. Cuando un usuario desea la conexión manda un número destino. Con el número, se comprueba si el destino pertenece a este servidor y, en caso negativo, se manda al resto de la red, estableciéndose, en su caso, la comunicación a través de un nuevo descriptor para el usuario destino. No se contempla el caso de peticiones desde usuarios externos. Se pide: A) Código para aceptar la conexión de los usuarios y proporcionar la concurrencia. El “resto de la red” está conectada desde el arranque del servidor. (20%) B) Código de establecimiento de las llamadas entre los usuarios locales. No incluya la gestión de las llamadas una vez establecidas, sólo lo referente a la recepción de los números, y petición al usuario destino (con el número origen) y espera de la respuesta (no hay que responder al usuario origen). La conexión se realiza si se recibe un “SI” y no se realiza si es un “NO”. (20%) C) Código de establecimiento de las llamadas con usuarios remotos. En este caso las peticiones hay que encaminarlas por el descriptor “resto de la red”. La respuesta en este caso puede ser un “NO” si es negativa con lo que se rechaza, y en caso afirmativo se recibe un paquete de conexión SYN a través del descriptor “resto de la red”. En este caso se procederá a la conexión del nuevo cliente y pasará a ser un usuario local más. No hay que responder al cliente origen.(20%) Otra información: #define N 20 #define SERVIDOR “192.168.5.254” #define N 20 #define LISTENQ N // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N a la vez. #define SERVER_PORT 5999 //Dato #define SERVIDOR “192.168.5.254” #define RESTO 500 //Descriptor del resto de la red #define MSG_SIZE 1024 //No se dice nada, mínimo mayor de 4 int main (int argc, char **argv) { int i, j, maxi, maxfd, listenfd, connfd, sockfd, sockfd1, nready,*numero, numero_resto; int client [N]; // Array de descriptores de cada línea int numeros[N]; //Posición-client<->Número int conexiones [N]; //Relacionales entre números int masclient [FD_SETSIZE]; //Para clientes externos que se conecten (apartado C) int conexiones2 [FD_SETSIZE];//Para los relacionales de los externos (apartado C) int numeros2 [FD_SETSIZE];// Para los números externos (apartado C) ssize_t len; Centro de Estudios NOVA – Cartagena fd_set rset, allset; char * msg_env; //Mensaje enviado de conexión char * msg_rec; // Mensaje recibido de conexión char *valor1, *valor2; //Auxiliares para formar mensaje socklen_t clilen; struct sockaddr_in cliaddr, servaddr; if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"; exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; numeros[i]=-1; conexiones [i]=-1; } FD_ZERO(&allset); FD_ZERO(&rset); //Inicialización de los conjuntos de descriptores //Preparado para aceptar la entrada de los posibles clientes. FD_SET(listenfd, &allset); for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //USUARIO NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario for (i=0; i<MAXOPERARIO; i++)//Actualización del array if (client[i]<0) { client[i]=connfd; break; 868-97-38-38 Centro de Estudios NOVA – Cartagena } FD_SET(connfd,&allset);//Actualización del conjunto de descriptores if (connfd > maxfd)//Actualización de variables maxfd=connfd;// Los operarios van después de las líneas if (i>maxi) maxi=i; if (--nready<=0) continue;//Previsión para más de una interrupción } /* ESPACIO RESERVADO PARA EL PROCESADO DEL SERVIDOR*/ } //Del bucle principal } //Del main B) /* CÓDIGO SERVIDOR PARA ATENDER LAS PETICIONES DE CADA CLIENTE*/ for (i=0; i<=maxi; i++){ if ((sockfd1 =client[i])<0) continue; // Se van a comprobar todos los CLIENTES LOCALES if (FD_ISSET(sockfd1, &rset))//Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec,MSG_SIZE))==0){ close(sockfd1); FD_CLR(sockfd1,&allset); client[i]=-1; numeros[i]=-1; if (maxi==i) --maxi; } if (--nready<=0) break; else continue; else{ if (conexiones[i]<0) //Conexiones. No conversación. if (len==sizeof(int)){ // Se recibe un número de teléfono if (numeros[i]<0){ numero=(int *) &msg_rec; numeros[i]=*numero;// Actualización número propio if (--nready<=0) break; else continue; }else{// No es el número propio, es el destino numero=(int *) &msg_rec; conexiones[i]=numero;// Actualización conexiones j=0; while (numeros[j]!=numero) j++; if (j<20) //Número local write(client[j], &numeros[i], sizeof(numeros[i])); if (j>=20){ //Número externo valor1=(char*)&numeros[i]; 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 valor2=(char *)conexiones[i]; msg_env=strcat(valor1, “:”); msg_env=strcat(msg_env, valor2); write(RESTO, msg_env, strlen(msg_env)); //Envíamos mensaje de conexión . FD_SET(RESTO, &allset); //Se activa el descriptor del RESTO de la red. Solución óptima numero_resto=numero; } //Variable para actualizar la petición if (--nready<=0) break; else continue; } //Previsión para más de una interrupción else // (sizeof(int)) No es número de télefono, será respuesta... if (len=strlen(“SI”)) // Se puede comprobar que es lo esperado... if (strcmp (msg_rec,”SI”)==0){ // Es un SI j=0; while (conexiones[j]!=numeros[i]) j++; //Actualización de arrays de conexión conexiones[i] = numeros[j]; }else{ //Es un NO j=0; while (conexiones[j]!=numeros[i]) j++; //Actualización de arrays no conexión conexiones[j] = -1; } else break; //error else // De conexiones... ES TEXTO if (--nready<=0) continue;//Previsión para más de un operario simultáneo. } // Fin del for de atención a los usuarios C) Ahora las llamadas pueden ser externas... if (FD_ISSET(RESTO, &rset)) {//Para el caso de que uno de ellos escriba algo if ((len=read(RESTO, msg_rec,MSG_SIZE))!=0) // Va a ser un “NO” if (strcmp(msg_rec,“NO”)==0){ // Se puede comprobar que es lo esperado... FD_CLR (RESTO, &allset); j=0; while (conexiones[j]!=numero_resto) j++; //Actualización de arrays no conexión conexiones[j] = -1; }else continue; //del strcmp (caso de error), no es un “NO” y tampoco un SYN else{ //del read //Como se recibe len =0 y no puede ser un CLOSE, es el paquete SYN, con lo que deberemos hacer la //conexión. clilen=sizeof(cliaddr); connfd =accept(RESTO, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el operario for (i=0; i<(FD_SETSIZE); i++)//Actualización del array if (masclient[i]<0) {//Nuevo array, el viejo se queda pequeño masclient[i]=connfd; break; } Centro de Estudios NOVA – Cartagena FD_SET(connfd,&allset);//Actualización del conjunto de descriptores if (connfd > maxfd)//Actualización de variables maxfd=connfd;// Los operarios van después de las líneas if (i>maxi) maxi=i; //Faltaría añadir la conexión y el número a los nuevos arrays j=0; while (conexiones[j]!=numero_resto) j++; conexiones2 [i] =numeros[j]; numeros2 [i]= numero_resto; FD_CLR (RESTO, &allset); if (--nready<=0) continue; } //Previsión para más de una interrupción que realmente no existe //En este último caso en la gestión de la comunicación (no se pide) debería incluir la gestión no //sólo de los arrays conexiones, numeros y client, sino que además habría que tener en cuenta los // arrays masclient, conexiones2 y numeros2. 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Problema 2 (40%) Se diseña un servicio de telefonía móvil, en el que la estación base ofrece servicios muy diversos, entre los que se encuentran los mensajes de texto. Se pretende realizar el software de los terminales móviles, de manera que sean capaces en enviar y recibir los mensajes de texto referentes a este servicio. De esta forma, el sistema consiste en atender simultáneamente al usuario del teléfono móvil por si desea mandar el mensaje y atender al servidor por si es éste el que nos manda el mensaje de otro usuario. Se pide: A) Realice el código necesario de conexión del terminal al servidor y mantener la concurrencia entre el usuario local y el servidor conectado. (20%) B) Realice el código para mandar y recibir mensajes. En caso del envío, el teléfono incluye en el mensaje el propio número de teléfono de la forma: “Número_propio:mensaje”. De igual forma, en la recepción se recibe un mensaje del mismo tipo y se deben separar los dos campos, mostrando sólo el mensaje por la pantalla. (20%) Otra información: #define MSG_SIZE 1024 //Tamaño máximo de los mensajes #define PUERTO 3000 #define SERVIDOR “192.168.5.254” #define SERVER_PORT 3000 //Dato #define SERVIDOR “192.168.5.254” //Dato #define MSG_SIZE 1024 //Se sobreentiende que completo #define TECLADO 0 // Descriptor para el teclado #define PANTALLA 1 // Descriptor para la pantalla int main (int argc, char **argv) { int maxfd, listenfd, numero, sockfd1; char* numero propio; ssize_t len; fd_set rset, allset; char * msg_env; //Mensaje enviado de conexión char * msg_rec; // Mensaje recibido de conexión char *valor1, *valor2; //Auxiliares para formar mensaje socklen_t clilen; struct sockaddr_in servaddr; numero_propio= argv[1]; //Por ejemplo, se puede pasar así, no viene especificado en el enunciado. if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(connect(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } Centro de Estudios NOVA – Cartagena 868-97-38-38 if (listenfd>TECLADO) maxfd=listenfd; else maxfd =TECLADO; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; numeros[i]=-1; conexiones [i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); //Socket hay que activarlo FD_SET(TECLADO, &allset); //Teclado hay que activarlo for(;;){ rset=allset; select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset)){ //SERVIDOR //ATENCIÓN A LA INTERRUPCIÓN DEL SERVIDOR, se recibe mensaje if (len=read(listenfd, msg_rec, MSG_SIZE)==0) // Caso de cierre close (listendfd); else{ // Mensaje msg_env=strchr(msg_rec, “:”); //Buscamos los “:” write(PANTALLA, msg_env++, strlen(msg_env)); } } if ( FD_ISSET(TECLADO, &rset)){ //USUARIO //ATENCIÓN A LA INTERRUPCIÓN DEL USUARIO, se tx mensaje if (len=read(TECLADO, msg_rec, MSG_SIZE-strlen(numero_propio)-1)==0){ // Caso de cierre close (listendfd); exit(1); }else{ // Mensaje valor=numero_propio; msg_env=strcat(valor, “:”); //Ponemos los “:” msg_env=strcat(msg_env,msg_rec); // Componemos el mensaje write(listenfd, msg_env, strlen(msg_env)); // Y tx al servidor } } } Fin del for } Fin del main Centro de Estudios NOVA – Cartagena 868-97-38-38 2 DE FEBRERO DE 2010 Problema 1 (50% Ejercicios) Se diseña un servicio TCP concurrente en el que el los clientes se conectan para acceder a información que están generando aplicaciones que corren de manera autónoma en la misma máquina en el que se ejecuta el servicio. El gráfico representa las conexiones de las aplicaciones: Entre las tareas del servicio, se encuentran: − Gestionar las conexiones permanentes con los clientes. Éstos se conectan cuando desean acceder a una aplicación. Habrá un total de N conexiones de este tipo en el servidor. − Gestionar la conexión con las aplicaciones suministradoras de información (independientes de la aplicación servidor). Éstas son conectadas cuando un cliente pide su contenido. − Gestionar la devolución del contenido a los clientes. También se realiza la gestión de un estadístico. Se pide: A) Código de aceptación de conexiones de los clientes y lectura del nombre de la aplicación de la que se lee la información. Este proceso se realiza de forma concurrente, con un único select. (20%) B) Código para la comunicación con las aplicaciones y lectura de su contenido. Una vez el servicio recibe el nombre de la aplicación, se procede a la conexión con la aplicación de lectura y lectura de los datos y transmisión de su contenido de forma concurrente. Para saber qué aplicación ha de vincularse, el nombre de la aplicación lleva un número en su nombre según formato: “nombreXX” que indica el número de aplicación que lee ese contenido. Se mantendrá el uso de un único select. (20%) C) Código para componer la temporización del tiempo de servicio. Para ello discretize el tiempo por una décima de segundo y obtenga el instante de comienzo de petición del cliente y el de devolución de la información. El tiempo será la diferencia de ambos. Informe de este tiempo en un mensaje explícito al usuario. (10%) Otra información: #define N 20 #define M 29 #define SERVIDOR “192.168.5.254” #define SERVER_PORT 2399 #define MSG_SIZE 256 // Tamaño máximo para todos los mensajes. #define APLICATTION_PORT 300M //Siendo M el número de la aplicación [3001..30M] #define N 20 //dato #define M 29 //dato #define LISTENQ N // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N a la vez. #define SERVER_PORT 2399 //Dato #define APLICACION_PORT 3000 Centro de Estudios NOVA – Cartagena 868-97-38-38 #define SERVIDOR “192.168.3.254” #define MSG_SIZE 256 //Tamaños maximos int main (int argc, char **argv) { int i, j, maxi, maxfd, listenfd, connfd, sockfd1, nready, numero, valor; int client [N]; // Array de descriptores de los usuario int descriptores_aplicaciones[M]; //Se crea un array de “descriptores locales” para las aplicaciones. // char *aplicaciones[M]; Nombres de aplicaciones. No hace falta guardarlos. // int relaciones[N]; //Array de relaciones de clientes, para relacionar cliente->aplicacion int clientes_destinos[M]; // Como para el apartado C hay que relacionar directamente todo, se hace un segundo // array de relaciones aplicación->cliente long tiempo_final[N]; //Para el cálculo del retardo de servicio long tiempo_comienzo[N[; //Guardar tiempo de apertura del servicio ssize_t len; char buff[MSG_SIZE]; fd_set rset, allset; char msg_rec[MSG_SIZE]; // Mensaje recibido socklen_t clilen; struct sockaddr_in cliaddr, servaddr; struct timeval tv; //Arranque del servidor concurrente if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0){ printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; clientes_destinos[i]=-1; descriptores_servicios[i]=-1; tiempo_final[i]=0; buff[i][0]=’\0’; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); Centro de Estudios NOVA – Cartagena 868-97-38-38 FD_SET(listenfd, &allset); for(;;){ rset=allset; tv.tv_sec=0; tv.tv_usec=100000; //Discretización 1 décima de segundo nready=select (maxfd+1, &rset, NULL,NULL,&tv); //Bloqueo en espera de entradas if (nready==0){ //Salida de Timeout, if(maxi<0){ // Si no hay clientes no hay que hacer nada, ni siquiera contar, se está en espera. contador=0; break; } contador++; //Ha pasado 1 décima }; //Ahora la conexiones de clientes if ( FD_ISSET(listenfd, &rset)){ //USUARIO NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario for (i=0; i<N; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; // Los operarios van después de las líneas if (i>maxi) maxi=i; if (--nready<=0) continue; //Previsión para más de una interrupción } // En esta parte se leen los nombres de las aplicaciones. for (j=0; j<N; j++){ if ((sockfd1 =client[j])<0) // Se van a comprobar todas los clientes continue; if (FD_ISSET(sockfd1, &rset)) //Se recibe algo if((len=read(sockfd1, msg_rec, MSG_SIZE))> 0){ //Se lee. No es vacío //Hay que extraer el valor XX if ((msg_rec[len-2] != ‘1’) && (msg_rec[len-2] != ‘2’)) //Se ve si el penúltimo dígito es 1 ó 2: numero =atoi (msg_rec[len-1]); else numero = atoi (msg_rec[len-2]); //Si hay que considerarlo. if (aplicaciones[numero]<0){ //NO ESTÁ OCUPADO (Si lo está no se pide solución) if ((descriptores_servicios[numero]=socket(AF_INET, SOCK_STREAM, 0))<= 0) { printf ("ERROR \n"); exit(1); } Centro de Estudios NOVA – Cartagena 868-97-38-38 servaddr.sin_port=htons(APLICACION_LECTURA00+numero); //IMPORTANTE PARA CONECTARSE if ((connect (descriptores_aplicaciones[numero], (struct sockaddr*)&servaddr, sizeof(struct sockaddr))<0){ printf ("ERROR \n"); exit(1); } //No se dice si las aplicaciones son concurrentes o no //La aplicación ya está conectada, actualizamos variables. FD _SET(descriptores_aplicaciones[numero],&allset); if (descriptores_aplicaciones[numero]>maxfd) maxfd = descriptores_aplicaciones[numero]; //Preparado select para escuchar clientes_destino[numero]=client[i]; //Para las operaciones relacionales. //TIEMPOS tiempo_comienzo[numero]=contador; tiempo_final[numero]=0; //Inicializacion tiempo }else{ //Cierre del cliente }; //NO SE PIDE } } } for (j=0; j<M; j++){ if ((sockfd1 =descriptores_aplicaciones[j])<0) // Se van a comprobar todas las aplicaciones continue; if (FD_ISSET(sockfd1, &rset)) { //Se recibe algo -> COMPUTAR TIEMPO if((len=read(sockfd1, buff, MSG_SIZE))> 0){ //Se lee. No es vacío valor=strlen(buff); strncat(buff[i],msg_rec, valor)); if ((valor+strlen(msg_rec))=>MSG_SIZE){ write(clientes_destino[j], buff, strlen(msg_rec)); if (tiempo_final[j]==0){ //Hay que hacer calculo temporal tiempo_final[j]= contador; if (--nready<=0) continue; else break; } retardo=tiempo_final[j]-tiempo_inicial[j]; write(clientes_destino[j],&retardo,strlen(retardo)); if (--nready<=0) continue; } //Previsión para más de una interrupción } } } } //FIN del bucle principal for } //FIN del main Centro de Estudios NOVA – Cartagena 868-97-38-38 Problema 2 (50% Ejercicios) Se pretende realizar un programa test de un servidor concurrente TCP que es un repositorio de logins y passwords (un repositorio de identidades). El servidor consiste en que recibido un login indica si ese usuario existe y en su caso indica si el password recibido es correcto o no (acepta un formato de mensajes: “login:password”). La respuesta del servidor puede ser “OK”, “DESCONOCIDO” (no se conoce el login) o “FALLO” (fallo del password) y a continuación se cierra la conexión. El test consiste en (1) comprobar el funcionamiento del servidor (responde a logins y passwords de manera adecuada), (2) se pueden realizar un número de conexiones elevado simultáneas como todo servidor concurrente, y (3) comprobar el retardo que ofrece el servidor al cliente. Para ser más precisos se supone que la aplicación test y el servidor operan en la misma máquina. El test necesita una identidad de prueba veraz para poder realizar el test, supóngase que se reciben login y password como argumentos 1 y 2 de la línea de comando respectivamente. Se pide: A) Realice el código necesario para realizar la comprobación (1). Se trata pues de acceder, y hacer dos peticiones una que se supone correcta y otra que no. La comprobación consistirá en ver que efectivamente se recibe “OK” y otro valor en el segundo caso. Para la primera petición se utilizará el login y password leído de la línea de comando, y para la segunda otra combinación aleatoria para login y password de 6 caracteres cada uno. Atiéndalos de forma concurrente. En caso de error se muestra por pantalla “ERROR 1” y se continúa el test. (15%) B) Realice el código necesario para realizar la comprobación (2). Se realiza, utilizando el número máximo de sockets creables (no suponga restricciones del sistema operativo), de realizar conexiones con el servidor, de forma que aproximadamente el 50% pida autorización positiva y el 50% negativas. Atiéndalos de forma concurrente. Se comprobará que efectivamente no quede ningún socket abierto pasados 10 minutos desde la última respuesta recibida. En caso de error se muestra por pantalla “ERROR 2”. (25%) C) Por último, realice el código necesario para realizar la comprobación (3). Modifique el código del apartado B para que se pueda calcular el tiempo que tarda cada servicio (se admite un error tolerable de 0,1 segundos). Conforme se reciban las respuestas se va calculando la media y se muestra por pantalla. (10%) Otra información: #define MSG_SIZE 33 //Tamaño máximo de los mensajes #define servidor_port 5000 //Puerto de funcionamiento del servidor NOTA IMPORTANTE PARA LOS DOS PROBLEMAS: 1.- NO es necesario incluir las macros y librerías habituales en el código de la solución. #define MSG_SIZE 33 //Tamaño máximo de los mensajes #define servidor_port 6200 //Puerto de funcionamiento del servidor #define N FD_SETSIZE-3 // N=número de clientes según apartado B es el máximo menos 3. int main (int argc, char *argv[]){ int maxfd, sockfd1, i ,j, k, contador, contadorC; ssize_t len; fd_set rset, allset; char msg_rec[MSG_SIZE]; // Mensajes recibidos char buff[13]; //Para la gestión de login y passwords aleatorios char* msg_envA ; socklen_t clilen; struct sockaddr_in servaddr; time *s; int cerrrados=0; struct timeval tv; //La variable temporal de 10 segundos int abiertos; int retardomedio=0; Centro de Estudios NOVA – Cartagena FD_ZERO(&rset); bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (“127.0.0.1”); servaddr.sin_port=htons(servidor_port); maxfd=-1 ; //Se utilizan los datos de entrada para generar un mensaje de petición char* user=strdup(arg[1]); char* user_pass=strdup(arg[2]); char* msg_envA=strcat(user, “:”); msg_env=strcat(msg_envA, user_pass); msg_rec[0]=’\0’; msg_rec_old[0]=’\0’; abiertos=0; FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); //Se deben generar N clients (para el apartado A dos son suficientes) for (i=0; i=N; i++){ //NECESARIO APARTADO B if ((sockfd[i] = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } connect(sockfd[i], (struct sockaddr*) &sevaddr, sizeof(struct sockaddr)); // Siendo una aplicación que utiliza un servicio (un cliente), no se realiza la funcion bind. if (i%2==0){ //Los pares se toman como correctos (50%) if(write(sockfd[i], msg_env, strlen(msg_envA))<0) { printf("ERROR \n"); exit (1); }; }else{ //los impares serán aleatorios srand(time(s)/2); // Se pone la semilla for (j=0; j=13; j++) if(j==6) buff[j]=’:’; else { valor=atoi(a)+rand()%26; buff[j]=char(valor); } if(write(sockfd[i], buff, strlen(buff))<0) { printf("ERROR \n"); exit (1); }; } FD_SET(sockfd[i],&allset); //Socket hay que activarlo, pues espera algo. if(listenfd >maxfd) maxfd= listenfd; 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 if (i>maxi) maxi=i; }; contador=0; for(;;){ rset=allset; tv.tv_sec=600; //Tiempo de 600 segundos (diez minutos) tv.tv_usec=0; //Para el apartado (C) tv.tv_sec=0; //Tiempo de 600 segundos son 6000 décimas. tv.tv_usec=1000; nready=select (maxfd+1, &rset, NULL,NULL,&tv); //Bloqueo en espera de entradas //TIME-calculo de tiempo para retardo. Apartado C. if (nready==0){ //Salida de Timeout, contadorC++; contador++; if (contador==6000) if(maxi<0){ // Si no hay clientes se ha hecho bien. exit(1); } //No se indica qué realizar, parece que se puede terminar el test else printf(“ERROR 2\n”); //CASO DE ERROR 2 } }else { //Se lee valor for (j=0; j<N; j++){ //Para apartado B if ((sockfd1 =sockfd[j])<0) // Se van a comprobar todas las aplicaciones continue; if (FD_ISSET(sockfd1, &rset)) { if((len=read(sockfd1, buff, MSG_SIZE))> 0){ //Se lee. //Se realiza la comprobación 1 if ((j%2)==0) //Debería ser OK if (strcmp(buff, “OK”)!=0) printf (“ERROR 1\n”); else //Debería ser algún tipo de error if (strcmp(buff, “OK”)==0) printf (“ERROR 1\n”); retardomedio=(retardomedio*cerrados+contadorC)/(cerrados+1) printf(“El retardo ha sido %d segundos y %d décimas de segundos\n”, retardomedio/10, retardomedio%10); cerrados++; contador==0; if (j==maxi) while(sockfd[maxi]>0) maxi--; if (sockfd1==maxfd) maxfd--; FD_CLR(sockfd1, &allset); continue; } } } //Fin for Centro de Estudios NOVA – Cartagena }//Fin main 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 30 DE JUNIO DE 2009 Problema 1 (50% Ejercicios) Se diseña un servicio TCP concurrente temporizado en el que el los clientes se conectan a este servicio para descargarse información de unos archivos propios del servicio, pero con un tiempo límite de descarga, para asegurar que la copia descargada es la versión correcta, ya que pasado ese tiempo es posible que sean actualizados. El gráfico representa las conexiones de las aplicaciones: Entre las tareas del servicio, se encuentran: − Gestionar las conexiones permanentes con los clientes. Éstos se conectan cuando desean acceder a un archivo. Habrá un total de N conexiones de este tipo en el servidor. − Gestionar la conexión con las aplicaciones de lectura (independientes de la aplicación servidor) que leen el contenido de los archivos (o ficheros). Éstas son conectadas cuando un cliente pide el contenido del archivo que estas leen. − Gestionar la descarga del fichero por el cliente. Se pide: A) Código de aceptación de conexiones de los clientes y lectura del nombre del archivo a enviar por parte del servicio, que se transmite en el primer mensaje que llega del cliente. No olvide realizar el proceso de forma concurrente, utilizando un único select. (20%) B) Código para la lectura de archivos y envío concurrente de su contenido. Una vez el servicio recibe el nombre del archivo, se procede a la conexión con la aplicación de lectura y se transmite su contenido de forma concurrente con mensajes de tamaño máximo. Para saber qué aplicación ha de vincularse, el nombre del archivo lleva un número en su nombre según formato: “nombreXX” que indica el número de aplicación que lee ese contenido. Se mantendrá el uso de un único select. (15%) C) Código para componer la temporización del tiempo válido de descarga de los archivos, que dependerá del tamaño que tenga cada uno. Para ello discretize el tiempo por una décima de segundo y obtenga el instante de comienzo de lectura de cada archivo. Una vez abierto el archivo, obtenga el tamaño del archivo, que suponga se lee en la primera lectura de la aplicación, y calcule el tiempo máximo para su descarga tomando valor de velocidad 300Kbps. Si no se ha completado la transmisión en ese tiempo se reinicia el proceso de transmisión. Suponga que la operación de lectura y transmisión de un paquete es de una centésima de segundo y los demás retardos de código son despreciables. (15%) #define N 20 //dato #define LISTENQ N // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N a la vez. #define SERVER_PORT 2399 //Dato #define APLICACION_LECTURA00 3500 #define SERVIDOR “192.168.3.254” #define MSG_SIZE 256 //Tamaños maximos int main (int argc, char **argv) { int i, j, maxi, maxfd, listenfd, connfd, sockfd1, nready, numero, valor; int client [N]; // Array de descriptores de los usuario int descriptores_servicios[M]; //Se crea un array de “descriptores locales” para los servicios. Centro de Estudios NOVA – Cartagena 868-97-38-38 // char *aplicaciones[M]; Nombres de aplicaciones. No hace falta guardarlos. int relaciones[N]; //Array de relaciones de clientes, para relacionar cliente->aplicacion int clientes_destinos[M]; // Como para el apartado C hay que relacionar directamente todo, se hace un segundo // array de relaciones aplicación->cliente long tiempo_final[N]; //Para el cálculo del tiempo_final de transmisión long tiempo_comienzo[N[; //Guardar tiempo de apertura del servicio ssize_t len; buff[N][256]; fd_set rset, allset; char msg_rec[MSG_SIZE]; // Mensaje recibido socklen_t clilen; struct sockaddr_in cliaddr, servaddr; struct timeval tv; //Arranque del servidor concurrente if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; clientes_destinos[i]=-1; descriptores_servicios[i]=-1; tiempo_final[i]=0; buff[i][0]=’\0’; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); for(;;){ rset=allset; tv.tv_sec=0; tv.tv_sec=100000; //Discretización 1 décima de segundo nready=select (maxfd+1, &rset, NULL,NULL,&tv); //Bloqueo en espera de entradas Centro de Estudios NOVA – Cartagena 868-97-38-38 //TIMEOUT if (nready==0){ //Salida de Timeout, if(maxi<0){ // SI no hay clientes no hay que hacer nada, ni siquiera contar, se está en espera. contador=0; break; } contador=contador+10; //Ha pasado 1 décima que son 10 centésimas }; //Ahora la conexiones de clientes if ( FD_ISSET(listenfd, &rset){ //USUARIO NUEVO +TIEMPO DESPRECIABLE clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario for (i=0; i<N; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; // Los operarios van después de las líneas if (i>maxi) maxi=i; if (--nready<=0) continue; //Previsión para más de una interrupción } // En esta parte se leen los nombres de las aplicaciones. for (j=0; j<N; j++){ if ((sockfd1 =client[j])<0) // Se van a comprobar todas los clientes continue; if (FD_ISSET(sockfd1, &rset)) //Se recibe algo +TIEMPO DESPRECIABLE if((len=read(sockfd1, msg_rec, MSG_SIZE))> 0){ //Se lee. No es vacío if (relaciones[N]<0) { //Es el primer mensaje… luego nos interesa el número de puerto //Hay que extraer el valor XX if ((msg_rec[len-2] != ‘1’) && (msg_rec[len-2] != ‘2’)) //Se ve si el penúltimo dígito es 1 ó 2: numero =atoi (msg_rec[len-1]); else numero = atoi (msg_rec[len-2]); //Si hay que considerarlo. //Ahora se pasa a establecer servicio con la aplicación, el “numero” calculado es muy práctico… if (descriptores_servicios[numero]<0){ //NO ESTÁ OCUPADO (Si lo está no se pide solución) if ((descriptores_servicios[numero]=socket(AF_INET, SOCK_STREAM, 0))<= 0) { printf ("ERROR \n"); exit(1); } servaddr.sin_port=htons(APLICACION_LECTURA00 +numero); //IMPORTANTE PARA CONECTARSE if ((connect (descriptores_servicios[numero], (struct sockaddr*)&servaddr, sizeof(struct sockaddr))<0){ printf ("ERROR \n"); exit(1); Centro de Estudios NOVA – Cartagena 868-97-38-38 } //No se dice si las aplicaciones son concurrentes o no //La aplicación ya está conectada, actualizamos variables. FD_SET(descriptores_servicios [numero],&allset); if (descriptores_servicios[numero]>maxfd) maxfd = descriptores_servicios[numero]; //Preparado select para escuchar relaciones[i]=descriptores_servicios[numero]; //Luego para las respuestas clientes_destino[numero]=client[i]; //Para las operaciones relacionales. //TIEMPOS tiempo_comienzo[numero]=contador; tiempo_final[numero]=0; } else //CUANDO SE RECIBAN DATOS DEL CLIENTE. NO SE PIDE {} }else //Cierre del cliente { }; //NO SE PIDE } } for (j=0; j<M; j++){ if ((sockfd1 =descriptores_aplicaciones[j])<0) // Se van a comprobar todas las aplicaciones continue; if (FD_ISSET(sockfd1, &rset)) { //Se recibe algo -> COMPUTAR TIEMPO if((len=read(sockfd1, msg_rec, MSG_SIZE))> 0){ //Se lee. No es vacío if (tiempo_final[j]==0){ //Es el tamaño tiempo_final[j]= contador+(int) (8*atoi(msg_rec)/3000); if (--nready<=0) continue; else break; } if (tiempo_final[j]<contador){ valor=strlen(buff[i]); strncat(buff[i],msg_rec,MSG_SIZE-valor)); if ((valor+strlen(msg_rec))=>MSG_SIZE){ //Si al añadir sobrepasamos el tamaño max. write(clientes_destino[j], buff[i], strlen(msg_rec)); strcpy(buff[i], msg_rec[MSG-SIZE-valor-1]); buff[i][MSG-SIZE-valor]=’\0’; } contador++; } }else{ FD_CLR(sockfd1, &allset); close(sockfd1); } if ((descriptores_servicios[numero]=socket(AF_INET, SOCK_STREAM, 0))<= 0) { printf ("ERROR \n"); exit(1); Centro de Estudios NOVA – Cartagena 868-97-38-38 } FD_SET (descriptores_servicios[numero], &allset); servaddr.sin_port=htons(APLICACION_LECTURA00 +j); //IMPORTANTE PARA CONECTARSE if ((connect (descriptores_servicios[numero], (struct sockaddr*)&servaddr, sizeof(struct sockaddr))<0){ printf ("ERROR \n"); exit(1); } //La aplicación ya está nuevamente conectada… faltan los tiempos. tiempo_comienzo[numero]=contador; tiempo_final[numero]=0; if (--nready<=0) continue; } //Previsión para más de una interrupción } } //FIN del bucle principal for } //FIN del main Centro de Estudios NOVA – Cartagena 868-97-38-38 Problema 2 (50% Ejercicios) Se pretende realizar una aplicación de encaminamiento en una red datagrama (sin conexión) que se encarga de reenviar, según las indicaciones de un algoritmo, los paquetes que le llegan por cada entrada, hacia una salida determinada (que puede ser otra aplicación cómo esta o un destino definitivo). Como existen todo tipo de aplicaciones y por tanto muchos servicios en la red, es necesario abrir un conjunto (se dimensiona desde el 2000 al 2099) de puertos para asegurar que un paquete no se pierde mientras la aplicación calcula la salida de otro paquete. Se pide: A) Realice el código necesario para que la aplicación sea capaz de abrir los 100 puertos que se solicitan para esta aplicación y atender simultáneamente las entradas (leer los paquetes) que se pueden producir por cada uno de ello. Realícelo de forma concurrente y utilice un único select para esta aplicación. (20%) B) Realice el código para que la aplicación sea capaz de enviar la información leída a un destino determinado, de forma concurrente. Para ello utilice una función que calcula el algoritmo de encaminamiento: struct sockaddr *routing(struct sockaddr *origen destino). Envíe la información por la salida determinada por la mencionada función. (10%) C) Por último, suponga ahora el servicio se adapta al entorno de manera que cuando la tasa de llegada de paquetes es elevada y, por ello, la aplicación no puede esperar a que se ejecute la función routing, que es bastante lenta. Para ello: C.1) Detecte la saturación mediante un temporizador en select de una décima de segundo. Cuando se ejecute select 100 veces seguidas sin expirar el timer suponga saturación. (10%) C.2) En caso de saturación se generará un servicio multiprocesado concurrente, asignando un único puerto a cada nuevo proceso. Estos nuevos procesos realizarán el servicio tal y como estaba diseñado. (10%). Otra información: #define MSG_SIZE 1024 //Tamaño máximo de los mensajes #define Direccion_local “192.168.3.1” NOTA IMPORTANTE PARA LOS DOS PROBLEMAS: 1.- NO es necesario incluir las macros y librerías habituales en el código de la solución. #define MSG_SIZE 1024 //Tamaño máximo de los mensajes #define Direccion_local “192.168.3.1” #define PUERTO_ORIGEN 2000 int main (int argc, char **argv) { int maxfd, listenfd, i ,j, k; int contador; ssize_t len; fd_set rset, allset; char msg_rec[MSG_SIZE]; // Mensajes recibidos socklen_t clilen; int client[100]; struct sockaddr_in servaddr, cliaddr, *destaddr; struct timeval tv; //La variable temporal de 1 decima segundo int multiprocesado=0; // Para solo hacerlo una vez Centro de Estudios NOVA – Cartagena 868-97-38-38 //Se abre el servicio //Hay que abrir 100 puertos. Al ser UDP, no se requiere conexión, no hay bloqueo asíncrono, por lo que se puede //proceder a hacerlo directamente en “bucle”. FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (Direccion_local); maxfd=-1 ; for (i=0 ; i<=99 ;i++) { if ((client[i] = socket(AF_INET,SOCK_DGRAM,0))<= 0) { printf ("ERROR \n"); exit(1); } servaddr.sin_port=htons(PUERTO_ORIGEN+i); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))<0) { printf("ERROR \n"); exit (1); }; FD_SET(client[i],&allset); //Socket hay que activarlo if(client[i]>maxfd) maxfd=client[i]; }; //Aplicación UDP preparada msg_rec[0]=’\0’; for(;;){ rset=allset; tv.tv_sec=0; tv.tv_usec=100000; nready=select (maxfd+1, &rset, NULL, NULL, &tv); //Bloqueo en espera de entradas o timeout if (nready==0){ //Salta timeout contador=0; continue; } for (j=0; j<M; j++){ if ((sockfd1 =client[j])<0) // Se van a comprobar todas las entradas continue; if (FD_ISSET(sockfd1, &rset)) { //Se recibe algo -> COMPUTAR TIEMPO EN CONTADOR contador++; if ((contador>=100) && !multiprocesado){//Condición de salto… Se necesita generación multiproceso. for (j=0; j<99, j++){ //Hay que generar 99 hijos, el padre se hará cargo de un puerto. multiprocesado=1; if (fork()==0){ //UN HIJO Centro de Estudios NOVA – Cartagena 868-97-38-38 //Se le asigna por ejemplo, el descriptor del puerto j, por tanto el resto hay que cerrarlo. for (k=99; k>0; k--) if (k!=j){ FD_CLR(client[k], &allset); if (client[k]==maxfd) maxfd--; client[k]=-1; } //El hijo se quedaría con un select y sólo un puerto el j-ésimo atento break; //Y en el hijo no hay que hacer nada más. } }else { //Y el padre debe cerrar el puerto tomado por el hijo FD_CLR(client[j], &allset); if (client[j]==maxfd) maxfd--; client[j]=-1; }; // El padre ha de seguir iterando para generar hijos }; if((len=recvfrom(sockfd1, msg_rec, MSG_SIZE, 0, (struct sockaddr*)&cliaddr, &clilen))> 0){ //Se lee. No es vacío //APLICACIÓN SERVICIO (APARTADO B) msg_rec[len]=’\0’; destaddr= (struct sockaddr_in*) routing((struct sockaddr*)&cliaddr); //Según indica el enunciado esta funcion //devuelve el destino a donde hay que reenviar. //Es una función interna, por lo que no se puede multiplexar. //Y se envía por ejemplo por el mismo puerto que llegó. sendto(sockfd1, msg_rec, strlen(msg_rec),0, (struct sockaddr *)destaddr, sizeof(struct sockaddr)); }; }//Fin del for de atención a puertos }//FIN bucle principal } //Fin del MAIN Centro de Estudios NOVA – Cartagena 868-97-38-38 12 DE SEPTIEMBRE DE 2008 Problema 1 (50% Ejercicios) Se diseña un servicio TCP concurrente en el que el los clientes se conectan para acceder a información que están generando aplicaciones que corren de manera autónoma en la misma máquina en el que se ejecuta el servicio. El gráfico representa las conexiones de las aplicaciones: Entre las tareas del servicio, se encuentran: − Gestionar las conexiones permanentes con los clientes. Éstos se conectan cuando desean acceder a una aplicación. Habrá un total de N conexiones de este tipo en el servidor. − Gestionar las conexiones permanentes con las aplicaciones (actúan como servicio). Éstas son conectadas por el servidor en el momento detallado en la cuestión B. Habrá un total de M conexiones de este tipo. − Gestionar la comunicación cliente-aplicación. Se pide: A) Código para aceptar la conexión de clientes. Éstos se conectan al servidor que les debe proporcionar servicio. En el primer mensaje, el cliente invoca la aplicación deseada con su nombre. Recuerde utilizar un solo select para todo el ejercicio. (10%) B) Código para conectarse con las aplicaciones. Estas operan como servicios TCP en los puertos [3100..310M]. El proceso de conexión se produce cuando algún cliente solicita la aplicación que se menciona. Para relacionar aplicación con el puerto, el nombre de cada una lleva incluido el número M en él con formato “nombreM”. El puerto de la aplicación es por tanto 3100+M. Una vez conectada, el servidor no se desconecta de la aplicación hasta que el cliente se desconecta de él. (25%) C) Código para preparar la comunicación cliente-aplicación. Con el nombre capturado, y el descriptor de la conexión se trata de relacionar de manera directa cliente y aplicación (p. ej. uno/dos arrays), de forma que lo que llegue por uno se pueda propagar al otro sin más cálculos. Indique en el código dónde se debe incluir dicha propagación de mensajes. (15%) Otra información: #define N 20 #define M 29 #define SERVIDOR “192.168.5.254” #define SERVER_PORT 2399 #define MSG_SIZE 256 // Tamaño maximo para los nicks, datos del juego y nicks+struct_sockaddr. #define N 20 //dato #define M 29 //dato #define LISTENQ N // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N a la vez. #define SERVER_PORT 2399 //Dato #define SERVICIOS 3100// A partir de este están todos los servicios #define SERVIDOR “192.168.5.254” Centro de Estudios NOVA – Cartagena 868-97-38-38 #define MSG_SIZE 256 //Tamaños maximos int main (int argc, char **argv) { int i, j, maxi, maxfd, listenfd, resto, connfd, sockfd1, nready, numero, valor; int client [N]; // Array de descriptores de los usuario int descriptores_servicios[M]; //Se crea un array de “descriptores locales” para los servicios. // char * aplicaciones[M]; Nombres de aplicaciones. No hace falta guardarlos. int relaciones[N]; //Array de relaciones de clientes, para relacionar cliente->aplicacion int clientes_destinos[M]; // Como para el apartado C hay que relacionar directamente todo, se hace un segundo // array de relaciones aplicación->cliente ssize_t len; fd_set rset, allset; char msg_rec[MSG_SIZE]; // Mensaje recibido socklen_t clilen; struct sockaddr_in cliaddr, servaddr; //Arranque del servidor concurrente if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; relaciones[i]=-1; } for (i=0; i<M;i++){ //Hay dos arrays distintos clientes_destinos[i]=-1; descriptores_servicios [i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); //Ahora no hay que inicializarlo for(;;){ Centro de Estudios NOVA – Cartagena 868-97-38-38 rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas //Ahora la conexiones de clientes if ( FD_ISSET(listenfd, &rset){ //USUARIO NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario for (i=0; i<N; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; // Los operarios van después de las líneas if (i>maxi) maxi=i; if (--nready<=0) continue; //Previsión para más de una interrupción } for (j=0; j<N; j++){ if ((sockfd1 =client[j])<0) // Se van a comprobar todas los clientes continue; if (FD_ISSET(sockfd1, &rset)) //Se recibe algo if((len=read(sockfd1, msg_rec, MSG_SIZE))> 0){ //Se lee. No es vacío if (relaciones[N]<0) { //Es el primer mensaje… luego nos interesa el número de puerto //Hay que extraer el valor M… valor=atoi(msg_rec[len-2]); if ((valor != 1) || (valor != 2)) //Se ve si el penúltimo dígito es 1 ó 2: numero =atoi (msg_rec[len-1]); else numero = 10*valor+ atoi (msg_rec[len-1]); //Si hay que considerarlo. //Ahora se pasa a establecer servicio con la aplicación, el “numero” calculado es muy práctico… if (aplicaciones[numero]<0){ //NO ESTÁ OCUPADO (Si lo está no se pide solución) if ((descriptores_servicios[numero]=socket(AF_INET, SOCK_STREAM, 0))<= 0) { printf ("ERROR \n"); exit(1); } servaddr.sin_port=htons(SERVICIOS+numero); //IMPORTANTE PARA CONECTARSE if ((connect (descriptores_servicios[numero], (struct sockaddr*)&servaddr, sizeof(struct sockaddr))<0){ printf ("ERROR \n"); exit(1); Centro de Estudios NOVA – Cartagena 868-97-38-38 } //La aplicación ya está conectada, actualizamos variables. FD _SET(descriptores_servicios [numero],&allset); if (descriptores_servicios[numero]>maxfd) maxfd = descriptores_servicios[numero]; //Preparado select para escuchar relaciones[i]=descriptores_servicios[numero]; //Luego para las relaciones apartado C clientes_destino[numero]=client[i]; //Para las operaciones relacionales. }else //CUANDO SE RECIBAN DATOS. NO SE PIDE { } } //Fin del if entrada de datos else //Cierre del cliente //Es cerrar dos conexiones a la vez… close (sockfd1); //Cliente close(relaciones[i]); //Aplicación relacionada //Y se actualizan variables j=0; while (client[i]!=cliente_destino[j]) j++; if (j<M){ //Encontrada la aplicación FD_CLR(client[i], &allset); FD_CLR(aplicaciones[j],&allset); //Conjuntos de descriptores if (aplicaciones[i]==maxfd) maxfd--; //maxfd client[i]=-1; cliente_destino[j]=-1; relaciones[i]=-1; aplicaciones[j]=-1; //arrays actualizados if (i==maxi) maxi--; //maxi }else{ printf(“error/n”); } if (--nready<=0) break; } } for (j=0; j<M; j++){ if ((sockfd1 =descriptores_aplicaciones[j])<0) // Se van a comprobar todas las aplicaciones continue; if (FD_ISSET(sockfd1, &rset)) //Se recibe algo if (--nready<=0) continue; //Previsión para más de una interrupción } //CÓDIGO APARTADO B Centro de Estudios NOVA – Cartagena } //FIN del bucle principal for } //FIN del main 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Problema 2 (50% Ejercicios) Se pretende realizar una aplicación distribuida en una red local en la que los distintos equipos que la conforman puedan transmitirse entre ellas información del propio sistema al resto de equipos, para que tomen decisiones sobre rendimiento, tasas de transmisión, etc. (las decisiones a considerar no se piden en este ejercicio). La aplicación es única, es decir, la misma aplicación se encarga de leer los datos del sistema y mandarlos al resto de equipos y de recibir los datos de los otros equipos y guardarlos. Se pide: A) Realice el código necesario para que el equipo sea capaz de leer los datos de funcionamiento interno del equipo. Estos datos se releen regularmente cada segundo o cada ver que se reciben datos del resto de equipos (código del apartado C). La lectura se realiza sobre una serie de 10 archivos denominados “datosX.dat”, (X es un número entre 0 y 9), que se actualizan solidariamente. (20%) B) Realice el código para transmitir los datos leídos al resto de equipos. La transmisión se produce cada vez que se realiza la lectura de datos, sea cuál sea el momento, y en un solo mensaje individual para cada equipo de la red (UNICAST), excepto él mismo claro. El mensajes es composición de los datos leídos del apartado A concatenados uno detrás de otro en orden X y separados por el símbolo “:”. (15%) C) Por último, la misma aplicación es capaz de recibir datos de la aplicación que se ejecute en otro equipo. Prepare la aplicación para ello y con la información de llegada (mismo formato que apartado B) guárdela sin modificar en una serie de archivos “salidaY.dat” donde Y es el número de equipo de donde viene la información, que coincide con el último dígito de su IP. (15%) Otra información: #define EQUIPOS 8 //Número de equipos de la red local #define MSG_SIZE 1024 //Tamaño máximo de los mensajes #define APPLICATION_PORT 5200 // Puerto de la aplicación #define DATO_SIZE 8 //Tamaño máximo de cada uno de los datos a leer #define Direccion_local “192.168.3.1” Nota: La máscara de red es 255.255.255.254. Suponga que las direcciones ocupadas son las menores posibles: 1, 2,… EQUIPOS. #define EQUIPOS 8 //Número de equipos de la red local #define MSG_SIZE 1024 //Tamaño máximo de los mensajes #define APPLICATION_PORT 5200 // Puerto de la aplicación #define DATO_SIZE 8 //Tamaño máximo de cada uno de los datos a leer #define Direccion_local “192.168.3.1” int main (int argc, char **argv) { int maxfd, listenfd; int X, Y; char *nombre, *leido, *aux, *aux2; char *direccion, *ip_grupo; ssize_t len; fd_set rset, allset; char * msg_env; //Mensaje enviado char msg_rec[MSG_SIZE]; // Mensajes recibidos socklen_t clilen; Centro de Estudios NOVA – Cartagena 868-97-38-38 FILE *fl; struct sockaddr_in servaddr, cliaddr; struct timeval tv; //La variable temporal de 1 segundo //Se abre el servicio if ((listenfd = socket(AF_INET,SOCK_DGRAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (Direccion_local); servaddr.sin_port=htons(APPLICATION_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))<0) { printf("ERROR \n"); exit (1); } //La operación bind hay que hacerla para que asegurar la utilización del puerto 5200, y para poder recibir datos en el // apartado C. //Aplicación UDP preparada numero=0; msg_rec1[0]=’\0’; msg_rec2[0]=’\0’; FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); //Socket hay que activarlo //Se puede hacer la primera lectura y tx ahora. El código es repetido para el caso genérico. msg_env=NULL; for (X=0; X<=9; X++){ nombre=NULL; //Inicialiazion del nombre del archive. nombre=strcat(“dato”, (char *) X); //Como X es menor de 256 se puede hacer casting sin perder nada nombre=strcat(nombre, “.dat·”); fl=fopen(nombre, “r”) ; fgets(leido, DATO_SIZE, fl) ; // Se leen los datos (una sóla lectura de 8 bytes) fclose(fl) ; msg_env=strcat(msg_env, leido); if (X!=9) msg_env=strcat(msg_env,”:”); //El mensaje está listo }; //Mecanismo para sacar la dirección IP de los destinos ipgrupo=strdup(Direccion_local); aux=ipgrupo; for (i=0; i<strlen(ip_agrupo); i++) aux++; Centro de Estudios NOVA – Cartagena 868-97-38-38 aux=`’\0’; //Se coge la dirección menos el último carácter [1..8]. //La dirección IP de los equipos esta guardad en ip_grupo. for (Y=1, Y<=EQUIPOS; Y++){ aux2= strcat(ipgrupo, (char *)Y); //Se forma direccion destino servaddr.sin_addr.s_addr=inet_addr(aux2); if (strcmp(aux2, Direccion_local)) //Control para no enviar a sí mismo sendto(listenfd, msg_env, strlen(msg_env), 0, (struct sockaddr*) &servaddr, sizeof(struct sockaddr)); } for(;;){ rset=allset; tv.tv_sec=1; tv.tv_usec=0; nready=select (maxfd+1, &rset, NULL, NULL, &tv); //Bloqueo en espera de entradas o timeout if ( FD_ISSET(listenfd, &rset){ //SOCKET len=recvfrom(listenfd, msg_env, strlen(msg_env), 0, (struct sockaddr*) &cliaddr, &clilen); direccion=inet_ntoa(&cliddr.sin_addr.s_addr); //Se saca la dirección del remitente aux=direccion; //Se pueden reusar variables for (i=0; i<strlen(ip_agrupo);i++) {aux++;} // Se obtiene el último caracter para el nombre del archivo nombre=strcat(“salida”, aux); nombre=strcat(nombre, “.dat·”); //Se forma el nombre fl=fopen(nombre, “w”) ; //Se abre archivo fputs(msg_rec, DATO_SIZE, fl) ; //Se escribe lo recibido fclose(fl) ; // Se cierra archivo } //SIEMPRE QUE SE SALGA POR SELECT SE TRANSMITE LUEGO SE PONE SIN NECESIDAD DE HACER // FD_ISSET msg_env=NULL; for (X=0; X<=9; X++){ nombre=NULL; //Inicialiazion del nombre del archive. nombre=strcat(“dato”, (char *) X); //Como X es menor de 256 se puede hacer casting sin perder nada nombre=strcat(nombre, “.dat·”); fl=fopen(nombre, “r”) ; fgets(leido, DATO_SIZE, fl) ; // Se leen los datos (una sóla lectura de 8 bytes) fclose(fl) ; msg_env=strcat(msg_env, leido); if (X!=9) msg_env=strcat(msg_env,”:”); //El mensaje está listo }; //Mecanismo para sacer la dirección IP de los destinos ip_grupo=strdup(Direccion_local); aux=ipgrupo; for (i=0; i<strlen(ip_agrupo); i++) aux++; aux=`’\0’; //Se coge la dirección menos el último carácter [1..8]. //La dirección IP de los equipos esta guardad en ip_grupo. Centro de Estudios NOVA – Cartagena 868-97-38-38 for (Y=1, Y<=EQUIPOS; Y++){ aux2= strcat(ipgrupo, (char *)Y); //Se forma direccion destino servaddr.sin_addr.s_addr=inet_addr(aux2); if (strcmp(aux2, Direccion_local)) //Control para no enviar a sí mismo sendto(listenfd, msg_env, strlen(msg_env), 0, (struct sockaddr*) &servaddr, sizeof(struct sockaddr)); } } //Fin del for } //Fin del main Centro de Estudios NOVA – Cartagena 868-97-38-38 01 DE JULIO DE 2008 Problema 1 (50% Ejercicios) Se diseña un servicio TCP concurrente en el que el los clientes se conectan para acceder a información que están generando aplicaciones que corren de manera autónoma en la misma máquina en el que se ejecuta el servidor. El gráfico representa las conexiones de las aplicaciones: Entre las tareas del servicio, se encuentran: − Gestionar las conexiones permanentes con los clientes. Éstos se conectan cuando desean acceder a una aplicación. Habrá un total de N conexiones de este tipo en el servidor. − Gestionar las conexiones permanentes con las aplicaciones (actúan como servicio). Éstas son conectadas por el servidor cuando se arranca el mismo. Habrá un total de M conexiones de este tipo. − Gestionar la comunicación cliente-aplicación. Se pide: A) Código para conectarse con las aplicaciones. Estas operan como servicios TCP en los puertos [3101..310M]. Una vez conectadas las aplicaciones, éstas mandan el nombre de cada aplicación, que se debe guardar en el servidor. Hágalo de manera concurrente. (25%) B) Código para aceptar la conexión de clientes. Una vez las M aplicaciones están activas para la comunicación, se puede pasar a aceptar los N clientes. Éstos se conectan al servidor que les debe proporcionar servicio. Recuerde utilizar un solo select para todo el ejercicio. (12,5%) C) Código para preparar la comunicación cliente-aplicación. El cliente manda el nombre de la aplicación en la que está interesado en su primer mensaje, el servidor lo lee y en un array específico relaciona la conexión del cliente con la de la aplicación. No realice el código de transmisión de mensajes. (12,5%) Otra información: #define N 20 #define SERVIDOR “192.168.5.254” #define SERVER_PORT 2399 #define MSG_SIZE 256 // Tamaño maximo para los mensajes. #define N 20 //Se le da un valor #define M 30 //Se le da otro valor #define LISTENQ N // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N a la vez. #define SERVER_PORT 2399 //Dato #define SERVICIOS 3101 // A partir de este están todos los servicios #define SERVIDOR “192.168.5.254” #define MSG_SIZE 256 //Tamaños maximos Centro de Estudios NOVA – Cartagena 868-97-38-38 struct clientes { //aunque no se pide, parece interesante crear una estructura para guardar los datos… int sockfd; //puede llevar el descriptor del socket. char * aplicacion; //puede llevar el nombre de la aplicación solicitada int descriptor_aplicacion; //lo más importante será el descriptor de la aplicación }; int main (int argc, char **argv) { int i, j, k, maxi, maxfd, listenfd, resto, connfd, sockfd1, nready, numero, numero_resto; int client [N]; // Array de descriptores de los usuario int descriptores_servicios[M]; //Se crea un array de “descriptores locales” para los servicios. char * aplicaciones[M]; // Array de nombres de aplicaciones. struct clientes relaciones[N]; //Array de estructuras clientes, para relacionar cliente->aplicacion ssize_t len; fd_set rset, allset; char msg_rec[MSG_SIZE]; // Mensaje recibido socklen_t clilen; struct sockaddr_in cliaddr, servaddr; /* Para conectar servicios hay que generar M sockets TCP clientes, cada uno para cada servicio, y hacerlos concurrentes para poder atender la información que mandan cuando se conecta. Se podría denominar “cliente concurrente de servicios TCP”. Se podría resolver haciendo: a) polling… muy apropiado para este funcionamiento. b) Realizar las conexiones iterativas y recibir la información (nombre) de forma concurrente. Más eficiente. c) Select, escalonado-> el fin del establecimiento de la conexión con una aplicación, lanza la de la siguiente. Parece lo más apropiado debido a que son conexiones con la misma máquina y no saturar. d) Simplemente iterativo, uno después de otro, sin utlilizar select-> no es concurrente, peor solución. Como polling no está estudiado explícitamente en este curso, optamos por la forma (c) [(b) es igual de válido y más sencillo de implementar, (d) pierde puntuación] */ //Como va a haber que abrir el puerto de aceptar conexiones de clientes, lo vamos haciendo… //Arranque del servidor concurrente if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes Centro de Estudios NOVA – Cartagena 868-97-38-38 client[i]=-1; relaciones[i].sockfd=-1; relaciones[i].descriptor_aplicacion=-1; relaciones[i]->aplicacion=NULL; } for (i=0; i<M;i++){ //Hay dos arrays distintos aplicaciones[i]=NULL; descriptores_servicios [i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); // FD_SET(listenfd, &allset); Ahora no hay que inicializarlo k=0; //UN CONTADOR NECESARIO PARA LLEVAR EL NÚMERO DE APLICACIONES CONECTADAS //Se empiezan a conectar servicios: Se utilizar servaddr ya que está medio configurada. servaddr.sin_port=htons(SERVICIOS); if ((descriptores_servicios[0]=socket(AF_INET, SOCK_STREAM, 0))<= 0) { printf ("ERROR \n"); exit(1); } if ((connect (descriptores_servicios[0], (struct sockaddr*)&servaddr, sizeof(struct sockaddr))<0) { printf ("ERROR \n"); exit(1); } //El primero ya está…. A falta de recibir el nombre. Cuando termine el primero irá al segundo… y así hasta el M. FD_SET(descriptores_servicios [0],&allset); if (descriptores_servicios[0]>maxfd) maxfd = descriptores_servicios[0]; //Preparado select para escuchar for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas for (j=0; j<M; j++){ if ((sockfd1 =aplicaciones[j])<0) // Se van a comprobar todas las aplicaciones continue; if (FD_ISSET(sockfd1, &rset)) //Se recibe el nombre de la aplicación if((len=read(sockfd1, msg_rec, MSG_SIZE))> 0) //Se lee if(strlen(aplicaciones[j]==0)) { // Para posibles futuros usos, se puede preparar… aplicaciones[j]=strdup(msg_rec); // Se guarda el nombre k++; //Uno hecho if (k<M){ //Hay que preparar uno nuevo servaddr.sin_port=htons(SERVICIOS+k); if ((descriptores_servicios[j+1]=socket(AF_INET, SOCK_STREAM, 0))<= 0) { printf ("ERROR \n"); exit(1); Centro de Estudios NOVA – Cartagena 868-97-38-38 } if ((connect (descriptores_servicios[j+1], (struct sockaddr*)&servaddr, sizeof(struct sockaddr))<0){ printf ("ERROR \n"); exit(1); } FD_SET(descriptores_servicios [j+1],&allset); if (descriptores_servicios[j+1]>maxfd) maxfd = descriptores_servicios[j+1]; //Preparado select para escuchar else{ // Cuando se han conectado todos los serv y todos han respondido se puede pasar a los clientes FD_SET(listenfd, &allset); // Y así está preparado para aceptar clientes } } } } } if (--nready<=0) break; } // Fin del for de aplicaciones if ( FD_ISSET(listenfd, &rset){ //USUARIO NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario for (i=0; i<N; i++){ //Actualización del array if (client[i]<0) { client[i]=connfd; break; } relaciones[i].sockfd=connfd; //Luego para las relaciones apartado C. FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; // Los operarios van después de las líneas if (i>maxi) maxi=i; if (--nready<=0) continue; //Previsión para más de una interrupción } } for (i=0; i<=maxi; i++){ if ((sockfd1 =client[i])<0) // Se van a comprobar todos los usuarios conectados continue; if (FD_ISSET(sockfd1, &rset)){ //Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec,MSG_SIZE))>0){ //El proceso de cierre no se pide… if (strlen(relaciones[i].aplicacion)==0){ //Es el nombre. No tiene nombre guardado. relaciones[i].aplicacion =strdup(msr_rec); j=0; while ((j<=M) && strcmp(msg_rec, aplicaciones[j])) { Centro de Estudios NOVA – Cartagena 868-97-38-38 j++; } //Se busca el nombre de la aplicación. if (j<M) //Se ha encontrado relaciones[i].descriptor_aplicacion= descriptores_servicios[j]; //Se relaciona else write(sockfd1, “SERVICIO NO DISPONIBLE\0”, strlen(“SERVICIO NO DISPONIBLE\0”)); }else{ /* FALTA AÑADIR CÓDIGO DE ATENCIÓN A CLIENTES NO SE PIDE */ } } } //Fin del FD_ISSET if (--nready<=0) continue; } //Previsión para más de una interrupción }Fin del for de clientes }// FIN del bucle principal for }// Fin del main Centro de Estudios NOVA – Cartagena 868-97-38-38 Problema 2 (50% Ejercicios) Se pretende realizar una aplicación que permita a un usuario mandar datos (texto) de un móvil a otro, tipo servicio SMS. El servicio se realiza sin servidor, directamente se realiza la transmisión de móvil a móvil, por lo que para poder realizar la transmisión es necesario que el destino se encuentre en la misma celda de cobertura, ya que la estación base propaga mensajes a todos lo móviles conectados a él pero no al resto de la red. Es decir, para que se pueda transmitir el mensaje los móviles deben estar cerca, y además la comunicación se realiza sin servidor. Se pide: A) Realice el código necesario de para que el móvil sea capaz de leer primero el número destino y luego el mensaje texto del usuario a través del teclado. Por supuesto, no se puede bloquear el móvil durante la recepción de ambos datos proporcionados por el usuario. Prepare la concurrencia para el resto del ejercicio. (20%) B) Realice el código para que la aplicación, con el número de teléfono, manda un mensaje “broadcast” (IP “192.168.255.255”) conteniendo en el mensaje el número de teléfono solicitado. A continuación, esperará la recepción de un mensaje sin datos que le informa de la dirección IP del destino. Con esa dirección se puede realizar la transmisión del mensaje concreto de texto. Transmita el mensaje de texto con la dirección recibida. Hágalo de forma concurrente y de la forma más ágil posible. (25%) C) Por último, la misma aplicación es capaz de recibir datos en cualquier momento de la misma aplicación de otro móvil. Prepare la aplicación para ello y con la información de texto llegada muéstrela por pantalla. (5%) Otra información: #define MSG_SIZE 1024 //Tamaño máximo de los mensajes #define APPLICATION_PORT 4200 // Puerto de la aplicación #define PUERTO_BROADCAST 5000 //Puerto destino para mandar información broadcast #define Direccion_local “192.168.3.1” Nota: Los número de teléfono posee nueve cifras. #define MSG_SIZE 1024 //Tamaño máximo de los mensajes #define APPLICATION_PORT 4200 // Puerto de la aplicación #define PUERTO_BROADCAST 5000 //Puerto para esperar recibir información broadcast #define Direccion_local “192.168.3.1” #define TECLADO 0 // Descriptor para el teclado int main (int argc, char **argv) { int maxfd, listenfd, mandarya; char numero[9]; ssize_t len; fd_set rset, allset; char * msg_env; //Mensaje enviado de llamada char msg_rec1[MSG_SIZE], msg_rec2[MSG_SIZE]; // Mensaje recibido de teclado socklen_t clilen; struct sockaddr_in servador, cliaddr; mandarya=0; if ((listenfd = socket(AF_INET,SOCK_DGRAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); Centro de Estudios NOVA – Cartagena 868-97-38-38 servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (Direccion_local); servaddr.sin_port=htons(APLICATION_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))<0) { printf("ERROR \n"); exit (1); } //La operación bind hay que hacerla para que asegurar la utilización del puerto 4200, y para poder recibir datos en el // apartado C. //Aplicación UDP preparada numero[0]=’\0’; msg_rec1[0]=’\0’; msg_rec2[0]=’\0’; if (listenfd>TECLADO) maxfd=listenfd; else maxfd =TECLADO; FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); //Socket hay que activarlo FD_SET(TECLADO, &allset); //Teclado hay que activarlo for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL, NULL, NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //SOCKET { //RECEPCIÓN RESPUESTA BROADCAST. APARTADO B if ((len=recvfrom(listenfd, msg_rec2, MSG_SIZE,0, (struct sockaddr*)&cliaddr,&clilen))== 0){ // Mensaje vacio //respuesta al paquete broadcast //Lo que interesa es la direccion IP del destino. El puerto habrá que actualizarlo. cliaddr.sin_port=htons(APPLICATION_PORT); //Con esta información se puede enviar el mensaje. if (strlen(msg_rec)>0){ // Por si acaso no está todavía escrito sendto (listenfd, msg_rec1, strlen(msg_rec1), 0, (struct sockaddr*)&cliaddr, sizeof(struct sockaddr)); numero [0]=’\0’; }else mandarya=1; else{ // Se reciben datos printf( “%s\n”, msg_rec2); }; } if ( FD_ISSET(TECLADO, &rset){ //USUARIO=TECLADO if(strlen(numero)==0){ //NO se ha recibido todavía el número. fgets(numero, 9, stdin); //Se lee numero msg_rec1=’\0’; Centro de Estudios NOVA – Cartagena 868-97-38-38 // ENVÍO PAQUETE BROADCAST CÓDIGO APARTADO B //Configurar direccion servaddr.sin_addr.s_addr= inet_addr (“192.168.255.255”); servaddr.sin_port=htons(PUERTO_BROADCAST); //Y mandar sendto (listenfd, numero, strlen(numero), 0, (struct sockaddr*)&servaddr, sizeof(struct sockaddr)); }else //SE había leído ya el número fgets(msg_rec1, MSG_SIZE, stdin); if (mandarya) { sendto (listenfd, msg_rec1, strlen(msg_rec1), 0, (struct sockaddr*)&cliaddr, sizeof(struct sockaddr)); mandarya=0;numero[0]=’\0’; } //Caso para optimizar tx (APARTADO B) } } Fin del for 04 DE FEBRERO DE 2007 Problema 1 (60% Ejercicios) Se diseña un servidor concurrente TCP destinado al servicio SSH. El demonio SSHD acepta la conexión de los distintos usuarios y los mantiene conectados mientras no termina la sesión. El gráfico representa el sistema una vez conectados los usuarios: Entre las tareas del servidor, se encuentran: − Gestionar las conexiones permanentes con los usuarios. Éstos se conectan cuando desean establecer una sesión. Habrá un total de N conexiones de este tipo. − Gestionar la lectura de login y password del usuario. Los usuarios sólo pueden enviar tres tipos de paquetes: su conexión, su password y los comandos a ejecutar. − Gestionar la creación de las consolas de ejecución de comandos una vez los usuarios se conectan y su password es correcto. Se pide: A) Código para aceptar la conexión de los usuarios y proporcionar la concurrencia. Gestionar la lectura de los login y password de usuario que son respectivamente el primer y segundo mensaje que se recibe del usuario. Procure proporcionar concurrencia en este proceso. (25%) B) Comprobar y aceptar la autorización de usuario, (con su login y password) mediante la función: int confirma (char * login, char *password). La función devuelve 0 si está autorizado y otro número en cualquier otro caso. En caso de no estar autorizado se cierra la conexión. (15%) C) Gestione la creación de las consolas de usuarios. Para ello debe ejecutar las funciones fork() y execl(“/bin/bash”) que crea una nueva consola. Recuerde que debe guardar el pid asociado para poder comunicarse con ella. Finalmente, puede gestionar la lectura de comandos del usuario y su paso (p. ej.: Mediante la función write) a su correspondiente consola. No incluya la gestión de las respuestas de la consola al usuario. (20%) Centro de Estudios NOVA – Cartagena Otra información: #define N 20 #define SERVIDOR “192.168.5.254” #define SERVER_PORT 2199 #define N 20 #define LISTENQ N // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N a la vez. #define SERVER_PORT 2199 //Dato #define SERVIDOR “192.168.5.254” #define MSG_SIZE 256 //Tamaños maximos int main (int argc, char **argv) { int i, j, maxi, maxfd, listenfd, resto, connfd, sockfd, sockfd1, nready, numero, numero_resto; int client [N]; // Array de descriptores de los usuario int descriptores_locales[N]; //Se crea un array de “descriptores locales” para las consola (APARTADO C). char * login[N]; // Array de logins de usuarios, para ir guardándolos mientras llegan los passwwords. char *password; // Password. No hace falta guardarlos porque sólo se utiliza una vez en “confirma”. ssize_t len; fd_set rset, allset; char * msg_rec; // Mensaje recibido char *aux; //Auxiliares para formar mensaje socklen_t clilen; struct sockaddr_in cliaddr, servaddr; //Arranque del servidor concurrente if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; login[i]=NULL; consolas [i]=-1; } 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //USUARIO NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario for (i=0; i<N; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; // Los operarios van después de las líneas if (i>maxi) maxi=i; /* Si se quiere pasar el login del servidor en este instante se añadiría. No es solución óptima*/ // read (connfd, msg_rec, sizeof(numero)); //Se entiende que no se va a tx otra cosa // strcpy(login[i],numero); //Copia del login if (--nready<=0) continue; //Previsión para más de una interrupción } for (i=0; i<=maxi; i++){ if ((sockfd1 =client[i])<0) // Se van a comprobar todos los usuarios conectados continue; if (FD_ISSET(sockfd1, &rset)){ //Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec,MSG_SIZE))>0){ //El proceso de cierre no se pide… if (login[i]==NULL){ //Es el login. No password o comando. strcpy(login[i],msg_rec); } //Copia del login else if (consola[i]==0){ //NO es login, no es comando (la consola debería estar abierta) es password. strcpy(password, msg_rec); // Copia del password. if (confirma (login[i], password)!=0){// Cuando no es correcto… close (sockfd); client[i]=-1; login[i]=NULL; FD_CLR(sockfd1,&allset); if (maxi==i) --maxi;} if (--nready<=0) break; else continue; }else{ // Cuando es 0, es correcto y hay que proceder a crear las consolas… (Apartado C) int puerto_local=4000+i; // Buscamos un puerto “aleatorio”. if ((int sockfd = socket(AF_INET,SOCK_DGRAM,0))<= 0) { Centro de Estudios NOVA – Cartagena 868-97-38-38 printf ("ERROR \n"); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(puerto_local); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); } // El servidor ya tiene un puerto local con el que poder comunicarse con… pid=fork( ); // Se crea proceso hijo. Recordar guardar el pid. if (pid==0){ //El hijo va a ser la nueva consola… for (i = 0; i <= maxi; i++) if (client[i]>-1) { close(client[i]); close(descriptor_local[i]); } // Cerramos todos los puertos abiertos. close(listenfd); // Cerramos el puerto de atención a nuevas conexiones. Sólo queda abierto sockfd. // No hace falta inicializar variables, pues éstas no se volverán a utilizar. break; break; // Se sale de los bucles for en los que se encontraba el programa padre. // Y ya se puede proceder a crear la consola… execl(“/bin/sh”); // Se ejecuta la nueva consola. }else { //y el proceso padre… close(puerto_local); //Cierra el socket UDP… // Y para poder utilizar write en vez de sendto, más sencillo, se pasa a hacer un UDP conectado. if (descriptor_local[i]=socket(AF_INET, SOCK_DGRAM, 0))<= 0) { printf ("ERROR \n"; } if(connect(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); } } } //Fin del “else” principal }else{ // Atencion a comandos write(descriptor_local[i], msr_rec, len+1); } } } if (--nready<=0) continue; } //Previsión para más de una interrupción }Fin del for de clientes … }// FIN del bucle principal for Centro de Estudios NOVA – Cartagena }// Fin del main 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Problema 2 (40% Ejercicios) Se diseña un servicio de telefonía móvil, en el que la estación base ofrece servicios muy diversos, entre los que se encuentran los mensajes de texto. Se pretende realizar el software de los terminales móviles, de manera que sean capaces en enviar y recibir los mensajes de texto referentes a este servicio. De esta forma, la aplicación consiste en atender simultáneamente al usuario del teléfono móvil por si desea mandar el mensaje y atender al servidor por si es éste el que manda el mensaje de otro usuario. Se pide: A) Realice el código necesario de conexión del terminal al servidor y mantener la concurrencia entre el usuario local (el teclado del terminal) y el servidor al que se ha conectado. (10%) B) Realice el código para mandar y recibir mensajes. En caso del envío, el teléfono incluye en el mensaje el propio número de teléfono de la forma: “Número_propio:mensaje”. De igual forma, en la recepción se recibe un mensaje del mismo tipo y se deben separar los dos campos, mostrando sólo el mensaje por la pantalla. (10%) C) Añada ahora una confirmación al usuario para proceder a la presentación por pantalla (por ejemplo, pulsar la tecla #) de un mensaje recibido desde el servidor, evitando bloquear la aplicación sólo por esta causa. Incluya un “timeout” de tiempo máximo de espera para esta confirmación (1 minuto) de forma que si no se realiza, se vuelve a la espera original. ( 20%) Otra información: #define MSG_SIZE 1024 //Tamaño máximo de los mensajes #define PUERTO_SERVIDOR 3000 #define SERVIDOR “192.168.3.254” #define numero_propio 100 El primer paso consiste en decidir si es un proceso cliente o servidor. Es un cliente porque se refiere a los terminales móviles con interfaces para usuario (STDIN) y para el servidor. El segundo paso es decidir el protocolo TCP ó UDP. UDP no permite asegurar que el mensaje llegue y además el servidor no podría saber el destino que corresponde a cada mensaje cuando tenga que enviarlos: luego es TCP. #define SERVER_PORT 3000 //Dato #define SERVIDOR “192.168.5.254” //Dato #define MSG_SIZE 1024 //Se sobreentiende que completo #define TECLADO 0 // Descriptor para el teclado #define PANTALLA 1 // Descriptor para la pantalla int main (int argc, char **argv) { int maxfd, listenfd, numero, sockfd1, nready; char* numero propio; ssize_t len; fd_set rset, allset; char * msg_env; //Mensaje enviado de conexión char * msg_rec1, *msg_rec2; // Mensaje recibido de conexión char *valor1, *valor2; //Auxiliares para formar mensaje socklen_t clilen; struct timeval * tv; //Variable temporal necesaria para el apartado C struct sockaddr_in servaddr; numero_propio= argv[1]; //Por ejemplo, se puede pasar así, no viene especificado en el enunciado. Centro de Estudios NOVA – Cartagena 868-97-38-38 if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(connect(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))<0) { printf("ERROR \n"); exit (1); } if (listenfd>TECLADO) maxfd=listenfd; else maxfd =TECLADO; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; numeros[i]=-1; conexiones [i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); //Socket hay que activarlo FD_SET(TECLADO, &allset); //Teclado hay que activarlo tv->tv_sec=NULL; tv->tv_usec=NULL; for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL, tv); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //SERVIDOR if (len=read(listenfd, msg_rec1, MSG_SIZE)>0){ // Mensaje msg_env=strchr(msg_rec1, “:”); //Buscamos los “:” write(PANTALLA, msg_env++, strlen(msg_env)); tv->tv_sec=60; tv->tv_usec=0; } } if ( FD_ISSET(TECLADO, &rset){ //USUARIO if (len=read(TECLADO, msg_rec2, MSG_SIZE-strlen(numero_propio)-1)> 0){ // Mensaje if (strcmp(msg_rec1,”#”)==0){ msg_env=strchr(msg_rec1, “:”); //Buscamos los “:” write(PANTALLA, msg_env++, strlen(msg_env)); tv->tv_sec=NULL; Centro de Estudios NOVA – Cartagena 868-97-38-38 tv->tv_usec=NULL; }else{ valor=numero_propio; msg_env=strcat(valor, “:”); //Ponemos los “:” msg_env=strcat(msg_env,msg_rec2); // Componemos el mensaje write(listenfd, msg_env, strlen(msg_env)); // Y tx al servidor tv->tv_sec=NULL; tv->tv_usec=NULL; } } } if (nready == 0){ //Expira el tiempo tv->tv_sec=NULL; tv->tv_usec=NULL; } //Inicializamos el sistema. } } Centro de Estudios NOVA – Cartagena 868-97-38-38 03 DE JULIO DE 2007 Problema 1 (50% Ejercicios) Se diseña un servicio TCP destinado a un videojuego distribuido. El servicio se encuentra distribuido entre todos los equipos de los jugadores que en ese momento juegan una partida del videojuego. El gráfico representa las conexiones de las aplicaciones una vez conectados los jugadores: Entre las tareas del servicio, se encuentran: − Gestionar las conexiones permanentes con el resto de jugadores. Éstos se conectan cuando desean realizar una partida. Habrá un total de N-1 conexiones de este tipo en cada servicio. − Gestionar la lectura de nicknames de los jugadores. Los usuarios sólo pueden enviar tres tipos de paquetes: su nickname, su fin de juego y los datos de situación referentes al juego en sí. − Gestionar la inclusión de nuevos jugadores con la partida ya comenzada. Se pide: A) Código para aceptar la conexión de nuevos jugadores y proporcionar la concurrencia entre ellos. Gestionar la lectura de los nickname’s de jugadores que se transmite en el primer mensaje de datos, que se recibe de un nuevo jugador. Procure proporcionar concurrencia en este proceso. (25%) B) Código para conectarse a un grupo o partida ya comenzada. En este caso el jugador busca a los jugadores ya conectados. Para ello, como conoce el número de puerto en el que funciona, va testeando las IP’s que el jugador local le va proporcionando por teclado. Cuando encuentra un jugador activo, le manda el nickname propio y recibe de este jugador a su vez su nickname . (25%) Otra información: #define N 20 #define SERVIDOR “192.168.5.254” #define SERVER_PORT 2399 #define NICKNAME nombrealumno //Cada alumno que ponga su nombre #define MSG_SIZE 256 // Tamaño maximo para los nicks, datos del juego y nicks+struct_sockaddr. #define N 20 #define LISTENQ N-1 // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N-1 a // la vez. #define NICKNAME alumnoLSC //Nick del jugador #define SERVER_PORT 2199 //Dato #define SERVIDOR “192.168.5.254” #define MSG_SIZE 256 //Tamaños maximos de mensajes int main (int argc, char **argv) { int i, j, maxi, maxfd, listenfd, resto, connfd, sockfd, sockfd1, nready, numero, numero_resto; int client [N]; // Array de descriptores de los jugadores char * nickname [N]; // Array de nicks de usuarios, para ir guardándolos (en realidad N-1). char * IP [N]; //Array de caracteres para ir guardando las direcciones IP de los jugadores. Centro de Estudios NOVA – Cartagena 868-97-38-38 ssize_t len; fd_set rset, allset; char * msg_rec; // Mensaje recibido char *IPCON; // Char para guardar la IP del jugador al que se conecta. socklen_t clilen; struct sockaddr_in servaddr; //Arranque de la parte de servidor concurrente, para aceptar nuevos jugadores. if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n";) exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; nickname[i]=NULL; IP[i]=NULL; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); //Para el apartado B) donde hay que gestionar la conexión a nuevos juegos: FD_SET (0, &allset); for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //USUARIO NUEVO clilen=sizeof(cliaddr); if (connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen)<0) continue; //Se acepta el usuario for (i=0; i<N; i++){ //Actualización del array if (client[i]<0) { client[i]=connfd; break; } } Centro de Estudios NOVA – Cartagena 868-97-38-38 IP[i]=cliaddr.sin_addr.s_addr; // Se guarda la dirección IP del nuevo jugador. FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; if (i>maxi) maxi=i; /* Si se quiere leer el nick del jugador en este instante se añadiría. No es solución concurrente*/ // read (connfd, msg_rec, sizeof(msg_rec)); //Se entiende que no se va a tx otra cosa // strcpy(nickname[i],msg_rec); //Copia del nick // Aquí se puede hacer comprobación de NICKNAME+IP y comprobar que no es un jugador repetido. if (--nready<=0) continue; //Previsión para más de una interrupción } for (i=0; i<=maxi; i++){ if ((sockfd1 =client[i])<0) // Se van a comprobar todos los usuarios conectados continue; if (FD_ISSET(sockfd1, &rset)){ //Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec,MSG_SIZE))>0) //El proceso de cierre no se pide… if (nickname[i]=NULL){ //Es el nick. strcpy(nickname [i],msg_rec); //Copia del nick. La IP ya estaba copiada en la conexión. //Según apartado B, cuando alguien se conecta se le manda el nick propio. if (strcmp(IP[i],IPCON)!=0) write(sockfd1, &NICKNAME, strlen(NICKNAME)); } }else{ //Es comando o dato del juego /* FALTA AÑADIR CÓDIGO DE FUNCIONAMIENTO DEL JUEGO. NI SE EXPLICA, NI SE PIDE.*/ } } if (--nready<=0) continue; } //Previsión para más de una interrupción } if (FD_ISSET(0, &rset)) // sale por el teclado. if (len=read (0, msg_rec, strlen(msg_rec))==strlen (SERVIDOR)) { // caso de que “parezca” una IP. // Aprovechamos servaddr que ya está configurado y: servaddr.sin_addr.s_addr= inet_addr (msg_rec); //Actualizamos IP. if ((sockfd= socket(AF_INET, SOCK_STREAM, 0))<0) continue;) if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr))<0) close(sockfd); continue;} // Se intenta conectar al jugador indicado. //Y se gestiona la nueva conexión: for (i=0; i<N; i++) //Actualización del array if (client[i]<0) { client[i]=sockfd; break; } IP[i]=strdup(servaddr.sin_addr.s_addr); // Se guarda la dirección IP del nuevo jugador. IPCON=strdup(IP[i]); //Variable para saber a quién nos hemos conectado, para luego realizar el intercambio de nicks. Centro de Estudios NOVA – Cartagena 868-97-38-38 FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (sockfd > maxfd) //Actualización de variables maxfd=sockfd; if (i>maxi) maxi=i; /* Se pasa el nick del jugador en este instante se añadiría. */ write (sockfd, &NICKNAME, strlen(NICKNAKE)); //Se entiende que no se va a tx otra cosa /* Si se quiere esperar el nick del otro jugador en este instante se añadiría, solución no conc. */ if ((len=read(sockfd, msg_rec,MSG_SIZE))>0){ if (nickname[i]=NULL) //Es el nick. strcpy(nickname[i],msg_rec); } //Copia del nick } //FIN del if (TECLADO) … }// FIN del bucle principal for }// Fin del main Centro de Estudios NOVA – Cartagena 868-97-38-38 15 DE SEPTIEMBRE DE 2006 Problema 1 (60% Ejercicios) Se diseña un servidor central TCP destinado al servicio de descubrimiento de servicios. El servicio consiste en que un usuario se conecta y el servicio le informa de dónde se encuentra el servicio que está buscando. El gráfico representa el sistema una vez conectados los usuarios: Entre las tareas del servidor, se encuentran: − Gestionar las conexiones permanentes con los usuarios. Éstos se conectan cuando desean descubrir (saber donde está) un servicio. Habrá un total de N conexiones de este tipo. − Gestionar la búsqueda del servicio en la única base de datos local. Esta base de datos se abre por el servidor en el arranque y no se vuelve a cerrar. Se considera la gestión de la base de datos idéntica a la de un fichero. − Gestionar la búsqueda de la localización del servicio (por su nombre) en la base de datos. La base de datos es simplemente una lista de “nombres” y “localizaciones”, con tamaños de 20 bytes y “sizeof (struct sockaddr)” respectivamente. La base de datos (y el servicio) devuelve la dirección y puerto del servicio buscado en formato “struct sockaddr”. Se pide: A) Código para aceptar la conexión de los usuarios y proporcionar la concurrencia. Recuerde que el proceso ha de ser completamente concurrente, es decir, que el servidor no puede quedarse atendiendo la búsqueda de un único usuario. Incluya también la apertura de la base de datos (que se realiza antes de la conexión de los clientes). (20%) B) Código de peticiones y resolución de la localización. Una vez se recibe el nombre del servicio (20 bytes), se debe buscar en la base de datos (se supone que siempre es accesible) y encontrar el nombre, lo que le sigue es su localización. Los registros son directamente nombre y localización (sin espacios) y se separan por un “INTRO” (dos bytes). (25%) C) Suponga ahora que la búsqueda no encuentra resultado en la base de datos. Gestione la conexión (TCP) y petición sobre otro servicio de descubrimiento externo. A su vez se espera la respuesta de este servicio y se responde al usuario. (15%) Otra información: #define N 100 #define SERVIDOR_EXTERNO “192.168.5.254” #define SERVER_PORT 2499 Nota importante: Suponga que el equipo donde se ejecuta el servidor DISCOVERY no tiene limitaciones de memoria, ni de número de puertos abiertos simultáneamente. El sistema operativo permite la concurrencia en la base de datos. #define N 100 #define LISTENQ N // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N a la vez. #define SERVER_PORT 2499 //Dato #define SERVIDOR _EXTERNO “192.168.5.254” //Del servicio externo #define MSG_SIZE 1024 //No se dice nada, mínimo mayor de 20 (tamaño del nombre del servicio buscado) #define BASE “BASE DE DATOS” //No se indica nombre. Nos hace falta para proceder a su apertura. int main (int argc, char **argv) { int listenfd, connfd, sockfd, id, numfp; char *nombre; // Nombre que busca el cliente ssize_t len; Centro de Estudios NOVA – Cartagena 868-97-38-38 char * datos; // para la base de datos char *nombre_comprobar; // Nombre extraido de la base de datos socklen_t clilen; struct sockaddr_in cliaddr, servaddr, servexternoaddr; //Hay 3, el propio, el cliente y el del servicio externo. fp=fopen(BASE, ‘r’); //Se abre la base de datos. “fp” en el proceso padre siempre apuntará al origen. numfp=fileno(fp); //Descriptor numérico, para facilitar la búsqueda. if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= htonl(INADDR_ANY); //Como no se conoce la direccion local, no se usa inet_addr servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. for (;;){ clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario if ((id=fork())<0) { printf (“Error\n”); continue; } else if (id>0){ //Proceso padre close (connfd); // Cerramos puerto del cliente y esperamos por más. }else{ //Proceso hijo close (listenfd); // se cierra puerto de conexiones. Los procesos ya están funcionados y conectados. /* ESPACIO PARA EL PROCESADO DEL HIJO APARTADOS B Y C */ if (read (connfd, nombre, 20)==0) { close(connfd); exit(0); }else{ //Se lee el nombre, y se comprueba si es 0. while (!feof(fp)){ //Con el nombre vamos a buscar por toda la base de datos. read(datos, 20, numfp); // Leemos sólo la parte del nombre. Se podría leer todo y luego separar... nombre_comprobar=strdup(datos); //Duplicamos la cadena, nos hará falta, (se puede usar strcpy). fgets(datos, sizeof(struct sockaddr),fp); //Leemos lo que falta de la linea, ya que tenemos que // hacerlo tanto si es o no correcto. //Ahora toca comprobar: if (strcmp(nombre_comprobar,nombre)==0){ //Si lo es write (connfd, datos, strlen(datos)); //Rapidamente se envía la respuesta al cliente; //Se le puede hacer un casting a datos: (struct sockaddr *) &datos exit(0); Centro de Estudios NOVA – Cartagena 868-97-38-38 } //El hijo no tiene nada más que hacer se termina. } //Fin del bucle while. if ((sockfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servexternoaddr, sizeof (servaddr)); servexternoaddr.sin_family= AF_INET; servexternoaddr.sin_addr.s_addr= inet_addr(SERVIDOR_EXTERNO); //Ahora sí se usa inet_addr servexternoaddr.sin_port=htons(SERVER_PORT); //El mismo que el propio, es otro como //DISCOVERY. if(connect(sockfd, (struct sockaddr *)&servexternoaddr, sizeof(servexternoaddr)<0){ //Y se conecta con el externo. printf("ERROR \n"); exit (1); } // Ahora hay que hacer lo que haria un cliente con el servidor:preguntar y esperar respuesta. Como el // proceso no tiene que realizar ninguna otra tarea se puede bloquear, aunque se deberia usar select para // limitar el tiempo de espera. write (sockfd, nombre_comparar, strlen(nombre_comparar)); //Mandamos el nombre. //Se espera respuesta. if (read ( sockfd, datos, sizeof (struct sockaddr))!=sizeof(struct sockaddr)){ write(connfd, “Dato no dispoble\n”); exit (1);) }else { write(connfd, datos,strlen (datos)); exit (0); } } } // Fin del hijo. } Fin del for }Fin del main Centro de Estudios NOVA – Cartagena 868-97-38-38 Problema 2 (50% Ejercicios) Se diseña un servicio de subscripciones a una lista de distribución de publicidad mediante mensajes cortos de texto. Este servicio consiste en enviarles SMS’s indiscriminadamente a números de usuario obtenidos de una base de datos y éstos, al abrir el mensaje, automáticamente responden al SMS quedando subscritos al servicio de publicidad. Se pretende realizar el software del servidor de mensajes, de manera que sea capaz en enviar y recibir los mensajes de texto referentes a este servicio y suscribir a los usuarios. De esta forma, la aplicación consiste en atender simultáneamente al envío de mensajes SMS (que los va leyendo de la base de datos “DATABASE”), enviar los mensajes a los teléfonos móviles y atender las respuestas de éstos y subscribirlos al servicio SPAM (enviando un mensaje SMS a otro servicio que se encarga de proporcionar la publicidad). Se pide: A) Realice el código necesario de arranque del servidor y mantener la concurrencia entre el envío indiscriminado de SMS’s y la atención de las posibles respuestas de los usuarios. (25%) B) Realice el código para mandar y recibir mensajes. En caso del envío, el servidor busca el destino en la base de datos “DATABASE” y obtiene el número del usuario junto con una estructura “struct sockaddr” que indica la dirección y puerto de funcionamiento de los terminales, bajo el formato “NÚMERO:SOCKADDR”. Con esta información el servidor genera un mensaje mediante la función char* genera(double número, struct sockaddr* clientaddr) que ya está programada (no suponga bloqueo al ejecutar esta función). En recepción, el servidor lee el mensaje, leyendo el origen, y enviando al servidor que funciona en el puerto 4500 de la misma máquina un mensaje con formato “SOCKADDR+conectado” donde SOCKADDR es la struct sockaddr que contiene dirección IP y puerto del usuario conectado. (25%). Otra información: #define MSG_SIZE 1024 //Tamaño máximo de los mensajes #define PUERTO_SERVIDOR 3000 #define SERVIDOR “192.168.3.254” El número de teléfono posee nueve cifras. Considere la base de datos como un archivo compuesto por la estructura “NÚMERO:SOCKADDR”, indefinidamente repetida. El primer paso es decidir el protocolo TCP ó UDP. TCP no permite mandar paquetes a distintos destinatarios por el mismo descriptor, además y sobre todo, se desconoce si se tiene cliente o servidor en el otro extremo. UDP resuelve estas dificultades. #define SERVER_PORT 3000 //Dato #define SERVIDOR “192.168.5.254” //Dato #define MSG_SIZE 1024 //Se sobreentiende que completo #define SPAM 4500 //Enunciado int main (int argc, char **argv) { int maxfd, listenfd, numfd, sockfd1, nready; char* numero propio; ssize_t len; fd_set rset, allset; char * msg_env; //Mensaje enviado char * msg_rec1, *msg_rec2; // Mensaje recibido de conexión char *valor1, *valor2; //Auxiliares para formar mensajes socklen_t clilen; FILE *fp; //descriptor para la base de datos. double numero; //Para el numero de la función “genera” struct timeval * tv; //Podría ser utilizada. Centro de Estudios NOVA – Cartagena 868-97-38-38 struct sockaddr_in servaddr, cliaddr; if ((listenfd = socket(AF_INET,SOCK_DGRAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); fp=fopen(“DATABASE”, “r”); // Se abre base de datos. int numfd=fileno(fp); FD_SET(listenfd, &allset); //Socket hay que activarlo FD_SET(numfd, &allset); //Database hay que activarlo if (numfp>listenfd) maxfd=numfp; else maxfd=listenfd; // Actualizacion maxfd for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL, NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //SERVIDOR //ATENCIÓN A LA LLEGADA DE RESPUESTA DE USUARIOS se recibe mensaje if (len=recvfrom(listenfd, msg_rec1, MSG_SIZE, 0, (struct sockaddr*)cliaddr, &len)>0){ // Mensaje msg_env=(char *)&cliaddr; // Se va formando el mensaje a tx al otro servicio. msg_env=strcat(msg_env, ”+conectado”); // Se termina de formar el mensaje. servaddr.sin_port=htons(SPAM); // Destino del datagrama. sendto(listenfd, msg_env, strlen(msg_env), 0, (struct sockaddr*)&servaddr, sizeof (servaddr)); // Y se transmite el mensaje de información al otro servidor. } } if ( FD_ISSET(numfd, &rset){ //BASEDEDATOS - ATENCIÓN A LA INTERRUPCIÓN DE LA BASE DE DATOS, siempre mientras haya listado if (len=read(numfp, msg_rec2, MSG_SIZE)> 0){ // Se lee base de datos (Se puede medir el tamaño:9+1+sizeof(struct sockaddr)) numero=0; valor1=strdup(msg_rec2); valor2=valor1; //Extraccion número for (i=0; i<9; i++) { valor1++; valor2++; } ++valor2=’\0’; Centro de Estudios NOVA – Cartagena 868-97-38-38 for (i=0; i<9; i++) { numero=(atoi(valor1)*1ei)+numero; valor1--; --valor2=’\0’; } //Se extrae el número. Hay otras alternativas… (atof, atol). valor2=strchr (msg_rec2, ‘:’); cliaddr=(struct sockaddr_in) ++valor2; //Direccion msg_env=genera (numero, (struct sockaddr*) &cliaddr); //Generamos mensaje sendto(listenfd, msg_env, strlen(msg_env), 0, (struct sockaddr*)&cliaddr, sizeof (cliaddr)); } } } Fin del for } Fin del main Centro de Estudios NOVA – Cartagena 868-97-38-38 14 DE SEPTIEMBRE DE 2007 Problema 1 (50% Ejercicios) Se diseña un servicio TCP destinado a proporcionar concurrencia para el acceso a un servidor. El servicio consiste en que los usuarios se conectan, acceden (en caso de que sea posible), y se arranca la aplicación que los usuarios requieren del servidor. Entre las tareas del servicio, se encuentran: − Gestionar las conexiones permanentes de los usuarios. Éstos se conectan cuando desean acceder a algún servicio del servidor. Habrá un total de N conexiones de este tipo. − Gestionar la lectura de los comandos de usuarios. Los usuarios mediante la transmisión de comandos estándares de Linux pueden operar en la Maquina Servidor. Por tanto el servidor debe ser capaz de ejecutar estos comandos (incluyendo como tales los servicios de acceso). No se limita el número de comando a ejecutar en cada sesión. − Gestionar la devolución de parámetros según se produzcan o no en los servicios. Se pide: A) Código para aceptar la conexión de los usuarios y proporcionar la concurrencia. No existe control de acceso en esta máquina. (15%). B) Gestione la ejecución de los servicios de usuarios. Para ello debe ejecutar las funciones fork() y execl(“comando”) siendo “comando” lo que se recibe del usuario. La función “fork” genera un nuevo proceso (necesario para que el proceso sea concurrente) y execl ejecuta un comando. (20%) C) Incluya la recepción de parámetros de salida de los servicios y su transmisión al usuario que lo ha ejecutado. Para ello suponga que cada servicio antes de terminar el funcionamiento transmite un paquete UDP al puerto 4200+i (donde i es número del servicio: i є[1,N]) en el que se contiene esa información (no se requiere programar esta parte). Por ello, sea capaz de leer los datos de salida (en formato char*), y a continuación transmitirlos al usuario indicado. (15%). Otra información: #define N 20 #define SERVIDOR “192.168.3.254” #define SERVER_PORT 2399 #define MSG_SIZE 256 #define N 20 #define LISTENQ N // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N a la vez. #define SERVER_PORT 2399 //Dato #define SERVIDOR “192.168.3.254” #define MSG_SIZE 256 //Tamaños maximos int main (int argc, char **argv) { int i, j, maxi, maxfd, listenfd, resto, connfd, sockfd, sockfd1, nready, numero, numero_resto; int client [N]; // Array de descriptores de los usuario int descriptores_locales[N]; //Se crea un array de “descriptores locales” para las respuestas de los // servicios. (APARTADO C). Centro de Estudios NOVA – Cartagena ssize_t len; fd_set rset, allset; char * msg_rec; // Mensaje recibido = comando socklen_t clilen; struct sockaddr_in servaddr; //Arranque del servidor concurrente if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; login[i]=NULL; descriptors_locales [i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset){ //USUARIO NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario for (i=0; i<N; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; // Los operarios van después de las líneas if (i>maxi) maxi=i; 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 if (--nready<=0) continue; //Previsión para más de una interrupción } // Como no hay control de acceso la atención a la conexión es genérica. for (i=0; i<=maxi; i++){ if ((sockfd1 =client[i])<0) // Se van a comprobar todos los usuarios conectados continue; if (FD_ISSET(sockfd1, &rset)) //Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec,MSG_SIZE))>0) { //El proceso de cierre no se pide… /* FALTA AÑADIR CÓDIGO DE ARRANQUE DE SERVICIOS*/ // Se ha enviado un comando -> arranque del servicio. // Por cada servicio aparecerá un proceso nuevo. //Aquí se debería incluir código para el apartado C: if (descriptores_locales[i]<0){ //Posición vacía descriptores_locales[i]=socket(AF_INET, SOCK_DGRAM, 0); serveraddr.sin_port=htons(4200+i); //Actualizamos puerto… if (bind (descriptores_locales[i], (struct sockaddr*)&serveraddr, sizeof(serveraddr))<0) printf (“Error/n”); FD_SET(descriptores_locales[i], &allset); //Se activa para cuando deba ser atendida. } //Hay que crear un proceso hijo para cada nuevo servicio. if (pid=fork()<0) printf(“Error /n”); else if (pid == 0){ //Proceso hijo… //Lo fundamental del hijo es cerrar todas las conexiones abiertas que no se deben solapar con el padre. close (listenfd); for (i = 0; i <= maxi; i++) if (client[i]>-1) { close(client[i]); } if(descriptores_locales[i] >0) close(descriptores_locales[i])}; //Y la última tarea del hijo es generar el servicio: if (execl(msg_rec)<0) printf(“Error /n”); //Esto dedicará este proceso a la ejecución del servicio. Cuando termine no hay nada más a hacer: exit(0); }else{ if (--nready<=0) continue; // No hay que hacer nada especial en el padre (servidor). } } if (--nready<=0) continue; } //Previsión para más de una interrupción }//Fin del for de clientes Centro de Estudios NOVA – Cartagena 868-97-38-38 for (i=0; i<=maxi; i++){ if ((sockfd1 =descriptores_locales[i])<0) // Se van a comprobar todos los puertos continue; if (FD_ISSET(sockfd1, &rset)){ //Para el caso de que uno de ellos escriba algo if ((len=recvfrom(sockfd1, msg_rec,MSG_SIZE, 0, NULL, NULL))>0) { //Se sabe el origen… // Se responde write (client[i], msg_rec, strlen(msg_rec)); // Y se transmite al usuario } // Por último se libera el socket: FD_CLR(sockfd1, &allset); close (sockfd1); } } … }// FIN del bucle principal for }// Fin del main Centro de Estudios NOVA – Cartagena 868-97-38-38 Problema 2 (50% Ejercicios) Se diseña un servicio de transmisión de archivos (FTP) en una red LAN, por lo que se opta por implementar un servicio TFTP, basado en UDP. De esta forma, la aplicación consiste en atender simultáneamente la conexión de N usuarios que requieren descargar o cargar un archivo del servidor. La instrucción para cada caso es “get” y “put” respectivamente. Se pide: A) Realice el código necesario de arranque del servidor y mantener la atención de las peticiones de los usuarios. No hay control de acceso, al ser una LAN cerrada. (10%) B) Realice el código para que el servicio actúe correctamente como servicio TFTP. Sólo hay dos instrucciones válidas (get y put) seguidas de un espacio y el nombre de un archivo, es decir, “get filename” o “put filename”. En el caso de que se produzca una descarga deberá de abrirse el archivo, leerlo y mandarlo, y en caso de carga se tratará de abrir el archivo, leerlo y guardarlo. (25%). C) Añada un timeout al funcionamiento del servidor. Para agilizar el funcionamiento y que el sistema no se sature realice las modificaciones para que el tiempo máximo de carga o descarga de cada archivo de manera individualizada no supere los 5 minutos. (15%). Otra información: #define MSG_SIZE 1024 //Tamaño máximo de los mensajes tanto de comunicación como de los archivos #define PUERTO_SERVIDOR 3000 #define SERVIDOR “192.168.3.254” #define N 20 El enunciado indica claramente la naturaleza de la aplicación es un servidor UDP. La concurrencia para clientes UDP es natural, es decir, UDP es concurrente por sí mismo. Lo que ya no es concurrente automáticamente es cuando se tienen N clientes con N ficheros… hace falta select. Problema: gestionar concurrencia de diversos ficheros y relacionarlos con sus usuarios. Además pueden ser de salida (get) o entrada (put). #define SERVER_PORT 3000 //Dato #define SERVIDOR “192.168.3.254” //Dato #define MSG_SIZE 1024 //Se sobreentiende que completo #define N 20 //Dato int main (int argc, char **argv) { int maxfd, listenfd, numero, sockfd1, nready; char* numero propio; ssize_t len; fd_set rset, allset; char * msg; //Mensaje (simultáneamente sólo va a hacer falta uno). char *valor1, *valor2; //Auxiliares para separar mensaje socklen_t clilen; char buffer[MSG_SIZE]; struct timeval * tv; //Variable temporal necesaria para el apartado C struct sockaddr_in servaddr; struct sockaddr_in cliaddr; struct sockaddr_in clientddr [N]; // Array para guardar las direcciones de los clientes. int enviar[N]; // Array para separar get/put, por ejemplo get=0; put =1; inactivo=-1; Centro de Estudios NOVA – Cartagena 868-97-38-38 int ficheros[N]; //Descriptores de ficheros en uso. FILE *fp; if ((listenfd = socket(AF_INET,SOCK_DGRAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); // Todo servidor, incluye operación bind. if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))<0) { printf("ERROR \n"); exit (1); } for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; enviar[i]=-1; bzero(clientaddr[i], sizeof(struct sockaddr_in)); ficheros[i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); //Socket hay que activarlo long iteracion= 300000; // 300/0,001 Este valor depende del tipo de PC, velocidad procesador… Un valor de 1 mseg, es una buena opción long iteraciones[N]; for (int k=0; k<N; k++) iteraciones[k]=-1; tv->tv_sec=0; tv->tv_usec=1000; for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL, tv); //Bloqueo en espera de entradas for (int k=0; k<N; k++){ if (enviar[k]>-1) // Está trabajando iteraciones[k]--; if (iteraciones[k]<=0) // Hay que cerrar… if (enviar[k]==0){ close(fichero[i] // Se cierra fichero; FD_CLR(fichero[i], &allset); } // Actualizar atención a fichero. bzero(clientaddr[i], sizeof(cliaddr)); enviar[k]=-1; Centro de Estudios NOVA – Cartagena 868-97-38-38 iteraciones[k]=-1; }else{ close(fichero[i]); //Fin de archivo fichero[i]=-1; bzero(clientaddr[i], sizeof(cliaddr)); enviar[i]=-1; iteraciones[k]=-1; } } if ( FD_ISSET(listenfd, &rset){ //CLIENTES //ATENCIÓN A LA LLEGADA DE NUEVOS Clientes, pero hay que separarlos de una operación // put en curso (comando y operación “put” tendrán el mismo descriptor de entrada). if (len=recvfrom (listenfd, msg, MSG_SIZE, 0, (struct sockaddr *)&cliaddr, &clilen)<=0){ printf(“Error \n); }else{ for (i=0; i=N-1; i++) if (strcmp((char*)&clientaddr[i], (char *) &cliaddr))==0) { int encontrado =1; break; } if (encontrado ==0) { // Indica que el usuario no está conectado luego es nuevo; //Buscamos si lo que quiere es “get”o “put”) valor2=strchr(msg, “ “)++; //Nombre del fichero; char *filename=strdup(valor2); strncpy(valor1, msg, strlen(“put”)); // Se captura las tres primeras letras y… for (i=0; i=N-1;i++) if (enviar[i]==-1){ //No hay get ni put, luego está vacío.. if (strcmp(valor1, “get”)==0){ // Es una descarga clientaddr[i]= cliadd; enviar[i]=0; // Actualizamos variables. fp=fopen (filename, “r”); // Se abre fichero; fichero[i]=fileno(fp); FD_SET(fileno(fp), &allset); //Actualizar atención a fichero. }else{ // Es una carga… clientaddr[i]= cliadd; enviar[i]=1; // Actualizamos variables. fp=fopen (filename, “w+”); // Se abre fichero; fichero[i]=fileno(fp); } // NO hace falta atención a escritura. ... } }else{ // Está conectado-> está realizando una parte del “put”. //En i estará el índice del cliente. Simplemente queda escribir en fichero… write (fichero[i], msg, strlen (msg)); //Se supone paquetes máximos… y no maz. Si FIN Centro de Estudios NOVA – Cartagena 868-97-38-38 if (len<MSG_SIZE) { for (j=0; j=len-1; j++) msg++; if (msg==’\0’) { close(fichero[i]); //Fin de archivo fichero[i]=-1; bzero(clientaddr[i], sizeof(cliaddr)); enviar[i]=-1; // Actualizamos variables if (--nready<=0) continue; } for (i=0; i<=N-1; i++){ if ((sockfd1 =ficheros[i])<0) // Se van a comprobar todos los archivos, para ver quién es continue; // el que tiene que transmitir. if (FD_ISSET(sockfd1, &rset)){ //Para el caso de que sea uno En modo get: for (j=0; j<MSG_SIZE; j++) { read (fichero[i], buffer[j], 1); // Se lee caracter a caracter… if (buffer[j]=’\0’) { //Fin de archivo en lectura.. enviar[i]=-1; // Actualizamos variables. close(fichero[i] // Se cierra fichero; FD_CLR(fichero[i], &allset); } // Actualizar atención a fichero. break; } sendto{listenfd, buffer, strlen(buffer), 0, (struct sockaddr*)& clientaddr[i], sizeof(cliaddr)); //Se tx paquete del fichero… if (buffer[j]=’\0’) //Fin de archivo en lectura bzero(clientaddr[i], sizeof(cliaddr)); if (--nready<=0) continue; } } } //Fin del for } //Fin del main Centro de Estudios NOVA – Cartagena 868-97-38-38 05 DE FEBRERO DE 2008 Problema 2 (50% Ejercicios) Se diseña un servicio de voto por Internet mediante la activación de un código que llevan las papeletas individualizadas de los votantes. Un votante, que quiere votar con este sistema, solicita el envío de las papeletas “codificadas” de todas las candidaturas. Las papeletas le llegan a casa del votante con el código asignado y a partir de ese momento, y, en el plazo estipulado, puede realizar la votación. Para ello se conecta con el “servidor electoral”, se identifica con el certificado de usuario que posee y le da acceso al sistema electoral y por último introduce el código de la candidatura a la que pretende votar. Se pide: A) Realice el código necesario del “servidor electoral”, para que atienda la conexión de los electores. El servicio ha de ser capaz primero de aceptar la conexión y luego de leer el certificado que el usuario le envíe (le llega en el primer mensaje). Recuerde la concurrencia del proceso. (15%) B) Realice el código para comprobar la autenticidad del certificado. Para ello, el servidor se lo envía a otro servicio, una autoridad de certificación, que responde con “OK” ó “ERROR”. En el primer caso, el servicio pasará a autorizar la votación con un “OK” al votante, y en el segundo cerrará la conexión. Recuerde mantener la concurrencia del proceso. (25%) C) Por último, en caso de que todo el proceso sea válido, se procederá a permitir la lectura del código de votación. El código se almacenará en una base de datos (archivo) denominado “VOTACION”, incluyéndose simplemente el código seguido de un “\n”, para dejarlo listo para el siguiente votante. (10%) Otra información: #define N 20 #define MSG_SIZE 32 //Tamaño máximo de los códigos #define MSG_CERT 1024 // Tamaño máximo del certificado de usuario #define PUERTO 3000 #define SERVIDOR “192.168.3.254” # define AUTORIDAD “192.168.5.253” # define PTO_AUTORIDAD 2999 El primer paso consiste en decidir si es un proceso UDP ó TCP. Dado el tipo de información que se pretende enviar, la respuesta es obvia, es un servidor concurrente TCP. #define N 20 #define MSG_SIZE 32 //Tamaño máximo de los códigos #define MSG_CERT 1024 // Tamaño máximo del certificado de usuario #define PUERTO 3000 #define SERVIDOR “192.168.3.254” # define AUTORIDAD “192.168.5.253” # define PTO_AUTORIDAD 2999 #define LISTENQ N-1 int main ( ) { int maxfd, listenfd, numero, sockfd1, connfd, sockfd; char* numero propio; ssize_t len; fd_set rset, allset; char * msg_rec; // Mensaje recibido socklen_t clilen; struct sockaddr_in servaddr; FILE *fp; // Para el archivo Centro de Estudios NOVA – Cartagena int i, j, maxi, maxfd, listenfd, connfd, sockfd, sockfd1, nready; int client [N]; // Array de descriptores de cada usuario int estado[N]; // Habrá que conocer en qué instante de la votación se encuentra, por ejemplo: 0 sin // certificado, 1 certificado leído y enviado y 2 listo para votar. Ahorra código… int desc_autoridad[N]; //Habrá un array de sockets para la autoridad… serán lógicamente TCP if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(PUERTO); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; maxi=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; estado [i]=-1; desc_autoridad[i]=-1; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); // Para el apartado C hay que abrir un archivo, se puede hacer ya… fp=fopen(“VOTACION”, “w+”); // Se abre el archivo for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas if ( FD_ISSET(listenfd, &rset)){ //USUARIO NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta el usuario for (i=0; i<N; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; estado[i]=0; // Actualizamos arrays break; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 maxfd=connfd; // Los operarios van después de las líneas if (i>maxi) maxi=i; if (--nready<=0) continue; //Previsión para más de una interrupción } for (i=0; i<=maxi; i++){ if ((sockfd1 =client[i])<0) // Se van a comprobar todos los clientes continue; if (FD_ISSET(sockfd1, &rset)) //Para el caso de que uno de ellos escriba algo if ((len=read(sockfd1, msg_rec, MSG_CERT))==0{ //Proceso de cierre en caso de //CLIENTE. NO SE PIDE. close(sockfd1); FD_CLR(sockfd1,&allset); client[i]=-1; estado[i]=-1; des_autoridad[i]=-1; if (maxi==i) --maxi; if (--nready<=0) break; else continue; else{ if (estado[i]==0){ //Se espera certificado // Hay que crear socket…. APARTADO B //Dos partes 1º mandar a la autoridad certificado // 2º Recibir resultado y actuar… // Primera parte… mandar el certificado… hay que crear un socket TCP… cliente. if ((sockfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (AUTORIDAD); servaddr.sin_port=htons(PTO_AUTORIDAD); if (connect(sockfd,(struct sockaddr*) &seraddr, sizeof(servaddr)))<= 0) { printf ("ERROR \n"); exit(1); } //Una vez creado… se le manda el certificado por él… write (sockfd, msg_rec, strlen(msg_rec)); //Actualizamos descriptors, arrays… para estar atentos a su respuesta FD_SET(sockfd, &allset); desc_autoridad[i]=sockfd; estado[i]=1; //Estado de esperando respuesta.. } if (estado[i]==2) // Cuando se manda el código… APARTADO C. if (strlen(msg_rec)<=MSG_SIZE){ //Es un código: Centro de Estudios NOVA – Cartagena 868-97-38-38 strcat (msg_rec,”\n”); //Se añade lo que se indica… fputs (msg_rec, strlen (msg_rec), fp); // Se escribe en “VOTACION” } // Y si se desea se puede cerrar al votante… para que no vote más… close(sockfd1); FD_CLR(sockfd1,&allset); client[i]=-1; estado[i]=-1; des_autoridad[i]=-1; if (maxi==i) --maxi; } if (--nready<=0) break; else continue; } if (--nready<=0) break; //Previsión para más de una interrupción }//Fin del for de clientes. for (i=0; i<=maxi; i++){ if ((sockfd1 =desc_autoridad[i])<0) // Se van a comprobar las conexiones con //“AUTORIDAD” continue; if (FD_ISSET(sockfd1, &rset)) { // Atención a los sockets de autoridad… //Cuando la autoridad responde, puede decir correcto o no… if ((len=read(sockfd1, msg_rec, MSG_SIZE))!=0) //No se plantea el fallo de la AUTORIDAD if (strcmp(msg_rec, “OK”)==0){ //Está bien… estado[i]=2; //Se permite votar al votante write(client[i],”OK”, strlen(“OK”)); // Se le comunica a éste }else if (strcmp(msg_rec, “ERROR”)==0){ // Se le cierra al votante close(client[i]); FD_CLR(client[i],&allset); client[i]=-1; estado[i]=-1; des_autoridad[i]=-1; if (maxi==i) --maxi; } } FD_CLR(sockfd1, &allset); desc_autoridad[i]=-1; close (sockfd1); //Se cierra a la AUTORIDAD if (--nready<=0) break; else continue; } } //Fin del for } // Fin del for general }//Fin de main Centro de Estudios NOVA – Cartagena 868-97-38-38 03 DE FEBRERO DE 2009 Problema 1 (50% Ejercicios) Se diseña un servicio TCP concurrente en el que el los clientes se conectan para acceder a información que están generando aplicaciones que corren de manera autónoma en la misma máquina en el que se ejecuta el servicio. El gráfico representa las conexiones de las aplicaciones: Entre las tareas del servicio, se encuentran: − Gestionar las conexiones permanentes con los clientes. Éstos se conectan cuando desean acceder a una aplicación. Habrá un total de N conexiones de este tipo en el servidor. − Gestionar las conexiones permanentes con los servicios (actúan como clientes). Éstas se conectan cuando son arrancadas. Habrá un total de M conexiones de este tipo. − Gestionar la comunicación cliente-aplicación. Se pide: A) Código para aceptar la conexión de clientes. Éstos se conectan al servidor que les debe proporcionar servicio. El cliente indica el servicio (sólo uno por cliente) con un mensaje que indica su nombre mediante el código: ##nombreaplicación##. Recuerde utilizar un solo select para todo el ejercicio.(10%) B) Código para aceptar la conexión de los servicios. Éstos se conectan al servidor cuando se arrancan (en cualquier momento indeterminado). En el primer mensaje, indican su nombre mediante el conocido formato ##nombreaplicación##. Dada la igualdad de formatos no se admite la conexión a clientes de la misma máquina. Recuerde utilizar el mismo select para todo el ejercicio.(15%) C) Código para relacionar cliente y servicio. Al recibir el nombre del servicio, el servidor busca el nombre entre las aplicaciones ya conectadas. En caso de éxito, relaciona en un array cliente y servicio y responde al cliente con un “OK”. En caso de no encontrarlo se devuelve un “ERROR”.(25%) Otra información: #define N 20 #define M 29 #define SERVIDOR “192.168.5.254” #define SERVER_PORT 2399 #define MSG_SIZE 256 // Tamaño maximo para los mensajes #define N 20 //dato #define M 29 //dato #define LISTENQ N+M // Tamaño de lista de entrada de conexiones. En el peor de los casos son los N+M a la vez. #define SERVER_PORT 2399 //Dato #define SERVIDOR “192.168.5.254” #define MSG_SIZE 256 //Tamaños maximos struct servicios { //aunque no se pide, parece interesante crear una estructura para guardar los datos… int sockfd; //puede llevar el descriptor de comunicación del . char * nombre; //puede llevar el nombre del servicio int descriptor_cliente; //también se le puede añadir el descriptor del cliente que lo está usando (apartado C) Centro de Estudios NOVA – Cartagena }; int main (int argc, char **argv) { int i, j, maxi, maxiser, maxfd, listenfd, sockfd1, nready; int client [N]; // Array de descriptores de los usuario int relaciones[N]; //Array de relaciones de clientes, para relacionar cliente->aplicación struct servicios local[M]; // Array de estructuras para los servicios ssize_t len; fd_set rset, allset; char msg_rec[MSG_SIZE]; // Mensaje recibido. socklen_t clilen; struct sockaddr_in cliaddr, servaddr; bzero(&cliaddr, sizeof(cliaddr)); // Será necesario luego. //Arranque del servidor concurrente if ((listenfd = socket(AF_INET,SOCK_STREAM,0))<= 0) { printf ("ERROR \n"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family= AF_INET; servaddr.sin_addr.s_addr= inet_addr (SERVIDOR); servaddr.sin_port=htons(SERVER_PORT); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)<0) { printf("ERROR \n"); exit (1); } listen (listenfd, LISTENQ); //Preparado para aceptar la entrada de los posibles clientes. maxfd=listenfd; maxi=-1; maxiser=-1; for (i=0; i<N;i++){ //Inicialización de los arrays de clientes client[i]=-1; relaciones[i]=-1; } for (i=0; i<M;i++){ //Hay dos tamaños distintos local[i].sockfd=-1; local[i].descriptor_cliente=-1; local[i]->nombre=NULL; } FD_ZERO(&allset); //Inicialización de los conjuntos de descriptores FD_ZERO(&rset); FD_SET(listenfd, &allset); for(;;){ rset=allset; nready=select (maxfd+1, &rset, NULL,NULL,NULL); //Bloqueo en espera de entradas 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 //Ahora la conexiones de clientes y servicios if ( FD_ISSET(listenfd, &rset){ //USUARIO NUEVO clilen=sizeof(cliaddr); connfd =accept(listenfd, (struct_sockaddr *) &cliaddr, &clilen); //Se acepta la conexión. //CÓDIGO APARTADO B //Aquí es cuando hay que diferenciar entre clientes y servicios if (strcmp((char *) &servaddr.sin_addr.s_addr, (char *) &cliaddr.sin_addr.s_addr))==0){ //Si es servicio. for (i=0; i<M; i++) //Actualización del array if (local[i].sockfd<0) { local[i].sockfd=connfd; break; } if (i>maxiser) maxiser=i; }else { //Si es cliente for (i=0; i<N; i++) //Actualización del array if (client[i]<0) { client[i]=connfd; break; } if (i>maxi) maxi=i; } FD_SET(connfd,&allset); //Actualización del conjunto de descriptores if (connfd > maxfd) //Actualización de variables maxfd=connfd; if (--nready<=0) continue; //Previsión para más de una interrupción } for (i=0; i<=maxi; i++){ if ((sockfd1 =client[i])<0) // Se van a comprobar todas los clientes continue; if (FD_ISSET(sockfd1, &rset)) //Se recibe algo if((len=read(sockfd1, msg_rec, MSG_SIZE))> 0){ //Se lee. No es vacío if (relaciones[N]<0){ //No está relacionado… luego es posible que se quiera conectar con algún servicio //Hay que comprobar que es un mensaje de nombre de servicio if ((len>4) && (msg_rec[0]==’#’) && (msg_rec[1]== ’#’) && (msg_rec[len-2]== ’#’) && (msg_rec[len-1]== ’#’)){ //Esta es la condicion de mensaje de nombre de servicio. //Ahora se pasa a comprobar si ese nombre está ya adquirido por el servidor (el servicio ya se ha //conectado). Como el mensaje es el mismo no hay ni que extraer el nombre del formato. j=0; while (strcmp (msg_rec, local[j]->nombre)!=0){ if (j>maxirec) break; j++; } if (j<=maxirec){ // Encontrado…. OK Centro de Estudios NOVA – Cartagena 868-97-38-38 relaciones[i]=local[j].sockfd; //se actualizan relaciones local[j].descriptor_cliente=sockfd1; write (sockfd1, “OK”, strlen(“OK”)); // Se responde } else // No encontrado write (sockfd1, “ERROR”, strlen(“ERROR”)); //Se responde } } } } else { // RESTO ATENCIÓN CLIENTE NO SE PIDE } } //FIN ATENCIÓN CLIENTES if (--nready<=0) continue; // Por si no ha habido entrada de los servicios //SERVICIOS for (i=0; i<=maxirec; i++){ if ((sockfd1 =local.sockfd[i])<0) // Se van a comprobar todos los servicios continue; if (FD_ISSET(sockfd1, &rset)) //Se recibe algo if((len=read(sockfd1, msg_rec, MSG_SIZE))> 0) //Se lee. No es vacío //Habría que comprobar que es mensaje de identificación… if ((len>4) && (msg_rec[0]==’#’) && (msg_rec[1]== ’#’) && (msg_rec[len-2]== ’#’) && (msg_rec[len-1]== ’#’)) //Aquí es muy sencillo… solo actualizar variable. local[i]->nombre=strdup(msrg_rec); // Se copia else { } else { } } //FIN ATENCIÓN SERVICIOS // AQUÍ SE INCLUIRÍA EL CÓDIGO DEL APARTADO C, Y RESTO. } //FIN del bucle principal for } //FIN del main Centro de Estudios NOVA – Cartagena 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Centro de Estudios NOVA – Cartagena 868-97-38-38 Función strtok ANSI C char *strtok(char *s1, const char *s2); Rompe la cadena s1 en segmentos o tókens. Esta ruptura destruye s1, en el proceso. La forma de romper la cadena depende de la secuencia de caracteres de la cadena s2. Estos caracteres se denominan [caracteres] delimitadores. La función recorrerá la cadena en busca de alguno de los delimitadores de la cadena s2. Cuando lo encuentre, el proceso se detiene, ya que tiene un token. Posteriores llamadas a strtok romperán la cadena s1 en otros tókens. Estas llamadas pueden tener otra secuencia de delimitadores. La primera llamada a strtok determina la cadena a romper, retornando el puntero al comienzo del primer token. Si se recorrió la cadena s1 sin haber encontrado un delimitador, y aún no se ha obtenido el primer token, entonces la función retornará un puntero nulo. Posteriores llamadas retornarán más tókens. Si ya no encuentra más delimitadores, entonces retornará todos los caracteres desde el último delimitador para ser el último token. Si ya se retornó el último token, entonces retornará un puntero nulo con demás llamadas a la función. Ejemplo: #include <stdio.h> #include <string.h> int main() { /* inicializa el arreglo de cadena */ char cadena[] = "cadena:puerto1:puerto2:puerto3"; char *ptrToken; /* crea un apuntador char */ printf( "La cadena a dividir en tokens es: %s. Los tokens son: ", cadena); ptrToken = strtok( cadena, ":" ); /* comienza la division en tokens del enunciado */ /* continua la division en tokens hasta que ptrToken se hace NULL */ while ( ptrToken != NULL ) { printf( "%s\n", ptrToken ); ptrToken = strtok( NULL, " " ); /* obtiene el siguiente token */ } /* fin de while */ return 0; /* indica terminacion exitosa */ } /* fin de main */ Centro de Estudios NOVA – Cartagena 868-97-38-38 Función FORK() A la hora de crear procesos linux provee de la función fork(). Fork crea un nuevo proceso a partir del proceso padre pero de una manera distinta. Cuando utilizamos la llamada al sistema fork, el proceso hijo creado es una copia exacta del padre (salvo por el PID y la memoria que ocupa). Al proceso hijo se le facilita una copia de las variables del proceso padre y de los descriptores de fichero. Es importante destacar que las variables del proceso hijo son una copia de las del padre (no se refieren físicamente a la misma variable), por lo que modificar una variable en uno de los procesos no se refleja en el otro. En el momento de la llamada a fork el proceso hijo: • es una copia exacta del padre excepto el PID. • tiene las mismas variables y ficheros abiertos. • las variables son independientes (padre e hijo tienen distintas memorias). • los ficheros son compartidos (heredan el descriptor). El proceso hijo creado es una copia del padre (mismas instrucciones, misma memoria). Lo normal es que a continuación el hijo ejecute una llamada al sistema exec. En cuanto al valor devuelto por el fork, se trata de un valor numérico que depende tanto de si el fork se ha ejecutado correctamente como de si nos encontramos en el proceso padre o en el hijo. • Si se produce algún error en la ejecución del fork, el valor devuelto es -1. • Si no se produce ningún error y nos encontramos en el proceso hijo, el fork devuelve un 0. • Si no se produce ningún error y nos encontramos en el proceso padre, el fork devuelve el PID asignado al proceso hijo. /* fork.c - Ejecución conjunta de procesos padre e hijo */ #include <stdio.h> #include <unistd.h> main () { printf ("Ejemplo de fork.\n"); printf ("Inicio del proceso padre. PID=%d\n", getpid ()); if (fork() == 0){ /* Proceso hijo */ printf ("Inicio proceso hijo. PID=%d, PPID=%d\n", getpid (), getppid ()); sleep (1); } else { /* Proceso padre */ printf ("Continuación del padre. PID=%d\n", getpid ()); sleep (1); } printf ("Fin del proceso %d\n", getpid ()); exit (0); } Ejemplo de fork. Inicio del proceso padre. PID=5331 Continuación del padre. PID=5331 Inicio proceso hijo. PID=5332, PPID=5331 Fin del proceso 5332 Fin del proceso 5331