52
  1  Prog ramación con sockets para Windows Índice 1 Introducción......... ...................................................................................................... 2 1.1 Arquitectura Cliente/Servidor........ ......................................................................  2 1.2 Concepto y tipos de sockets ..............................................................................  2 1.3 La API de Windows........ .................................................................................... 5 2 Operaciones básicas con sockets ............................................................................ 5 2.1 Inicialización de Ias DLLs ................................................................................ 5 2.2 Función socket ................................................................................................  7 2.3 Utilidades para las funciones ...........................................................................  8 2.4 Función bind .................................................................................................... 14 3 Operaciones para comunicaciones con UDP ........................................................... 16 3.1 Función sendto ................................................................................................. 16 3.2 Función recvfrom ….......................................................................................... 17 3.3 Función closesocket ......................................................................................... 17 3.4 Esquema c liente/servidor con UDP.......... .......................................................... 18 3.5 Un ejemplo con UDP ......................................................................................... 19 4 Operaciones para comunicaciones multicast .......................................................... 23 4.1 Función setsockopt .......................................................................................... 23 4.2 Función closesocket ........................................................................................ 26 4.3 Esquema cliente/servidor con multicast ............................................................ 27 4.4 Un ejemplo con multicast .................................................................................. 28 5 Operaciones para comunicaciones con TCP ........................................................... 32 5.1 Función connect ............................................................................................... 32 5.2 Función listen.......... ........................................................................................... 34  5.3 Función accept ................................................................................................. 34 5.4 Función send .................................................................................................... 37 5.5 Función recv .................................................................................................  37 5.6 Funciones closesocket y shutdown .................................................................. 38 5.7 Cliente con TCP........ ......................................................................................... 39 5.7.1 Ejemplo de un cliente con TCP .............................................................. 39 5.8 Servidor iterativo con TCP ........................................................................... 42 5.8.1 Esquema cliente/servidor con servidor iterativo con TCP ..................... 42 5.8.2 Un ejemplo con servidor iterativo con TCP ........................ .................. 43 5.9 Servidor concurrente con TCP ......................................................................  46 5.9.1 Función _beginthread ...........................................................................  46 5.9.2 Esquema cliente/servidor con servidor concurrente con TCP........ ........ 47 5.9.3 Un ejemplo con servidor concurrente con TCP......... ............................ 49

Sockets en Windows

  • Upload
    kurtx

  • View
    24

  • Download
    0

Embed Size (px)

Citation preview

Programacin con sockets para Windowsndice1 Introduccin............................................................................................................... 1.1 Arquitectura Cliente/Servidor.............................................................................. 1.2 Concepto y tipos de sockets .............................................................................. 1.3 La API de Windows............................................................................................ Operaciones bsicas con sockets ............................................................................ 2.1 Inicializacin de Ias DLLs ................................................................................ 2.2 Funcin socket ................................................................................................ 2.3 Utilidades para las funciones ........................................................................... 2.4 Funcin bind .................................................................................................... Operaciones para comunicaciones con UDP ........................................................... 3.1 Funcin sendto ................................................................................................. 3.2 Funcin recvfrom .......................................................................................... 3.3 Funcin closesocket ......................................................................................... 3.4 Esquema cliente/servidor con UDP.................................................................... 3.5 Un ejemplo con UDP ......................................................................................... Operaciones para comunicaciones multicast .......................................................... 4.1 Funcin setsockopt .......................................................................................... 4.2 Funcin closesocket ........................................................................................ 4.3 Esquema cliente/servidor con multicast ............................................................ 4.4 Un ejemplo con multicast .................................................................................. Operaciones para comunicaciones con TCP ........................................................... 5.1 Funcin connect ............................................................................................... 5.2 Funcin listen..................................................................................................... 5.3 Funcin accept ................................................................................................. 5.4 Funcin send .................................................................................................... 5.5 Funcin recv ................................................................................................. 5.6 Funciones closesocket y shutdown .................................................................. 5.7 Cliente con TCP................................................................................................. 5.7.1 Ejemplo de un cliente con TCP .............................................................. 5.8 Servidor iterativo con TCP ........................................................................... 5.8.1 Esquema cliente/servidor con servidor iterativo con TCP ..................... 5.8.2 Un ejemplo con servidor iterativo con TCP .......................................... 5.9 Servidor concurrente con TCP ...................................................................... 5.9.1 Funcin _beginthread ........................................................................... 5.9.2 Esquema cliente/servidor con servidor concurrente con TCP................ 5.9.3 Un ejemplo con servidor concurrente con TCP..................................... 2 2 2 5 5 5 7 8 14 16 16 17 17 18 19 23 23 26 27 28 32 32 34 34 37 37 38 39 39 42 42 43 46 46 47 49

2

3

4

5

1

Captulo 1. IntroduccinEn este captulo 1 se desea presentar una serie de conceptos necesarios para poder utilizar las funciones proporcionadas por la librera de sockets para Windows (winsock).

1.1 Arquitectura Cliente/ServidorEl modelo (o paradigma) cliente-servidor establece que en cualquier comunicacin entre un par de aplicaciones, una aplicacin debe comenzar la ejecucin y esperar a que la otra contacte con ella. El paradigma cliente-servidor divide las aplicaciones de comunicaciones en dos categoras, dependiendo de si la aplicacin espera la comunicacin o la inicia. A la aplicacin que inicia la comunicacin se la denomina cliente. A la aplicacin con la que contacta el cliente se la denomina servidor. La comunicacin entre el cliente y el servidor se puede resumir, en la mayora de los casos, de la siguiente manera: el cliente enva una (o varias) peticin(es) y espera una (o varias) respuesta(s) del servidor. Las peticiones y respuestas se realizan a travs de las operaciones proporcionadas por una conjunto de libreras denominadas API (son las siglas inglesas de Interfaz de Programacin de Aplicaciones)

SOLICITA UN SERVICIO AL SERVIDOR. LA/S PETICIN/ES SE HACEN UTILIZANDO LAS OPERACIONES DE UN API

cliente

servidor

PROCESO CLIENTE

PROCESO SERVIDOR

ESPERA LA PETICIN DE UN CLIENTE, PROCESA DICHA PETICIN, Y, EN LA MAYORA DE LOS CASOS, CONTESTA AL CLIENTE

Figura 1: Paradigma cliente/servidor.

1.2 Concepto y tipos de socketsEl identificador por el que el cliente (o el servidor) enva o recibe datos a travs de la red se denomina socket ("enchufe"). Un socket representa simplemente un punto de conexin entre la aplicacin y la red de comunicaciones. Es aqu donde aparece el famoso principio cliente/servidor, por el cual uno de los sockets (o puntos de conexin) acta como servidor, atendiendo las peticiones del otro socket, que adopta el papel de cliente, enviando peticiones al

2

servidor y recibiendo a su vez el resultado de dichas solicitudes. Desde el punto de vista de los programadores, los sockets son los nicos identificadores de la red de comunicaciones y es a travs de ellos por donde se enviarn o se recibirn los datos. Desde el punto de vista de la red, un socket debe ser implementado de forma que se le identifique de forma unvoca con respecto a todas las posibles aplicaciones que puedan existir en la red. Para realizar esa identificacin depender de cul sea la red que vamos a utilizar. Hoy en da la red que se emplea en la inmensa mayora de los casos es la red Internet, tambin llamada arquitectura TCP/IP. En todo este tema vamos a centrar nuestro estudio en la comunicacin con sockets utilizando siempre la arquitectura TCP/IP. Un socket, desde el punto de vista de la arquitectura TCP/IP, est representado por dos elementos fundamentales: la y por el . La identifica la ubicacin del ordenador donde se encuentra la aplicacin con el socket. El identifica uno de los distintos procesos que pueden tener lugar en la mquina . En la siguiente figura 2 podemos ver que la aplicacin cliente (y servidora) utiliza en el cdigo la variable s_cli (s_serv) para poder acceder a la red Internet. El cliente enva los datos por el socket s_cli con la funcin de la librera del API de sockets send() (ms adelante se estudiar con detalle). En el caso del servidor, los datos se reciben por el socket s_serv con la funcin de la librera del API de sockets recv() (tambin ms adelante se estudiar esta funcin con detalle). Obsrvese tambin en la siguiente figura que, desde el punto de vista del nivel de transporte, el enchufe s_cli se implementa mediante la concatenacin de la direccin IP 199.33.22.12 y el nmero de puerto 3333. En el caso de s_serv es mediante la concatenacin de la direccin IP 130.40.50.10, y del nmero de puerto 80.

Cliente SOCKET s_cli; send(s_cli, ) s cli

Servidor SOCKET s_serv; recv(s_serv, ) s serv Nivel Transporte Nivel Aplicacin

TCP o UDP IP interfaz de redFigura 2: Identificacin de los sockets en la arquitectura TCP/IP.

Nivel Red

3

Se puede decir que hay dos clases de aplicaciones clientes: aquellos que invocan servicios estndar TCP/IP y aquellos que invocan servicios a definir. Los servicios estndar son aquellos servicios ya definidos por TCP/IP, y que por lo tanto tienen ya asignado un nmero de puerto (llamado puerto bien-conocido o well-known). Por ejemplo, 80 es el nmero de puerto para el servidor web (http). Los puertos bien-conocidos estn en el rango de 1 a 1024. Consideramos al resto como servicios a definir, y su rango ser superior a 1024. En la mayora de sistemas operativos hay que tener permisos especiales para poder ejecutar los servidores que implementan los servicios estndar (puertos por debajo del 1024). Por ejemplo en UNIX, slo los puede ejecutar el super-usuario (o tambin llamado usuario root)

Tipos de socketsCuando los programadores disean las aplicaciones cliente-servidor, deben elegir entre dos tipos de interaccin: orientada a conexin y no orientada a conexin. Los dos tipos de interaccin corresponden directamente a los dos protocolos de nivel de transporte que suministra la familia TCP/IP. Si el cliente y el servidor se comunican usando UDP, la interaccin es no orientada a conexin. Si utilizan TCP, la interaccin es orientada a conexin. Vase el tema anterior para un conocimiento ms exhaustivo de ambos protocolos. TCP proporciona toda la fiabilidad necesaria para la comunicacin a travs de la Internet. Para ello, verifica que los datos llegan y automticamente retransmite los segmentos que no llegan. Computa un checksum sobre los datos para garantizar que no se corrompen durante la transmisin. Usa nmeros de secuencia para asegurar que los datos llegan en orden, y automticamente elimina segmentos duplicados. Proporciona control de flujo para asegurar que el emisor no transmite datos ms rpidos que el receptor puede consumir. Finalmente, TCP informa tanto al cliente como al servidor si la red es inoperante por algn motivo. Los clientes y servidores que utilizan UDP no tienen garanta acerca de una entrega fiable. Cuando un cliente enva una peticin, la peticin se puede perder, duplicar, retardar o entregar fuera de orden. Las aplicaciones del cliente y servidor tienen que tomar las acciones oportunas para detectar y corregir tales errores (si quieren hacerlo). Como se puede observar, un protocolo orientado a conexin hace ms fcil la tarea del programador al liberarle de la tarea de detectar y corregir errores. Desde el punto de vista del programador, UDP funciona bien si la red que hay por debajo funciona bien, o no le preocupa que se produzcan errores. Por ejemplo, en una LAN el protocolo UDP suele funcionar muy bien, ya que la tasa de errores es muy baja. Los principales tipos de sockets son: Sockets de flujo (stream sockets): Utilizan el protocolo de transporte TCP. Sockets de datagramas (datagram sockets): Utilizan el protocolo de transporte UDP.

4

1.3 La API de WindowsEn la mayora de las implementaciones, el protocolo TCP/IP reside en el sistema operativo. Por tanto si un programa de aplicacin usa TCP/IP para comunicarse, debe interactuar con el sistema operativo para pedir un servicio. Desde el punto de vista del programador, las rutinas que el sistema operativo suministra definen el interfaz entre la aplicacin y el protocolo en concreto de Internet. La arquitectura TCP/IP no especifica los detalles de como la aplicacin debe interactuar con la pila de protocolos de la arquitectura TCP/IP. Es decir, la arquitectura TCP/IP no define un determinado API. Varias APIs han sido creadas para poder utilizar los protocolos de la arquitectura TCP/IP. La ms famosa y ampliamente utilizada es la API de sockets. El diseo original de esta API parti de un grupo de diseadores de Berkeley all por los aos 80. Estas funciones de la API de sockets se implementaron, en el caso de la pila de protocolos de Internet, sobre una plataforma con el sistema operativo UNIX (la primera versin que incorpor esta API fue la 4.3BSD). Esta definicin del API hecha por los diseadores de Berkeley se ha venido incorporando desde entonces en todas las versiones con UNIX y LINUX hasta nuestros das. En este tema tambin vamos a centrar nuestro estudio en la API de sockets, pero implementada sobre el sistema operativo de Windows. A esta API de sockets para windows se la denomina Winsock. Es importante resaltar que aunque la API de sockets de Berkeley y Winsock son muy parecidas, no son totalmente iguales, y por tanto las aplicaciones no son portables directamente entre s.

Captulo 2. Operaciones bsicas con socketsEn este captulo se van a presentar todas las funciones y estructuras de datos que van a necesitarse para poder manejar los sockets, independientemente de si son sockets de flujo (stream sockets) o sockets de datagramas (datagram sockets). Es decir, se presentarn las operaciones comunes a los sockets tanto si emplean el protocolo UDP o el protocolo TCP.

2.1 Inicializacin de Ias DLLsAntes de poder utilizar ninguna funcin de la API, un proceso debe inicializar la DLL de Winsock (ws2_32.dll). El prototipo en C de la funcin es:

#include int WSAStartup(WORD version, WSADATA *wsa_datos); 5

El primer parmetro determina el nmero de versin de Winsock ms alto que nuestro programa puede manejar (en nuestro caso usamos la 2.2). Se puede poner la versin utilizando la macro MAKEWORD. El segundo parmetro es un puntero a una estructura de tipo WSADATA, que recibir informacin sobre la implementacin de Winsock que tengamos en nuestro ordenador: su nmero de versin, una descripcin y el estado actual de la misma, etc. Si la llamada tiene xito, ya podremos usar el resto de las funciones de sockets. Un ejemplo de utilizacin de la funcin WSAStartup() es:

#include ... int error; WSADATA wsa_datos; ...

error = WSAStartup(MAKEWORD( 2, 2 ), &wsa_datos); if ( error != 0 ) exit(1); // error al iniciar la DLL if ( LOBYTE( wsa_datos.wVersion ) != 2 || HIBYTE( wsa_datos.wVersion ) != 2 ) { WSACleanup( ); exit(2); //error en version DLL } ...

Para compilar hay que decirle al compilador que enlace la biblioteca Winsock (ws2_32.dll). Con Visual Studio esto debe hacerse desde el propio proyecto (en men Proyecto -> Propiedades -> Vinculador -> Entrada, y se aade "ws2_32.lib" ). Para una explicacin ms detallada, ver el tema de herramientas grficas.

Por ltimo, al finalizar la utilizacin de todas las funciones de la API Winsock hay que ejecutar la funcin WSACleanup() para descargar correctamente todas estructuras asignadas por la DLL. No obstante, si se nos olvida utilizarla, el sistema descarga la correspondiente DLL de forma automtica al finalizar la ejecucin de cualquier programa. Esto es as porque, como veremos ms adelante, muchos servidores no pueden invocan a esta funcin al tener que ejecutarse en un bucle permanente. 6

2.2 Funcin socketUna vez inicializada la DLL, para que una aplicacin pueda realizar operaciones de E/S en red para comunicarse con otra aplicacin remota, lo primero que tiene que hacer es crear un socket al cual pueda dirigirse. Obviamente, esto es necesario tanto en el cliente como en servidor. El prototipo en C de la funcin es:

#include SOCKET socket(int familia_protos, int tipo, int proto);

El parmetro familia_protos especifica la familia de protocolos que usaremos. Los diseadores de la API de Berkeley pensaron que podran coexistir muchas arquitecturas de comunicaciones que soportaran las operaciones proporcionadas por el interfaz. Hoy en da en la prctica totalidad de los casos se utiliza la arquitectura TCP/IP (o tambin llamada internet). En el caso de internet que es el que nos concierne en este tema, la constante empleada ser PF_INET. Por su parte, tipo indica si el tipo de socket que vamos a crear es de flujo o de datagramas, es decir, si usa TCP o UDP. Se emplea el valor SOCK_STREAM para crear un socket de flujo, y SOCK_DGRAM para crear el socket de datagramas. Por ltimo, el parmetro proto establece el protocolo que se usar en este socket dentro de la familia de protocolos familia_protos. Con el valor cero el protocolo es asignado automticamente por Winsock. Cualquier valor distinto de cero da la posibilidad de utilizar otros protocolos que no sean el estndar. Si todo va bien, obtendremos un valor de tipo SOCKET, que representa el nuevo punto de conexin obtenido y que tendremos que usar en las siguientes funciones para llegar a establecer una comunicacin completa. En caso de error, la funcin devolver la constante INVALID_SOCKET. Un ejemplo de utilizacin de la funcin socket() es:

#include #include ... SOCKET s; ... s = socket(PF_INET, SOCK_DGRAM, 0); if (s = = INVALID_SOCKET) { printf("ERROR AL CREAR EL SOCKET: %d\n",WSAGetLastError()); exit(1); } ...

7

Ntese en el ejemplo que en caso de error al crear el socket se emplea la funcin WSAGetLastError(). Esta funcin se puede utilizar siempre que se produzca un error al invocar cualquier funcin del API Winsock.

2.3 Utilidades para las funcionesPara que un socket pueda ser til debe estar asociado a una determinada direccin en internet que lo convierta en un punto nico dentro de Internet. Esto se conseguir asignndole al socket el par formado por la direccin IP de la mquina donde se ejecuta la aplicacin, y un nmero de puerto no ocupado ya por otro socket. Para hacer esto el lenguaje C proporciona una serie de estructuras de datos. La primera es una estructura genrica pensada para poder trabajar con mltiples arquitecturas de protocolos.

struct sockaddr { u_short sa_family; char sa_data[14]; };

Debido a esta generalidad no es muy usada. Pensada para Internet existe la siguiente estructura de datos:

struct sockaddr_in short u_short struct in_addr char };

{ sin_family; sin_port; sin_addr; sin_zero[8];

El campo sin_family indica la familia o formato de direcciones. En el caso de la arquitectura TCP/IP es el valor de la constante AF_INET el que hay que utilizar. El campo sin_port contendr un nmero con el puerto elegido. En el campo sin_addr debe ponerse una direccin IP en binario (cada uno de los cuatro bytes se ponen en binario por separado). La estructura in_addr tiene la siguiente estructura: 8

struct in_addr{ union { struct {u_char s_b1, s_b2, s_b3,s_b4;} S_un_b; S_un_w; struct { u_short s_w1, s_w2;} u_long S_addr; }S_un; }; #define s_addr S_un.S_addr

Normalmente slo la definicin s_addr del campo S_un.S_addr va a ser utilizado, como veremos ms adelante. No hay que hacer nada ms. No obstante, en el campo sin_zero de la estructura sockaddr_in debe encontrarse con todos sus campos a cero. Para asegurarnos de ello, normalmente se utiliza la funcin memset(). Tanto la estructura sockaddr como sockaddr_in se encuentran declaradas en . Posteriormente se van a presentar mltiples ejemplos de uso de esta estructura sockaddr_in.

ConversionesPara poder trabajar con los datos de la estructura sockaddr_in (bsicamente una direccin IP y un nmero de puerto) debemos tener en cuenta un aspecto muy importante: el orden de almacenamiento de los bytes dentro de las variables. Los campos sin_addr.s_addr y sin_port de la estructura sockaddr_in deben tener almacenados sus valores en el formato network byte order". El problema es que los ordenadores almacenan los datos en el formato host byte order, y ambos formatos no siempre coinciden. Para evitar esta posible disparidad, existen funciones que aseguren el buen almacenamiento de la informacin. Estas funciones son: Para el almacenamiento de un nmero de puerto (que tiene 16 bits) pasndolo del host byte order al network byte order: htons(). Para el almacenamiento de una direccin IP (que tiene 32 bits) pasndola del host byte order al network byte order: htonl(). Recurdese que con estas funciones se garantiza el orden que deben tener los datos en los campos sin_addr.s_addr y sin_port de la estructura sockaddr_in. En algunas ocasiones nos ocurrir lo contrario, tenemos datos en los campos sin_addr.s_addr y sin_port de la estructura sockaddr_in y queremos pasarlos a alguna variable de la aplicacin (que obviamente debe ser almacenada en el formato host byte order). Para ello contamos con las siguientes funciones: 9

Para el almacenamiento de un nmero de puerto (que tiene 16 bits) pasndolo del network byte order al host byte order: ntohs(). Para el almacenamiento de una direccin IP (que tiene 32 bits) pasndola del network byte order al host byte order: ntohl().

En el siguiente ejemplo presentamos un posible caso.

#include ... u_short puerto1, puerto2; //unsigned short es igual que u_short struct sockaddr_in direccion1, direccin2; ... puerto1=80; // valor almacenado en host byte order direccion1.sin_port=htons(puerto); //valor almacenado en //network byte order ... puerto2=ntohs(direccion2.sin_port); //valor almacenado en //host byte order

Vemos en el ejemplo que la variables puerto1 y puerto2 deben almacenar sus valores en el host byte order, mientras que las variables direccion1 y direccion2 deben hacerlo en el network byte order

Direcciones IPPara poder manejar de forma correcta las direcciones IP, el API Winsock proporciona las siguientes operaciones: La funcin inet_addr() convierte una direccin IP en un entero largo sin signo (u_long). Es importante resaltar que esta funcin devuelve el valor en el formato network byte order, por lo que no hay que utilizar la funcin htonl(). La funcin inet_ntoa() convierte un entero largo sin signo a una cadena de caracteres. Un ejemplo de utilizacin de direcciones IP: #include #include ... struct sockaddr_in direccion; char * cadena; ... direccion.sin_addr.s_addr = inet_addr("138.100.152.2"); cadena=inet_ntoa(direccion.sin_addr); printf("dir IP=%s\n",cadena); // imprime 138.100.152.2 ... 10

Otra forma de poder asignar una direccin IP en el campo sin_addr.s_addr de la estructura sockaddr_in es utilizando la constante INADDR_ANY. Esta constante le indica al sistema que asigne la direccin IP que ese equipo tenga. Utilizar esa constante permite poder portar directamente el cdigo de una mquina a otra sin tener que volver a compilar porque la direccin IP haya cambiado. A veces en vez de disponer de la direccin IP, lo que tenemos es el nombre de dominio del equipo. Para poder convertir ese nombre de dominio en el formato necesario para el campo sin_addr.s_addr de la estructura sockaddr_in, disponemos de la estructura hostent y de la funcin gethostbyname(), que vamos a explicar a continuacin:

struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; }; #define h_addr h_addr_list[0]

h_name: Es el nombre oficial del equipo. h_aliases: Es un array con los nombres alternativos del equipo. h_addrtype: Tipo de la direccin (en el caso de Internet, es AF_INET). h_length: Longitud de la direccin (en bytes). h_addr_list: Un array (terminado en cero) de direcciones IP del equipo. Es muy importante resaltar que las direcciones IP siguen el formato network byte order, por lo que no hay que utilizar la funcin htonl(). h_addr: Como ya sabemos, la mayora de los hosts slo tienen una direccin IP. Para facilitar su uso, se define La primera direccin de h_addr_list.

Esta estructura est definida en . En la mayora de los casos, de todos los campos slo se suele utilizar h_addr_list[0] (en realidad, h_addr) para convertir a una direccin IP un determinado nombre de domininio de un equipo. Para ello se utiliza la funcin gethostbyname(), cuyo prototipo en C es: 11

#include struct hostent *gethostbyname(const char *nombre);

El parmetro nombre indentifica el nombre de dominio del equipo cuya estructura hostent queremos que nos devuelva (en realidad nos devuelve un puntero a esa estructura). Si devuelve NULL, es porque ha habido un error. Obviamente para que esta funcin no de error, el nombre de dominio que pasamos debe estar dado de alta en la estructura de DNS (Servidor de Nombres de Dominio), y el sistema operativo de la aplicacin tener acceso a uno de estos DNS. Un ejemplo de esta utilizacin sera:

#include #include ... struct sockaddr_in direccion; struct hostent *datosHost; ... datosHost=gethostbyname("fenix.eui.upm.es"); if (datosHost==NULL){ printf("ERROR no existe ese nombre de dominio\n"); exit(1); } direccion.sin_addr=*((struct in_addr *)datosHost->h_addr); ...

Nmeros de puerto bien-conocidosComo ya se ha visto, se utiliza el campo sin_port de la estructura sockaddr_in para indicar al socket el puerto al que queremos asociarlo. A veces vamos a querer utilizar el puerto de un servicio estndar, es decir, ya definido. Para poder obtener ese puerto bien-conocido (well-known) de un determinado servicio estndar disponemos de la estructura servent y de la funcin getservbyname(), que vamos a explicar a continuacin: 12

struct servent { char *s_name; char **s_aliases; short s_port; char *s_proto; };

s_name: Es el nombre del servicio estndar. s_aliases: Es un array con los posibles nombres alternativos del servicio s_port: Indica el puerto del servicio. Es muy importante resaltar que este nmero sigue el formato network byte order, por lo que no hay que utilizar la funcin htons(). s_proto: Es el nombre del protocolo que implementa el servicio.

Esta estructura est definida en . En la mayora de los casos, de todos los campos slo se suele utilizar s_port. Para ello se utiliza la funcin getservbyname(), cuyo prototipo en C es:

#include struct servent *getservbyname(const char *servicio, const char *protocolo);

13

Un ejemplo de esta utilizacin sera:

#include #include ... struct sockaddr_in direccion; struct servent *datosServicio; short puerto; ... datosServicio=getservbyname("http","tcp"); if (datosServicio==NULL){ printf("ERROR no existe ese servicio estandar\n"); exit(1); } direccion.sin_port=datosServicio->s_port; ... puerto=ntohs(direccion.sin_port); printf("puerto del servicio=%d\n",puerto); ...

Ntese en el ejemplo que la variable puerto, como todas las variables de un programa excepto las del tipo sockaddr_in (y sockaddr), debe tener el formato host byte order. Por eso si se quiere que el equipo almacene bien el nmero debe utilizarse la funcin ntohs(). Como pequeo ejercicio pruebe que pasara si se elimina la funcin ntohs() del ejemplo anterior.

2.4 Funcin bindComo ya se ha comentado previamente, un socket necesita asociarse a una direccin para poder enviar o recibir datos por la red. Esta asociacin puede hacerse de forma explcita o implcita (ver Figura 2). Normalmente son los servidores los que de forma explcita (mediante la funcin bind) eligen la direccin a la que unirse. En la mayora de los casos, como los host slo disponen de una direccin IP, lo que se debe elegir es un nmero de puerto que no est ocupado por otra aplicacin. En el caso de los clientes, normalmente dejan que sean otras funciones utilizadas en la comunicacin (connect, sendto, recvfrom, ) las que elijan el puerto al que unirse (usualmente escogen el primer puerto que no est ya ocupado). No obstante un cliente tambin puede de forma explcita unirse a una determinada direccin, aunque no es lo habitual.

14

El prototipo en C de la funcin es: #include int bind(SOCKET s, const struct sockaddr * dir, int long_dir);

El primer parmetro s es el socket devuelto por la funcin socket(), el parmetro dir es un puntero a la estructura sockaddr (ver seccin 2.3), donde deber ponerse la direccin (es decir, el par ,) a la que se quiere unir el socket. Recurdese de la seccin 2.3 que es ms fcil de usar la estructura sockaddr_in. Para evitar warnings del compilador, si vamos a utilizarla hay que hacer un casting al tipo sockaddr (ver el ejemplo siguiente). El parmetro long_dir indica el tamao de la estructura apuntada por dir. Esta funcin devuelve 0 si todo ha ido bien, y SOCKET_ERROR si no se ha podido unir a la direccin apuntada por dir. Un ejemplo de utilizacin de la funcin bind() es:

#include #include ... SOCKET s; struct sockaddr_in dirMiEquipo; int resul; ... s = socket(PF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET) exit(1); //error al crear el socket memset(&dirMiEquipo, 0, sizeof(struct sockaddr_in));// pone a // cero toda la estructura dirMiEquipo.sin_family = AF_INET; dirMiEquipo.sin_addr.s_addr = INADDR_ANY; // IP que tenga el equipo dirMiEquipo.sin_port = htons(2222); //elijo un puerto libre resul=bind(s, (struct sockaddr *) &dirMiEquipo, sizeof(dirMiEquipo)); if (resul == SOCKET_ERROR){ printf("ERROR AL UNIR EL SOCKET: %d\n",WSAGetLastError()); exit(2); } ...

15

Recurdese de la seccin 2.3 que tenemos toda una serie de estructuras y funciones para poder manejar la direccin de un socket: inet_addr(), gethostbyname(), Tambin es muy importante conocer que el orden en el que almacenan los datos tanto en la estructura sockaddr (y sockaddr_in) como en el resto de variables del equipo. Por tanto, hay que utilizar correctamente las funciones htonl(), htons(), ntohl(), y ntohs().

Captulo 3. Operaciones para comunicaciones con UDPEn este captulo se van a presentar todas las funciones que van a necesitarse para poder comunicar un cliente y un servidor utilizando sockets de datagramas, es decir, utilizando el protocolo UDP.

3.1 Funcin sendtoEsta funcin permite a un socket enviar informacin a travs de la red a otro socket que se encuentra en una determinada direccin (es decir, al socket de una aplicacin con un determinado par formado por y ). El prototipo en C de la funcin es:

#include

int sendto(SOCKET s, const char *msj, int long_msj, int flags, const struct sockaddr *dirDestino, int long_dirDestino);

Esta function enva el array de datos contenido en el parmetro msj por el socket s. El parmetro long_msj indica el tamao del parmetro anterior. El parmetro flags permite enviar datos con distintas opciones (fuera de banda, adelantados, etc). Un envo normal de datos se consigue poniendo en este campo flags un 0. El parmetro dirDestino es un puntero a la estructura sockaddr, donde deber ponerse la direccin del socket de la aplicacin donde se quieren enviar los datos. Podemos utilizar tambin la estructura sockaddr_in, pero haciendo casting con sockaddr para evitar warnings del compilador. El parmetro long_dirDestino indica el tamao de la estructura apuntada por dirDestino. Esta funcin devuelve el nmero de bytes enviados por la red si todo ha ido bien, y SOCKET_ERROR si se ha producido un fallo al enviar. Al final de esta seccin se presenta un ejemplo donde se utilizar esta funcin sendto(). 16

3.2 Funcin recvfromEsta funcin permite a un socket recibir informacin a travs de la red, indicndonos desde qu direccin nos envan dicha informacin. El prototipo en C de la funcin es:

#include

int recvfrom(SOCKET s, const char *msj, int long_msj, int flags, struct sockaddr *dirDestino, int *long_dirDestino);

Esta function recibe para el socket s una serie de datos que almacena en el array del parmetro msj. El parmetro long_msj indica el tamao del parmetro anterior. El parmetro flags permite, al igual que en el caso de sendto, recibir datos con distintas opciones (fuera de banda, adelantados, etc). Una recepcin normal de datos se consigue poniendo en el campo flags un 0. Ntese, a diferencia de lo que pasa en sendto(), que a priori no podemos saber quin ser quien nos va a enviar los datos. Por lo tanto, esto dos ltimos parmetros los rellenar el sistema una vez que se reciban los datos, nunca la aplicacin que invoca a esta funcin. Por ello el parmetro dirDestino es un puntero a la estructura sockaddr, donde deber recibirse la direccin del socket de la aplicacin que nos ha enviado los datos. Al igual que con sendto(), podemos utilizar tambin la estructura sockaddr_in, pero haciendo casting con sockaddr para evitar warnings del compilador. El parmetro long_dirDestino es (a diferencia de la funcin sendto()) un puntero que nos indica el tamao de la estructura apuntada por dirDestino. Esta funcin devuelve el nmero de bytes recibidos si todo ha ido bien, y SOCKET_ERROR si se ha producido un fallo al enviar. Al final de esta seccin se presenta un ejemplo donde se utilizar esta funcin recvfrom().

3.3 Funcin closesocketEsta funcin en los sockets de datagramas slo tiene un efecto local a la aplicacin que lo invoca. La funcin closesocket() se invoca cuando la aplicacin ya no quiere hacer uso del socket que cre previamente. Hay que resaltar que esta funcin siempre debe ser invocada antes de la funcin WSACleanup(). El prototipo en C de la funcin closesocket() es:

#include

int closesocket(SOCKET s);

17

Esta funcin closesocket() devuelve 0 si todo ha ido bien, y SOCKET_ERROR si se ha producido un fallo al cerrar el socket.

3.4 Esquema cliente/servidor con UDPEn la siguiente figura 3 presentamos un esquema con las funciones a utilizar para una comunicacin con el protocolo UDP. Se ha supuesto, por sencillez, que se hace nicamente un envo y una recepcin de datos entre el emisor y el receptor. Obsrvese que la funcin recvfrom() es bloqueante, por lo que hasta que no reciba los datos (enviados mediante la funcin sendto()) la aplicacin no pasar a ejecutar ninguna otra instruccin.

Servidor Cliente

WSAStartup( )

WSAStartup( ) socket( )

socket( ) bind( )

DATOS (PETICION)

recvfrom( )BLOQUEO

sendto( ) recvfrom( )BLOQUEO

DATOS (RESPUESTA)

sendto( ) closesocket() WSACleanup( )

closesocket()) WSACleanup( )

Figura 3: Una posible comunicacin con sockets de datagrama 18

3.5 Un ejemplo con UDPPara clarificar los conceptos presentados hasta ahora, se va a presentar un ejemplo sencillo de comunicacin con UDP siguiendo el esquema del apartado anterior. En l tanto el cliente como el servidor enviarn un mensaje de saludo. En Windows Visual Studio podemos crear un proyecto con el cliente y el servidor. Con otros compiladores, con tener un fichero con la extensin .c ser suficiente.

El cliente#include #include #include void main(){ SOCKET s; struct sockaddr_in dir_serv; int resul, puerto_serv, error, long_dir_serv; WSADATA wsa_datos; char cadena_dir_ip_serv[20]; // cadena con la ip del servidor char msj_env[80]; // datos a enviar char msj_rec[80]; // datos a recibir printf("--- CLIENTE ---\n"); printf("Direccion IP del servidor="); scanf("%s",&cadena_dir_ip_serv); //lee la dir IP del servidor printf("Puerto del servidor="); scanf("%d",&puerto_serv); //lee el puerto del servidor error = WSAStartup(MAKEWORD( 2, 2 ), if ( error != 0 ) exit(1); // error if ( LOBYTE( wsa_datos.wVersion ) != HIBYTE( wsa_datos.wVersion ) != WSACleanup( ); exit(1); } &wsa_datos); al iniciar la DLL 2 || 2 ) {

s = socket(PF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET){ printf("ERROR AL CREAR EL SOCKET: %d\n",WSAGetLastError()); exit(2); } strcpy(msj_env,"Me saludas?, soy el cliente"); memset(&dir_serv, 0, sizeof(struct sockaddr_in)); dir_serv.sin_family = AF_INET; dir_serv.sin_addr.s_addr = inet_addr(cadena_dir_ip_serv); dir_serv.sin_port = htons(puerto_serv);

19

resul=sendto(s, msj_env, sizeof(msj_env),0, (struct sockaddr *) &dir_serv, sizeof(dir_serv)); if (resul == SOCKET_ERROR){ printf("ERROR AL ENVIAR: %d\n",WSAGetLastError()); exit(3); } long_dir_serv=sizeof(dir_serv); resul=recvfrom(s, msj_rec, sizeof(msj_rec),0, (struct sockaddr *) &dir_serv, &long_dir_serv); if (resul == SOCKET_ERROR){ printf("ERROR AL recibir: %d\n",WSAGetLastError()); exit(4); } printf("MENSAJE recibido: %s\n",msj_rec); closesocket(s); WSACleanup( ); } // fin del main

Figura 4: Cdigo del cliente UDP

Con todo el cdigo de la figura anterior se puede crear un fichero al que llamar, por ejemplo, clienteUDP.cpp en Windows Visual Studio. Obsrvese que lo nico que hace el cliente es enviar un mensaje a la direccin IP y puerto del servidor que se le pasen por la consola. La direccin del equipo servidor hay que pasarla como notacin decimal con puntos. Por ejemplo, una direccin vlida sera: 192.168.200.128 Si no se tiene red en el equipo, se puede pasar como direccin del servidor la 127.0.0.1 (que es la direccin local del propio equipo, o tambin llamada localhost) Obsrvese que el socket del cliente no se une de manera explcita (es decir con la funcin bind()) a ninguna direccin. Es el sistema, al ejecutar sendto(), el que le asignar al cliente una direccin IP (la de la mquina) y un nmero de puerto (el primero que encuentre libre).

20

El servidor#include #include #include void main(){ SOCKET s; struct sockaddr_in dirMiEquipo, dir_cli; int resul, error, long_dir_cli; WSADATA wsa_datos; char msj_env[80]; // datos a enviar char msj_rec[80]; // datos a recibir error = WSAStartup(MAKEWORD( 2, 2 ), &wsa_datos); if ( error != 0 ) exit(1); // error al iniciar la DLL if ( LOBYTE( wsa_datos.wVersion ) != 2 || HIBYTE( wsa_datos.wVersion ) != 2 ) { WSACleanup( ); exit(1); } printf("--- SERVIDOR ---\n"); s = socket(PF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET){ printf("ERROR AL CREAR EL SOCKET: %d\n",WSAGetLastError()); exit(2); } memset(&dirMiEquipo, 0, sizeof(struct sockaddr_in)); dirMiEquipo.sin_family = AF_INET; dirMiEquipo.sin_addr.s_addr = INADDR_ANY; dirMiEquipo.sin_port = htons(8888); // puerto del servidor resul=bind(s, (struct sockaddr *) &dirMiEquipo, sizeof(dirMiEquipo)); if (resul == SOCKET_ERROR){ printf("ERROR AL UNIR EL SOCKET: %d\n",WSAGetLastError()); exit(3); }

21

long_dir_cli=sizeof(dir_cli); resul=recvfrom(s, msj_rec, sizeof(msj_rec),0, (struct sockaddr *) &dir_cli, &long_dir_cli); if (resul == SOCKET_ERROR){ printf("ERROR AL recibir: %d\n",WSAGetLastError()); exit(4); } printf("MENSAJE recibido: %s\n",msj_rec); strcpy(msj_env,"Hola, soy el servidor"); resul=sendto(s, msj_env, sizeof(msj_env),0, (struct sockaddr *) &dir_cli, sizeof(dir_cli)); if (resul == SOCKET_ERROR){ printf("ERROR AL ENVIAR: %d\n",WSAGetLastError()); exit(5); } closesocket(s); WSACleanup( ); } // fin del main

Figura 5: Cdigo del servidor UDP

Con todo el cdigo de la figura anterior se puede crear un fichero al que llamar, por ejemplo, servidorUDP.cpp en Windows Visual Studio. Lo que hace el servidor es unir su socket s a la direccin formada por: la IP de la mquina (INADDR_ANY) y al puerto 8888 (no hay que olvidarse de utilizar la funcin htons()). Una vez que el servidor recibe el mensaje del cliente, lo escribe en la consola y le responde.

22

Captulo 4. Operaciones para comunicaciones multicastEn el captulo anterior se analiz la comunicacin UDP cuando cada envo de datos (hecho mediante sendto()) slo tena un destinatario posible. Esto era as porque como se ha visto la direccin a la que se vinculaban los sockets era nica. A este tipo de comunicacin se la denomina unicast. En ciertas aplicaciones (como chats, foros, videoconferencias, etc) es necesario (por razones de eficiencia) que un nico envo de datos llegue a mltiples destinatarios. Esta forma de comunicarnos se denomina multicast. Obviamente, para que este mecanismo funcione necesitaremos que varios sockets se puedan vincular (explcitamente con la funcin bind()) a una misma direccin (denominada direccin multicast). Para poder distinguirlas, a las direcciones utilizadas en la comunicacin unicast tambin se las suele denominar como direcciones unicast. Estas direcciones multicast, como con las unicast, la tenemos que ver divididas en el par y . Recurdese, del tema donde se presentaban los conceptos de la arquitectuta TCP/IP, que las direcciones IP multicast eran de clase D (estaban en el rango desde 224.0.0.0 hasta 239.255.255.255). En el caso de los puertos no hay nada especial, siguen siendo nmeros con el mismo significado que en la comunicacin unicast. Por lo tanto, en la comunicacin multicast podemos tener a mltiples sockets unidos a la misma direccin: . En la API Winsock slo se pueden utilizar las direcciones de multicast con el protocolo UDP, es decir, con sockets de datagramas. Para ello, adems de utilizar las direcciones multicast y las funciones sendto() y recvfrom(), debemos utilizar otras funciones que preparen a las aplicaciones para el envo multicast. Aunque parece obvio, no est de ms decir de manera explcita que siempre se puede utilizar una comunicacin unicast y hacer sendto() de un mismo mensaje a un grupo de n direcciones unicast. Obviamente, esto supone n envos del mensaje a cada direccin unicast. Esto es mucho ms ineficiente que utilizando una comunicacin (y direcciones) multicast, ya que en este ltimo caso slo se enviar.un nico mensaje.

4.1 Funcin setsockoptEsta funcin permite cambiar la configuracin del driver que implementa un determinado socket. Tales cambios pueden ser: la modificacin del buffer donde se almacenan los datos, el protocolo que implementa al socket, la MTU, etc. Vamos a orientar esta explicacin a multicast. El prototipo en C de la funcin es:

#include #include

int setsockopt(SOCKET s, int nivel, int opcion, const char *valores, int long_opcion); 23

El primer parmetro s indica el socket sobre el que se van a cambiar algunas opciones. En el parmetro nivel sealamos el protocolo al que afectarn dichas modificaciones. El identificador de la opcin se incluye en el parmetro opcion, y en el parmetro valores ponemos los datos que queramos modificar de opcion. Por ltimo, long_opcion contiene el tamao de valores. Esta funcin setsockopt() devuelve 0 si todo ha ido bien, y SOCKET_ERROR si se ha producido un error. Un ejemplo de utilizacin de esta funcin orientado al uso multicast es:

#include #include ... SOCKET s; struct sockaddr_in dirMiEquipo; int resul; struct ip_mreq req_multi; int ttl; ... s = socket(PF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET) exit(1); //error al crear el socket memset(&dirMiEquipo, 0, sizeof(struct sockaddr_in)); dirMiEquipo.sin_family = AF_INET; dirMiEquipo.sin_addr.s_addr = INADDR_ANY; //IP unicast dirMiEquipo.sin_port = htons(6666); // puerto libre resul=bind(s, (struct sockaddr*) &dirMiEquipo, sizeof(dirMiEquipo)); if (resul == SOCKET_ERROR){ printf("ERROR AL UNIR EL SOCKET: %d\n",WSAGetLastError()); exit(3); } //asociamos la dir. IP unicast con la multicast req_multi.imr_interface.s_addr =INADDR_ANY; //IP unicast req_multi.imr_multiaddr.s_addr=inet_addr("224.10.20.30"); resul=setsockopt(s, IPPROTO_IP,IP_ADD_MEMBERSHIP, (const char *) & req_multi, sizeof(req_multi)); if (resul == SOCKET_ERROR){ printf("ERROR EN OPCIONES MULTICAST: %d\n",WSAGetLastError()); exit(4); } // ahora se puede recibir datos por ... 24

... //preparamos un posible envio multicast ttl=1; //saltos que puede dar el datagrama en multicast resul=setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl, sizeof(ttl)); if (resul == SOCKET_ERROR){ printf("ERROR EN OPCIONES MULTICAST: %d\n",WSAGetLastError()); exit(3); } // ahora se puede enviar datos multicast por ...

En el ejemplo vemos que hemos elegido la direccin IP multicast 224.10.20.30 y el puerto 6666 para unir al socket. Seleccionamos como opcin para el envo multicast el protocolo IP (IPPROTO_IP), y decimos (IP_ADD_MEMBERSHIP) que la aplicacin que ejecuta este cdigo se una a la direccin multicast . Esto ltimo lo que provoca es que el protocolo de multicast (de forma transparente para el programador) enve datos indicando que le incluyan como uno de los miembros de esa direccin multicast. A partir de ese momento tenemos el equipo preparado para recibir datos (con recvfrom()) por la direccin multicast. Para poder hacerlo vemos que utilizamos la variable req_multi del tipo struct ip_mreq con las siguiente operaciones del ejemplo: req_multi.imr_interface.s_addr =INADDR_ANY; //IP unicast req_multi.imr_multiaddr.s_addr=inet_addr("224.10.20.30"); Con ellas vamos a asociar en el interfaz la direccin unicast del equipo con la multicast. Para poder configurar el socket para enviar datos (con sendto()) a una direccin multicast, seleccionamos como opcin para el envo multicast el protocolo IP (IPPROTO_IP), y decimos (IP_MULTICAST_TTL) que la aplicacin va a poder enviar por ese socket a la direccin multicast . La variable ttl lo que hace es limitar el rango de equipos que componen los posibles miembros a los que llega un envo multicast. Como sabemos por el tema de la arquitectura TCP/IP, la red Internet est formada por muchas redes IP conectadas entre s por routers. El valor ttl=1 limita a todos los equipos dentro de la misma red los posibles miembros del multicast. Este ttl=1 es el valor por defecto. Obviamente se puede poner un valor mayor que 1, pero para que tenga efecto debe contar con el permiso de los distintos routers (normalmente este permiso est inhibido para evitar la inundacin de Internet por datos no deseados). Para aclararlo ms, seguidamente se va a presentar un ejemplo de multicast. 25

4.2 Funcin closesocketCon muticast esta funcin, adems de realizar las operaciones locales que mencionamos en la seccin 3.3, genera el envo de datos a travs de la red para informar que el grupo multicast ya no cuenta con ese miembro. Tanto la sintaxis como la utilizacin de esta funcin es igual que la ya descrita en la seccin 3.3.

26

4.2 Esquema cliente/servidor con multicastEn la figura 6 presentamos un posible esquema con las funciones a utilizar para una comunicacin con el protocolo UDP en multicast. Se ha supuesto, para hacerlo sencillo, que el cliente hace un envo, y el servidor estar permanentemente esperando recibir datos. Obsrvese que la funcin recvfrom() es bloqueante, por lo que hasta que no reciba los datos (enviados mediante la funcin sendto()) la aplicacin no pasar a ejecutar ninguna otra instruccin.

ClienteWSAStartup( )

ServidorWSAStartup( )

socket( ) setsockopt( )

socket( ) bind( )

sendto( )DATOS

setsockopt( )

closesocket( )

recvfrom( )BLOQUEO

WSAcleanup( )

closesocket( )

WSAcleanup( )

Figura 6: Una posible comunicacin multicast con sockets de datagrama 27

4.3 Un ejemplo con multicastSe va a presentar seguidamente el ejemplo de comunicacin con UDP multicast descrito en el esquema del apartado anterior. En este ejemplo el cliente manda un mensaje de saludo, y el servidor lo muestra en la pantalla. Para que se pueda ver el concepto de multicast lo interesante es ejecutar n clientes que manden los mensajes al servidor. Para ello ser suficiente con ejecutar n veces el cliente en n ventanas windows, y ejecutar en una ventana de windows el servidor. En Windows Visual Studio podemos crear un proyecto con el cliente y el servidor. Con otros compiladores, con tener un fichero con la extensin .c ser suficiente.

El cliente#include #include #include #include

void main(){ SOCKET s; struct sockaddr_in dir_serv; int resul, error; int ttl; char msj_env[80]; // datos a enviar WSADATA wsa_datos; error = WSAStartup(MAKEWORD( 2, 2 ), &wsa_datos); if ( error != 0 ) exit(1); // error al iniciar la DLL if ( LOBYTE( wsa_datos.wVersion ) != 2 || HIBYTE( wsa_datos.wVersion ) != 2 ) { WSACleanup( ); exit(1); } printf("--- CLIENTE MULTICAST ---\n"); s = socket(PF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET){ printf("ERROR AL CREAR EL SOCKET: %d\n",WSAGetLastError()); exit(2); } ttl=1; //saltos que puede dar el datagrama en multicast resul=setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (const char *) &ttl, sizeof(ttl)); if (resul == SOCKET_ERROR){ printf("ERROR EN OPCIONES MULTICAST: %d\n",WSAGetLastError()); exit(4); } 28

memset(&dir_serv, 0, sizeof(struct sockaddr_in)); dir_serv.sin_family = AF_INET; dir_serv.sin_addr.s_addr = inet_addr("224.10.20.30"); dir_serv.sin_port = htons(6666); strcpy(msj_env,"Envio multicast desde el cliente"); resul=sendto(s, msj_env, sizeof(msj_env),0, (struct sockaddr *) &dir_serv, sizeof(dir_serv)); if (resul == SOCKET_ERROR){ printf("ERROR AL ENVIAR EN MULTICAST: %d\n",WSAGetLastError()); exit(5); } closesocket(s); WSACleanup( ); } // fin del main

Figura 7: Cdigo del cliente UDP con multicast

Con todo el cdigo de la figura anterior se puede crear un fichero al que se puede llamar, por ejemplo, cliente_multicast.cpp en Windows Visual Studio. Obsrvese que lo nico que hace el cliente es enviar un mensaje a la direccin multicast: . Para ello seleccionamos las opciones IPPROTO_IP y la IP_MULTICAST_TTL. El valor ttl=1 es para que el envo multicast no se propague ms alla del router que forman todos los equipos de la misma red IP (que es lo permitido por defecto).

29

El servidor#include #include #include #include

void main(){ SOCKET s; struct sockaddr_in dirMiEquipo, dir_cli; int resul, error, long_dir_cli; char msj_rec[80]; // datos a recibir WSADATA wsa_datos; struct ip_mreq req_multi; error = WSAStartup(MAKEWORD( 2, 2 ), &wsa_datos); if ( error != 0 ) exit(1); // error al iniciar la DLL if ( LOBYTE( wsa_datos.wVersion ) != 2 || HIBYTE( wsa_datos.wVersion ) != 2 ) { WSACleanup( ); exit(1); } printf("--- SERVIDOR MULTICAST ---\n"); s = socket(PF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET){ printf("ERROR AL CREAR EL SOCKET: %d\n",WSAGetLastError()); exit(2); } memset(&dirMiEquipo, 0, sizeof(struct sockaddr_in)); dirMiEquipo.sin_family = AF_INET; dirMiEquipo.sin_addr.s_addr = INADDR_ANY; //IP multicast dirMiEquipo.sin_port = htons(6666); // puerto libre resul=bind(s, (struct sockaddr *) &dirMiEquipo, sizeof(dirMiEquipo)); if (resul == SOCKET_ERROR){ printf("ERROR AL UNIR EL SOCKET: %d\n",WSAGetLastError()); exit(3); }

30

req_multi.imr_interface.s_addr =INADDR_ANY; req_multi.imr_multiaddr.s_addr=inet_addr("224.10.20.30"); resul=setsockopt(s, IPPROTO_IP,IP_ADD_MEMBERSHIP, (const char *) & req_multi, sizeof(req_multi)); if (resul == SOCKET_ERROR){ printf("ERROR EN OPCIONES MULTICAST: %d\n",WSAGetLastError()); exit(4); } while(1) { long_dir_cli=sizeof(dir_cli); resul=recvfrom(s, msj_rec, sizeof(msj_rec),0, (struct sockaddr *) &dir_cli, &long_dir_cli); if (resul == SOCKET_ERROR){ printf("ERROR AL ENVIAR EN MULTICAST: %d\n",WSAGetLastError()); exit(5); } printf("MENSAJE recibido: %s\n",msj_rec); } closesocket(s); WSACleanup( ); } // fin del main

Figura 8: Cdigo del servidor UDP con multicast

Con todo el cdigo de la figura anterior se puede crear un fichero al que llamar, por ejemplo, servidor_multicast.cpp en Windows Visual Studio. Obsrvese que lo nico que hace el servidor es unirse primero a una direccin unicast (ver el cdigo de la pgina anterior a sta). Posteriormente, gracias a: req_multi.imr_interface.s_addr =INADDR_ANY; req_multi.imr_multiaddr.s_addr=inet_addr("224.10.20.30"); Asocia la direccin unicast con la direccin multicast 224.10.20.30. 31

Una vez hecha la asociacin, el programa lo que hace es esperar de forma indefinida a que le lleguen mensajes Obviamente, para finalizar este servidor hay que teclear en algn momento las teclas C. Como podr observarse, las funciones closesocket() y WSACleanup() no se van a ejecutar, por lo que no hace falta que se incluyan. Si lo hacemos es por seguir la metodologa de siempre.

Captulo 5. Operaciones para comunicaciones con TCPEn este captulo se van a presentar todas las funciones que van a necesitarse para poder comunicar un cliente y un servidor utilizando sockets de flujo, es decir, utilizando el protocolo TCP. El protocolo TCP es un protocolo orientado a la conexin. Una conexin se establece entre slo un cliente y un servidor, por tanto, slo se pueden utilizar direcciones unicast. Por supuesto que siempre se pueden establecer n conexiones TCP entre un cliente y un servidor, donde el servidor puede ser el mismo. No es multicast porque sern n conexiones diferentes, no una nica conexin. En muchos casos las funciones presentadas en este captulo podrn ser invocadas tanto por el servidor como por el cliente. En algunos casos no es as, y ser indicado de forma explcita al explicar su funcionamiento.

5.1 Funcin connectEsta es una funcin en principio pensada para ser utilizada slo por los clientes, no por los servidores. Por ser un protocolo orientado a la conexin el que implementa connect(), antes de poder mandar informacin con las funciones de enviar y recibir debe establecerse la conexin. Es connect() la llamada empleada por el cliente para solicitar el establecimiento de una conexin TCP con un servidor. Esta solicitud de establecimiento se plasmar en el envo de un segmento TCP solicitando la conexin (ver tema de la arquitectura TCP/IP). Recurdese que para que un socket fuera til se necesitaba que estuviera asociado con una determinada direccin. Como ya hemos visto, esto se realiza de forma explcita con la funcin bind(). En caso de implementar un cliente que no utilice esa funcin, connect() tiene tambin el efecto de unir implcitamente el socket a una determinada direccin. Lo que hace connect es unirlo al par formado por la direccin IP de la mquina y el primer puerto que encuentre libre. Como TCP es orientado a la conexin, connect() debe esperar a que el servidor responda con un segmento TCP de confirmacin. Una vez recibido, connect() tambin vincula el socket con la direccin del otro extremo de la comunicacin. Por tanto, una vez ejecutada la funcin connect(), el socket tiene por defecto asociado la direccin origen y destino desde donde enviar o recibir los datos. Una observacin importante es que la funcin connect() fue diseada para ser utilizada con sockets de flujo (es decir, con TCP). No obstante, debido a este efecto que tiene de unir el socket de forma implcita a un par de direcciones (una la del cliente y otra la del servidor), muchos programadores utilizan esta funcin con sockets de datagrama (es decir, con UDP). En este caso, har la misma asociacin implcita de un par de direcciones, pero obviamente no generar una solicitud de conexin, ya que el protocolo UDP es no orientado a la conexin (ver tema de la arquitectura TCP/IP). 32

Se ha dicho que connect() es una funcin en principio pensada para ser utilizada slo por los clientes, no por los servidores. Esto es siempre as con los sockets de flujo porque el sistema genera un segmento TCP distinto para el cliente que slicita una conexin que para el servidor que tiene que aceptarla, y por tanto Winsock utiliza funciones distintas (como se ver para el servidor la funcin es accept()). En el caso de los sockets de datagrama ya no es as, al no generar el protocolo UDP ningn intercambio de unidades para establecer la conexin. Por tanto, lo nico que utiliza el programador es el efecto local que hace que el socket se vincule tanto a su direccin como a la direccin destino. Por tanto, con sockets de datagrama la funcin connect() puede ser invocada tanto por el cliente como por el servidor. El prototipo en C de la funcin es:

#include

int connect(SOCKET s, const struct sockaddr *dirDestino, int long_dirDestino);

Esta funcin asocia al socket s con la direccin destino apuntada por dirDestino. En caso de sockets de flujo (SOCK_STREAM), genera un establecimiento de conexin con dirDestino. El parmetro dirDestino es un puntero a la estructura sockaddr, donde deber ponerse la direccin del socket de la aplicacin donde se quieren enviar los datos. Podemos utilizar tambin la estructura sockaddr_in, pero haciendo casting con sockaddr para evitar warnings del compilador. El parmetro long_dirDestino indica el tamao de la estructura apuntada por dirDestino. Esta funcin devuelve 0 si todo ha ido bien, y SOCKET_ERROR si se ha producido un fallo. En la seccin 5.7 se presenta un ejemplo donde se utilizar connect().

33

5.2 Funcin listenEsta funcin prepara a un socket para recibir solicitudes de conexin (que se realizarn mediante connect()). Por tanto, esta funcin debe ser invocada nicamente por los servidores (nunca por un cliente). Cuando el servidor est ya conectado con un cliente (con la funcin accept() que se ver ms adelante) y est ejecutando otras instrucciones, puede ser que otros clientes realicen tambin solicitudes de conexin. Para que no se pierdan y el sistema las guarde hasta que el servidor pueda tratarlas, la funcin listen() tambin proporciona la posibilidad de crear una cola. El prototipo en C de la funcin es:

#include

int listen(SOCKET s, int long_peticiones);

El primer parmetro indica que el socket s debe ponerse en modo pasivo, es decir, a la espera de recibir peticiones de conexin. El segundo parmetro long_peticiones indica el nmero mximo de peticiones que debe encolar a la espera que el servidor pueda tratarlas. Esta funcin devuelve 0 si todo ha ido bien, y SOCKET_ERROR si se produce un fallo. Cuando expliquemos accept() tambin se presentar un ejemplo de uso de esta funcin listen().

5.3 Funcin acceptUna vez preparado el socket para recibir solicitudes de conexin (despus de ejecutar listen()), esta funcin acepta la conexin con el cliente (bien porque est encolada, o bien porque espera hasta que llega una solicitud hecha con connect() por un cliente). Esta llamada provoca por parte de TCP el envo de un segmento de confirmacin de la conexin. Esta funcin accept() slo puede ser invocada con sockets de flujo y por los servidores (nunca por un cliente). La funcin accept(), una vez establecida la conexin TCP, devuelve un nuevo socket que identificar la conexin entre el cliente y el servidor, es decir, ese nuevo socket estar asociado al par de direcciones formado por la direccin del cliente y la del servidor. El prototipo en C de la funcin es:

34

#include

SOCKET accept(SOCKET s, struct sockaddr *dirCliente, int *long_dirCliente);

El primer parmetro s indica el socket que est en modo pasivo a la espera de que los clientes le hagan connect() a su direccin. Una vez recibida una peticin, el sistema nos devuelve un nuevo socket que ser el resultado de la conexin entre un cliente y el servidor. Por tanto, el nuevo socket creado estar vinculado tanto a la direccin del cliente aceptado como a una direccin del servidor. Al finalizar correctamente la ejecucin del accept(), el sistema indica en el parmetro dirCliente el puntero a la direccin del cliente al que se ha conectado. El tercer parmetro es un puntero al tamao de dirCliente. Es importante resaltar que el programador debe poner antes de invocar a accept() este valor apuntando al tamao esperado de dirCliente (que es la estructura sockaddr o sockaddr_in). En el caso de que la conexin no se haya podido realizar, la funcin devuelve INVALID_SOCKET. Un ejemplo de utilizacin de esta funcin accept() es:

#include ... SOCKET s_serv; SOCKET s_con; struct sockaddr_in dirMiEquipo, dir_cli; int resul, long_dir_cli; ... //s_serv recibe las peticiones de conexion de los clientes s_serv = socket(PF_INET, SOCK_STREAM, 0); if (s_serv == INVALID_SOCKET)exit(1); memset(&dirMiEquipo, 0, sizeof(struct sockaddr_in)); dirMiEquipo.sin_family = AF_INET; dirMiEquipo.sin_addr.s_addr = INADDR_ANY; dirMiEquipo.sin_port = htons(8989); resul=bind(s_serv, (struct sockaddr *) &dirMiEquipo, sizeof(dirMiEquipo)); if (resul == SOCKET_ERROR) exit(2);

35

// prepara s_serv para aceptar conexiones listen(s_serv,5); while(1) { //acepta una conexion a la direccin de s_serv long_dir_cli=sizeof(dir_cli); s_con=accept(s_serv, (struct sockaddr *) &dir_cli, &long_dir_cli); if (s_con == INVALID_SOCKET){ printf("ERROR AL ACEPTAR CONEXION: %d\n",WSAGetLastError()); exit(3); } // s_con es el socket creado para la conexin // que se acaba de establecer ... send(s_con, ... ); // el envo se hace con s_con ... recv(s_con, ... ); // se recibe por s_con ... closesocket(s_con); //al finalizar, se cierra s_con } ...

En el ejemplo se puede ver que s_serv es el socket para que los clientes soliciten la conexin, mientras que s_con es el socket para trabajar con una conexin en concreto. El ejemplo reproduce un esquema habitual en el cual los servidores estn permanentemente aceptando conexiones de clientes. A partir de la seccin 5.8 se presentan ejemplos completos de servidores con TCP.

36

5.4 Funcin sendEsta funcin permite enviar datos a travs de un socket. Es similar a sendto(). La nica diferencia es que si se ha utilizado previamente la funcin connect() (o la funcin accept()), el socket ya sabe a qu direccin queremos enviar los datos, y por lo tanto no necesitamos ningn parmetro que lo indique. Al igual que pasaba con connect(), esta funcin fue diseada originalmente para utilizarse con sockets de flujo. No obstante, puede utilizarse tambin con sockets de datagrama si previamente se ha utlizado la funcin connect(). El prototipo en C de la funcin es:

#include

int send(SOCKET s, const char *msj, int long_msj, int flags);

Esta function enva el array de datos contenido en el parmetro msj por el socket s. El parmetro long_msj indica el tamao del parmetro anterior. El parmetro flags permite enviar datos con distintas opciones (fuera de banda, adelantados, etc). Un envo normal de datos se consigue poniendo en este campo flags un 0. Esta funcin devuelve el nmero de bytes enviados por la red si todo ha ido bien, y SOCKET_ERROR si se ha producido un fallo al enviar. En la secciones 5.7 y 5.8 se presentan ejemplos donde se utilizar send().

5.5 Funcin recvEsta funcin permite recibir datos a travs de un socket. Es similar a recvfrom(). La nica diferencia es que si se ha utilizado previamente la funcin connect() (o la funcin accept()), el socket ya sabe la direccin desde la cual queremos recibir los datos, y por lo tanto no necesitamos ningn parmetro que lo indique. Al igual que pasa con connect() y send(), esta funcin fue diseada originalmente para utilizarse con sockets de flujo. No obstante, puede utilizarse tambin con sockets de datagrama si previamente se ha utlizado la funcin connect(). El prototipo en C de la funcin es:

#include

int recv(SOCKET s, const char *msj, int long_msj, int flags);

37

Esta function recibe para el socket s una serie de datos que almacena en el array del parmetro msj. El parmetro long_msj indica el tamao del parmetro anterior. El parmetro flags permite, al igual que en el caso de sendto, recibir datos con distintas opciones (fuera de banda, adelantados, etc). Una recepcin normal de datos se consigue poniendo en el campo flags un 0. Esta funcin recv() devuelve el nmero de bytes recibidos si todo ha ido bien, y SOCKET_ERROR si se ha producido un fallo. Es muy importante destacar que la funcin recv() tambin puede devolver 0 como nmero de bytes recibidos por el socket de flujo s. En este caso lo que quiere decir es que la aplicacin remota ha cerrado la conexin. Este valor 0 se suele utilizar al implementar muchas aplicaciones para indicar que la aplicacin remota ya ha enviado todo lo que tena y que no hay por qu esperar a recibir ms datos de ella. Es tambin muy importante resaltar que en los sockets de flujo un envo de n datos con un send() no tiene por qu corresponderse con una nica recepcin de n datos. Esto es debido a que, a diferencia de UDP, el protocolo TCP puede generar segmentos de datos de un tamao distinto de los datos volcados por una funcin send(). Esto es as para poder optimizar el tamao de la ventana de TCP (ver el tema de la arquitectura TCP). Por tanto, esto se traduce para el programador en que un envo de n datos con un send() se puede traducir en recibir n veces 1 byte, o en recibir 2 veces n/2 bytes (o cualquier otra combinacin). En la secciones 5.7 y 5.8 se presentan ejemplos donde se utilizar recv().

5.6 Funciones closesocket y shutdownCon sockets de flujo la funcin closesocket(), adems de realizar las operaciones locales que mencionamos en la seccin 3.3, provoca la liberacin de la conexin. Para ello se intercambiarn (de forma transparente para el programador) los segmentos TCP que solicitan y confirman el cierre de la conexin en ambos sentidos (ver el tema de la arquitectura TCP/IP). Un aspecto a tener en cuenta es que cuando Winsock devuelve el control de closesocket() la liberacin a nivel TCP no est del todo finalizada (la otra parte puede estar todava mandando su segmento TCP de liberacin) Por tanto, si un cliente inmediatamente intenta volver a conectar puede tener problemas (no obstante este tiempo es muy pequeo, est en 1 2 segundos). Tanto la sintaxis como la utilizacin de esta funcin es igual que la ya descrita en la seccin 3.3. Como se ha visto closesocket() cierra la conexin. A veces se quiere tener un mayor control y poder cerrar slo un extremo de la conexin, para ello tenemos la funcin shutdown(). El prototipo en C de la funcin es:

38

#include

int shutdown(SOCKET s, int tipo_cierre);

El primer parmetro indica que el cierre de la conexin se realiza sobre el socket de flujo s. El significado del parmetro tipo_cierre depende de los valores: 0. La aplicacin remota ya no puede enviar ms datos a la aplicacin local (es decir, a la que invoca a esta funcin). 1. La aplicacin local no puede enviar ms datos. 2. Ni la aplicacin local ni la remota pueden enviar ms datos (es equivalente closesocket()). Esta funcin devuelve 0 si todo ha ido bien, y SOCKET_ERROR si se ha producido un fallo.

5.7 Cliente con TCP.Los clientes en TCP suelen ser mucho ms sencillos que los servidores. Por tanto vamos a dividir la explicacin en diferentes secciones. Para intentar reducir cdigo y aclarar los conceptos, vamos a presentar un ejemplo simple de cliente TCP que nos sirva para ser empleado con todos los tipos de servidores que explicaremos posteriormente. El funcionamiento siempre ser el mismo: el cliente enva un nico mensaje, y el servidor cuando reciba todo el mensaje le responde al servidor enviando otro mensaje de respuesta.

5.7.1 Un ejemplo de cliente con TCP.El cliente enviar un mensaje (de 24 bytes o caracteres), esperando que el servidor le conteste con cierta informacin (de la que no conoce su tamao, aunque s sabe que es menor que 80 caracteres). En Windows Visual Studio podemos crear un proyecto con el cliente y el servidor. Con otros compiladores, con tener un fichero con la extensin .c ser suficiente.

39

#include #include #include void main(){ SOCKET s; struct sockaddr_in dir_serv; int resul, puerto_serv, error; WSADATA wsa_datos; char cadena_dir_ip_serv[20]; // cadena con la ip del servidor char msj_env[80]; // datos a enviar char msj_rec[80]; // datos a recibir char msj[80]; // variable auxiliar para escribir lo recibido printf("--- CLIENTE TCP ---\n"); printf("Direccion IP del servidor TCP="); scanf("%s",&cadena_dir_ip_serv); printf("Puerto del servidor TCP="); scanf("%d",&puerto_serv); error = WSAStartup(MAKEWORD( 2, 2 ), &wsa_datos); if ( error != 0 ) exit(1); // error al iniciar la DLL if ( LOBYTE( wsa_datos.wVersion ) != 2 || HIBYTE( wsa_datos.wVersion ) != 2 ) { WSACleanup( ); exit(1); } s = socket(PF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET){ printf("ERROR AL CREAR EL SOCKET: %d\n",WSAGetLastError()); exit(2); } memset(&dir_serv, 0, sizeof(struct sockaddr_in)); dir_serv.sin_family = AF_INET; dir_serv.sin_addr.s_addr = inet_addr(cadena_dir_ip_serv); dir_serv.sin_port = htons(puerto_serv); resul=connect(s, (struct sockaddr *) &dir_serv, sizeof(dir_serv)); if (resul == SOCKET_ERROR){ printf("ERROR AL CONECTAR: %d\n",WSAGetLastError()); exit(3); } strcpy(msj_env,"Dame toda la informacion"); // mensaje de 24 bytes

40

resul=send(s, msj_env, sizeof(msj_env),0); if (resul == SOCKET_ERROR){ printf("ERROR AL ENVIAR: %d\n",WSAGetLastError()); exit(4); } printf("MENSAJE recibido: "); do{ resul=recv(s, msj_rec, sizeof(msj_rec),0); if (resul == SOCKET_ERROR){ printf("ERROR AL recibir: %d\n",WSAGetLastError()); exit(4); } strncpy(msj, msj_rec,resul);printf("%s",msj); // escribe el mensaje recibido strcpy(msj,""); //limpia msj

}while (resul!=0); // espera a que el servidor libere la conex. printf("\n FIN de la conexion \n"); closesocket(s); WSACleanup( ); } // fin del main

Figura 9: Cdigo del cliente TCP

Con todo el cdigo de la figura se puede crear un fichero al que llamar, por ejemplo, cliente_TCP.cpp en Windows Visual Studio. Aunque nos estamos adelantando a la presentacin del servidor, el cdigo del cliente es muy fcil de comprender. Lo nico que puede sorprender es el bucle do-while para leer lo que el servidor nos enve. Hay que recordar lo explicado para la funcin recv() en TCP: aunque el servidor utilice un solo send(), la informacin puede ir en varios segmentos de TCP, de forma que eso se puede traducir siempre en tener que hacer varios recv(). El lector un poco experimentado puede advertir que en muchos ejemplos que se pueden encontrar en la literatura no se hace como aqu, si no que si una aplicacin hace un nico send(), la otra hace un nico recv(). Esto es as porque la mayora de las implementaciones de sockets intentan respetar que lo indicado en el send() vaya en un nico segmento TCP. Pero lo importante es saber que nunca se pueden tener garantas de que eso vaya a ser as. 41

5.8 Servidor iterativo con TCPUn posible diseo del servidor es aquel en el cual las peticiones de conexin se atienden unas detrs de otras. Es decir, hasta que no se acaban de ejecutar todas las instrucciones involucradas en una conexin, el servidor no acepta otra nueva conexin. Esto es lo que se llama un servidor iterativo. Seguidamente vamos a presentar como sera esa comunicacin.

5.8.1 Esquema cliente/servidor con servidor iterativo con TCP

Servidor ClienteWSAStartup( ) WSAStartup( ) socket( ) socket( ) bind( ) connect( ) listen( ) send( ) accept( )DATOS (peticin)

recv( )

DATOS (respuesta)

send( )

recv( ) closesocket( ) closesocket( ) WSAcleanup( ) WSAcleanup( )42

5.8.2 Un ejemplo con servidor iterativo con TCP#include #include #include void procesa_conexion(SOCKET s); // atiende la conexin con el cliente void main(){ SOCKET s_serv; SOCKET s_con; struct sockaddr_in dirMiEquipo, dir_cli; int resul, long_dir_cli, error; WSADATA wsa_datos; printf("--- SERVIDOR TCP ---\n"); error = WSAStartup(MAKEWORD( 2, 2 ), &wsa_datos); if ( error != 0 ) exit(1); // error al iniciar la DLL if ( LOBYTE( wsa_datos.wVersion ) != 2 || HIBYTE( wsa_datos.wVersion ) != 2 ) { WSACleanup( ); exit(1); } //s_serv recibe las peticiones de conexion de los clientes s_serv = socket(PF_INET, SOCK_STREAM, 0); if (s_serv == INVALID_SOCKET){ printf("ERROR AL CREAR EL SOCKET: %d\n",WSAGetLastError()); exit(2); } memset(&dirMiEquipo, 0, sizeof(struct sockaddr_in)); dirMiEquipo.sin_family = AF_INET; dirMiEquipo.sin_addr.s_addr = INADDR_ANY; dirMiEquipo.sin_port = htons(8989); resul=bind(s_serv, (struct sockaddr *) &dirMiEquipo, sizeof(dirMiEquipo)); if (resul == SOCKET_ERROR){ printf("ERROR AL UNIR EL SOCKET: %d\n",WSAGetLastError()); exit(3); }

43

// prepara s_serv para aceptar conexiones listen(s_serv,5); while(1) { //acepta una conexion a la direccin de s_serv long_dir_cli=sizeof(dir_cli); s_con=accept(s_serv, (struct sockaddr *) &dir_cli,&long_dir_cli); if (s_con == INVALID_SOCKET){ printf("ERROR AL ACEPTAR CONEXION: %d\n",WSAGetLastError()); exit(4); } printf("--- CONEXION ACEPTADA ---\n"); procesa_conexion(s_con); //realiza la conexin aceptada } closesocket(s_serv); // cierra s_serv WSACleanup( ); } // fin del main

44

// funcion para tratar la connexion con un cliente void procesa_conexion(SOCKET s_con){ int cont, resul; char msj_env[80]; // datos a enviar char msj_rec[80]; // datos a recibir char msj[80]; // variable auxiliar para escribir lo recibido printf("MENSAJE recibido: "); cont=0; do{ resul=recv(s_con, msj_rec, sizeof(msj_rec),0); if (resul == SOCKET_ERROR){ printf("ERROR AL RECIBIR: %d\n",WSAGetLastError()); exit(5); } cont=cont+resul; strncpy(msj, msj_rec,resul); printf("%s",msj); // escribe el mensaje }while (cont