Escuela Politécnica Superior de Elche Grado de Ing. Electrónica y Automática Industrial Sistemas Informáticos Industriales Curso 2015-16 Práctica 8: Sockets en Windows Modelo cliente/servidor Streams Sockets (TCP) 1. Objetivos Los objetivos de esta práctica son: o o Aprender a programar sockets de tipo Stream para transmisión por TCP Aprender el funcionamiento de la librería WinSock Para ello realizaremos un chat desarrollando un servidor y varios clientes. 2. Introducción teórica Como hemos visto en clase de teoría, fundamentalmente, debemos tener claro qué tipo de aplicación queremos programar con sockets, ya que en función de la rapidez con la que deben transmitirse los datos y si deseamos o no tener un control de errores, debemos diferenciar entre Sockets Stream y Sockets Datagram. Recordemos que los primeros emplean el protocolo TCP, mientras que los segundos hacen uso del protocolo UDP. Cada uno tiene sus ventajas e inconvenientes, por ejemplo, recordemos que TCP nos provee un flujo de datos bidireccional, secuenciado, libre de errores y sin duplicación de paquetes; mientras que en UDP los paquetes pueden llegarnos fuera de secuencia, incluso no llegar o que éstos contengan errores. Sin embargo, UDP es extensamente empleado para transferencia de información ágil, como vídeo o audio a través de la red. En la práctica que nos ocupa, emplearemos el primero de ellos (TCP) para llevar a cabo una comunicación entre un servidor y varios clientes remotos. Un aspecto a tener en cuenta para la realización de un chat es que deberán pedirse al usuario los mensajes que quiera enviar. Para ello, podemos usar las funciones scanf o gets. Pero estas funciones presentan un gran inconveniente, ya que una vez ejecutadas, el programa se bloquea hasta que el usuario introduzca una cadena de texto y pulse intro. Por tanto, mientras un usuario no introduzca su mensaje no puede comprobar si está recibiendo mensajes. En programación, la solución más ampliamente utilizada para resolver esto es el uso de hilos (threads). Estos permiten que varias funciones se estén ejecutando de forma simultánea, y por tanto, mientras una función solicita información al usuario, otra está comprobando si recibe mensajes. Puesto que en clase no se ha visto el uso de hilos, solucionaremos este problema creando programas (exe) independientes, cada uno de los cuales tenga una determinada función. 1/9 Escuela Politécnica Superior de Elche Grado de Ing. Electrónica y Automática Industrial Sistemas Informáticos Industriales Curso 2015-16 Por tanto, la estructura para realizar esta práctica estará formada por un servidor y 2 clientes: • Tendremos un cliente tipo receptor. Su objetivo será el de recibir desde el servidor todos los mensajes escritos por ambos usuarios. Este sería el equivalente a la ventana del chat donde se muestran los mensajes. • Tendremos un cliente tipo escritor. Su objetivo será el de solicitar al usuario los mensajes que desee mandar y enviarlos al servidor. Este sería el equivalente a la ventana del chat donde el usuario escribe los mensajes. • Finalmente tendremos un servidor que se encargará de aceptar las conexiones con los 4 clientes. Este recibirá los mensajes de los clientes receptores y los enviará a los dos clientes escritores. Cada usuario deberá ejecutar en su ordenador tanto un cliente escritor como un cliente receptor. El servidor podrá ejecutarse en una de los ordenadores de los clientes o incluso en un tercer ordenador. 2/9 Escuela Politécnica Superior de Elche Grado de Ing. Electrónica y Automática Industrial Sistemas Informáticos Industriales Curso 2015-16 3. Descripción del servidor y clientes y tareas a realizar Para facilitar la práctica, se proporcionan los códigos tanto del servidor (P8_servidor.c) como de ambos clientes (P8_cliente_receptor.c y P8_cliente_escritor.c) faltando únicamente el bloque donde se intercambian los menajes. A continuación se describe la secuencia de comunicación en pseudo código para servidor y clientes indicando la parte a incluir por el alumno para completar la práctica: P8_servidor.c 1. Se definirán las variables y estructuras que intervienen en la comunicación. 2. Se inicicializará la DLL de winsock. 3. Se obtendrá la IP que utilizará nuestro servidor (localhost). 4. Se creará el socket y se asociará IP y puerto (3000) al mismo. 5. Se habilitan conexiones entrantes. 6. Comunicación inicial: a. Se aceptarán 4 conexiones entrantes, 2 de clientes receptores y 2 de clientes escritores. i. Al establecer la conexión se pregunta al cliente de que tipo es, receptor o escritor, y se almacena dicha información en las variables. ii. Además, si es un cliente escritor se pedirá a este que se identifique con un nombre de usuario (nick). 7. Una vez finalizado y puesto que no se van a aceptar más conexiones entrantes se cierra el socket de escucha. 8. Finalmente se envía a los 4 clientes un mensaje indicándoles que todos están preparados para comenzar el intercambio de mensajes. 9. Parte a incluir por el alumno: a. Se realizará un bucle para recibir mensajes de los clientes escritores. Al recibir un mensaje de uno de ellos (y en caso de no estar vacío) se enviará a los dos clientes receptores. Antes de enviar cada mensaje se añadirá al principio de este el nick del cliente que lo ha enviado. b. En caso de que el mensaje recibido sea “exit” o “EXIT” se finalizará el bucle y se enviará a los clientes escritores dicha palabra para que estos sepan que también deben finalizar. 10. Se cierra el socket y la librería winsock. 3/9 Escuela Politécnica Superior de Elche Grado de Ing. Electrónica y Automática Industrial Sistemas Informáticos Industriales Curso 2015-16 P8_cliente_receptor.c 1. Se definirán las variables y estructuras que intervienen en la comunicación 2. Se inicicializará la DLL de winsock. 3. Se indicará la IP que utilizará el servidor (localhost) 4. Se creará el socket y se definirá el puerto destino (3000). 5. Se realizará la conexión con el servidor 6. Comunicación inicial: a. Se espera recibir un mensaje de bienvenida por el servidor donde pregunta qué tipo de cliente es. b. Se envía al servidor el mensaje “Cliente receptor”. c. Se queda a la espera del mensaje del servidor que indique que todos los clientes están preparados. 7. Parte a incluir por el alumno: a. Se realizará un bucle para recibir mensajes del servidor (y en caso de no estar vacío) mostrarlos por pantalla. b. En caso de que el mensaje recibido sea “exit” o “EXIT” se finalizará el bucle 8. Se cerrará el socket y se cerrará la librería winsock. 4/9 Escuela Politécnica Superior de Elche Grado de Ing. Electrónica y Automática Industrial Sistemas Informáticos Industriales Curso 2015-16 P8_cliente_escritor.c 1. Se definirán las variables y estructuras que intervienen en la comunicación 2. Se inicicializará la DLL de winsock. 3. Se indicará la IP que utilizará el servidor (localhost) 4. Se creará el socket y se definirá el puerto destino (3000). 5. Se realizará la conexión con el servidor 6. Comunicación inicial: a. Se espera recibir un mensaje de bienvenida por el servidor donde pregunta qué tipo de cliente es. b. Se envía al servidor el mensaje “Cliente escritor”. c. Se espera un mensaje por parte del servidor donde nos solicita el nick. d. Se solicita al usuario su nick y se envía al servidor. e. Se queda a la espera del mensaje del servidor que indique que todos los clientes están preparados. 7. Parte a incluir por el alumno: a. Se realizará un bucle donde se solicita al usuario el mensaje que desee mandar y se envía al servidor. b. En caso de que el mensaje recibido sea “exit” o “EXIT” se finalizará el bucle. c. Tras enviar cada mensaje se comprobará si el servidor nos ha enviado algún mensaje que indique que otro cliente ha decidido finalizar la conexión. 8. Se cerrará el socket y se cerrará la librería winsock. 5/9 Escuela Politécnica Superior de Elche Grado de Ing. Electrónica y Automática Industrial Sistemas Informáticos Industriales Curso 2015-16 4. Cómo añadir librería libws2_32.a a nuestro proyecto Para poder usar la librería winsock es necesario seguir los siguientes pasos para incluirla en nuestro proyeto: 1. File New Project y escogemos: Empty Project. 2. Click con botón derecho en Project1 y New File. 3. Pestaña: Project Project Options. 4. En la nueva ventana que aparece nos vamos a la pestaña Parameters y Pulsamos el botón Add Library or Objet. 5. Buscamos la librería libws2_32.a que está ubicada en (según la instalación): a. “C:/Dev-Cpp/MinGW32/lib/” b. "C:/Program Files (x86)/Dev-Cpp/MinGW32/lib/libws2_32.a" 6/9 Escuela Politécnica Superior de Elche Grado de Ing. Electrónica y Automática Industrial Sistemas Informáticos Industriales Curso 2015-16 ANEXO: Principales estructuras a emplear con sockets bajo Windows y prototipos de las funciones más importantes. Recordemos las principales estructuras que se deben emplear en sockets bajo el sistema operativo Windows: • Estructura sockaddr_in: Es una estructura que contiene una dirección de internet struct sockaddr_in { short sin_family; /* must be AF_INET */ u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; /* Not used, must be zero */ }; • Estructura hostent: Estructura que define un host en internet struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses from name server */ #define h_addr h_addr_list[0] /* address, for backward compatiblity */ }; Asimismo, tal y como hemos visto en clase de teoría, los prototipos de algunas de las funciones más importantes que debemos emplear son las siguientes: • Función WSAStartup Inicializa la Dll encargada de los sockets de Windows. int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData); • Función WSACleanup Libera la DLL encargada de los sockets de Windows. int WSACleanup (void); • Función socket: SOCKET socket (int af, int type, int protocol); 7/9 Escuela Politécnica Superior de Elche Grado de Ing. Electrónica y Automática Industrial Sistemas Informáticos Industriales Curso 2015-16 • Función bind int bind (SOCKET sock, const struct sockaddr FAR* name, int name len); • Función listen int listen (SOCKET sock, int backlog); • Función accept SOCKET accept (SOCKET sock, struct sockaddr FAR* addr, int FAR* addrlen); • Función connect int connect (SOCKET sock, const struct sockaddr FAR* name, int n amelen); • Función send int send (SOCKET sock, const char FAR * buf, int len, int flags) ; • Función recv int recv (SOCKET sock, char FAR* buf, int len, int flags); • Función closesocket int closesocket (SOCKET sock); • Función getprotobyname Retorna un puntero a una estructura la cual contiene el nombre y el número del protocolo correspondiente con el nombre ingresado. struct protoent FAR * getprotobyname (const char FAR * name ); • Función getsockname Devuelve la dirección del socket. int getsockname (SOCKET sock, struct sockaddr FAR* name, int FAR * namelen ); • Función gethostbyaddr Obtiene información del Host a través de su dirección. struct hostent FAR * gethostbyaddr (const char FAR * addr, int l en, int type); • Función gethostbyname Obtiene información del Host a través de su nombre. struct hostent FAR * gethostbyname (const char FAR * name); 8/9 Escuela Politécnica Superior de Elche Grado de Ing. Electrónica y Automática Industrial Sistemas Informáticos Industriales Curso 2015-16 • Función gethostname Obtiene información del Host Local Predeterminado. int gethostname (char FAR * name, int namelen ); • Función inet_addr Convierte una dirección IP en notación números y puntos, en un unsigned long, retorna la dirección en network byte order. unsigned long inet_addr (const char FAR * cp ); • FUNCIÓN ADICIONAL Estas dos líneas permiten que la función recv no se quede bloqueada a la espera de recibir un mensaje. El tiempo seleccionado es de 0.1 segundos. Por tanto, si en 0.1 sesgundos no ha recibido ningún mensaje la ejecución del programa continúa. De esta forma, en un bucle se pueden comprobar las recepciones de varios clients. DWORD sock_timeout = 0.1*1000; setsockopt(comm_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&sock_timeout, sizeof(sock_timeout)); 9/9