[Tutorial]Winsock en c « : 30 de Noviembre de 2006, 04:36:18 » Tutorial sobre winsock en c para Foro MazarDZone http://www.mazard.info by MazarD 1.-Introducción 2.-Funciones de winsock 2.01.-WSAStartup 2.02.-socket 2.03.-gethostbyname 2.04.-htons 2.05.-connect 2.06.-bind 2.07.-listen 2.08.-accept 2.09.-recv 2.10.-send 3.-Códigos de ejemplo 3.1.-Código de un chat cómo cliente 3.2.-Código de un chat cómo servidor 4.-Despedida En línea YES! I walked through the dark ways of madness and Im still alive!! MazarD Administrador ***** Mensajes: 274 Ver Perfil WWW Re: [Tutorial]Winsock en c « Respuesta #1 : 30 de Noviembre de 2006, 04:38:13 » 1.-Introducción Al final me he decidido a escribir el tutorial sobre winsock, espero que os guste. En éste tutorial vamos a introducirnos en el mundo de los sockets en c para windows, para hacerlo fácil voy a explicar sólo las conexiones tcp/ip. Supongo que ya lo sabrás pero por si acaso decir que en una conexión a través de internet (o en lan, da igual) conectarán dos pcs, para ello necesitan dos parametros, el primero es la ip que sería cómo la dirección de tu casa y el otro el puerto que sería con quien quiere hablar dentro de la casa: con la mula, con el messenger, con el internet explorer... Para ello uno de los dos ordenadores tiene que estar escuchando y esperando a que alguien conecte, a este ordenador se le llama el servidor, y al que conecta se le llama el cliente. Cuando cliente y servidor han conectado ya pueden recibir o enviar datos sin preocuparse de nada mas hasta que se cierre la conexión. Bueno dejemos las explicaciones by barrio sésamo y vamos a lo que interesa. 2.-Funciones de winsock Aquí intentaré explicar todas las funciones que vamos a utilizar en el programa. Si vas a programar un servidor, solo necesitas las funciones 2.01,2.02,2.04,2.06,2.07,2.08,2.09 y 2.09 y si vas a programar un cliente necesitas entender las 2.01,2.02,2.03,2.04,2.05,2.09 y 2.10. 2.01.-WSAStartup Esta función lo que hace es inicializar el winsock en nuestro programa y definir la versión que vamos a utilizar, lo que cambia entre versiones es que unas tienen mas funciones a usar y otras menos, nosotros cómo vamos a lo básico sólo nos interesa compatibilidad y por tanto usaremos la version 2.0 que viene con todos los windows Para ello haremos lo siguiente: Definimos una variable para la inicializacion WSADATA wsa; Inicializamos determinando la versión 2.0 para ello usamos MAKEWORD que pasará ese 2.0 a una variable de tipo WORD (16bits) que es lo que pide la función. También pasamos wsa por referéncia para que la modifique. WSAStartup(MAKEWORD(2,0),&wsa); 2.02.-socket Con la funcion socket lo que hacemos es crear un socket (una conexión para que se entienda) definiendo el protocolo de la conexión y el tipo de información que se trasmitirá a través de él. definimos un socket SOCKET sock; Creamos el socket de tipo AF_INET (protocolo ipv4), SOCK_STREAM (tipo de paquetes para tcp), IPPROTOTCP (protocolo tcp). sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 2.03.-gethostbyname Con gethostbyname lo que hacemos es resolver un dominio cómo podría ser www.google.com, irc.irc-hispano.org, mazard.no-ip.org... a la ip que le corresponde, así luego podremos conectar. Definimos una estructura del tipo hostent, esta estructura guardará toda la información a bajo nivel sobre el host al que vayamos a conectar. Si a gethostbyname le pasamos una ip directamente no la resolverá y la asignará a direc correctamente. struct hostent *direc; Ahora resolvemos el nombre del host y se lo asignamos a la direccion. direc=gethostbyname("www.google.es"); Nota: Si no quisieramos resolver el nombre de dominio podriamos usar la función inet_addr para transformar la ip de cadena de caracteres a una variable de tipo in_addr. Pero es mejor utilizar esta función siempre, tiene la misma utilidad que in_addr y además te resuelve nombres de dominio. 2.04.-htons Con esta funcion guardaremos el puerto al que vamos a conectar mas adelante. En el protocolo de red los puertos no se expresan igual que los expresamos nosotros, no entraremos en detalles pero si tienes curiosidad busca big endian, little endian o tcp/ip network byte order en google. un unsigned short se ajusta exactamente al rango en el que pueden ir los puertos, desde 1 hasta 65535. u_short puerto; Así transformamos el puerto 9999 "al lenguaje que entenderá" el socket. puerto=htons(9999); 2.05.-connect Con esta función conseguiremos conectar con la máquina remota, esta es la parte mas delicada, intentaré explicarla lo mas detalladamente posible. Para ello haremos uso de las funciones explicadas anteriormente. Definimos una variable del tipo sockaddr_in que contendrá la información final de la dirección a la que vamos a conectar. struct sockaddr_in remoto; definimos que la direccion remota usa el protocolo ipv4 remoto.sin_family = AF_INET; definimos el puerto al que vamos a conectar usando la función htons explicada anteriormente remoto.sin_port = htons(9999); Ahora viene lo mas delicado, tenemos que la estructura hostent definida anteriormente tiene un elemento que es la dirección ip (h_addr) usamos -> en lugar de '.' para el elemento puesto que direc es un puntero. Pero el h_addr podemos tratarlo cómo un in_addr (son estructuras diferentes pero pueden usarse igual) pero el compilador para sin_addr sólo admite in_addr así que le decimos que lo trate cómo tal con (struct in_addr *) a esto que hemos hecho se le llama casting, ahora tenemos que direc->h_addr es un puntero a un in_addr. Pero con esto no nos basta, ya que sin_addr tiene que ser un valor no un puntero así que a todo le añadimos * delante para que guarde el contenido del puntero, no el puntero en sí. Se que si no has tratado mucho con punteros y castings puede resultar un poco rebuscado pero lo he explicado lo mejor que he podido. Para dudas en el foro. remoto.sin_addr = *((struct in_addr *)direc->h_addr); Ahora cómo se define en la msdn inicializamos el elemento sin_zero con zeros. memset(remoto.sin_zero,0,8); Ahora ya podemos usar el connect con sock creado con la función socket, y la estructura remoto que hemos inicializado, pero ahora connect necesita de un puntero a sockaddr no de un sockaddr_in así que volvemos a hacer el casting cogiendo la dirección de memoria de remoto con & y pasandole un puntero a esta direccion tratada como sockaddr. Además tenemos que pasarle a la función el tamaño de la estructura. connect(sock, (sockaddr *)&remoto, sizeof(sockaddr)); Una vez hecho esto si no se ha producido ningún error(cómo que la dirección a conectar no existe...) ya podemos empezar a enviar y recibir datos. 2.06.-bind La función bind lo que hace es asociar un socket a una dirección local (por si tubieramos varias) en nuestro caso podemos considerar que estamos preparando el socket para ponerlo a la escucha. Para ello utilizaremos algunas de las funciones explicadas anteriormente. Definimos una variable del tipo sockaddr_in que contendrá la información de nuestro pc para que puedan conectar. struct sockaddr_in local; definimos que estamos usando el protocolo ipv4 local.sin_family = AF_INET; Definimos la ip local "por defecto" local.sin_addr.s_addr = INADDR_ANY; Definimos el puerto que vamos a poner a la escucha. local.sin_port = htons(puerto); Llamamos a la funcion bind con el socket que habiamos creado la dirección local definida pasada cómo puntero y tratada como SOCKADDR, podríamos decir para entendernos que SOCKADDR y sockaddr_in son comptabiles y por tanto podemos tratarlo así. Además pasamos el tamaño de la estructura (cosas internas para la función) bind(sock, (SOCKADDR*) &local, sizeof(local)) 2.07.-listen Con esta función pondremos definitivamente el socket a la escucha, no se saldrá de ésta función hasta que alguien haya conectado o se produzca un error. Ponemos a la escucha el socket creado y le decimos que sólo espere a una conexión. listen(sock,1); 2.08.-accept Una vez alguien a conectado a nuestro puerto que estaba a la escucha, tenemos que aceptar dicha conexión, esto lo hacemos con la función accept. int len len=sizeof(struct sockaddr); Al aceptar la conexión local contendrá la información de la conexión que se ha establecido una vez mas tiene que tratarse como sockaddr. sock=accept(sock,(sockaddr*)&local,&len); Ahora ya hemos establecido la conexión y estamos preparados para enviar y recibir datos. 2.09.-recv Con ésta función recibiremos datos de la máquina remota cuando los envie. int i; char Buffer[1025]; Le decimos el socket del que queremos recibir datos, la cadena que recibirá la información, el número de carácteres (bytes) que cómo máximo queremos recibir, el último parámetro es un flag que marca cómo va actuar la función, a lo simple vamos a poner 0 que es además lo mas común. La función devolverá el número de carácteres recibidos, -1 si se ha producido un error o 0 si se ha cerrado el socket. i=recv(sock, buffer, 1024, 0); Lógicamente la función recv no nos guarda el final de cadena en el buffer, lo haremos nosotros a partir de los carácteres recibidos. (cuidado con ésto, hay que controlar que i no valga -1 antes de seguir con todo el tratamiento) buffer=0; 2.10.-send Usando esta función enviaremos datos a la máquina remota. char Buffer[]="Fucking world"; Le decimos que envie al socket conectado sock la cadena "Fucking world" tenemos que decirle cuantos carácteres hay que enviar de la cadena. El último parámetro definiria el comportamiento de los paquetes al ser enviados, le daremos 0 que es lo mas normal. La función devolverá el número de carácteres que se han enviado o -1 en caso de que ya no estemos conectados. int enviado=0; enviado=send(sock,Buffer,strlen(Buffer), 0); « Última modificación: 30 de Noviembre de 2006, 04:40:39 por MazarD » En línea YES! I walked through the dark ways of madness and Im still alive!! MazarD Administrador ***** Mensajes: 274 Ver Perfil WWW Re: [Tutorial]Winsock en c « Respuesta #2 : 30 de Noviembre de 2006, 04:40:21 » 3.-Código de ejemplo Ahora que ya sabemos cómo funciona cada una de las funciones que vamos a utilizar vamos a programar el típico ejemplo en temas de estos de sockets, es decir un chat. Por si no lo sabes para hacer pruebas puedes usar el cliente y el servidor en el mismo ordenador poniendo cómo ip "127.0.0.1" Si te funciona así puedes estar seguro de que funcionará en internet, sin tener en cuenta el tema de abrir puertos en los routers, firewalls, blablabla En el chat el servidor solo recibira datos y el cliente sólo los enviará, pero está claro que puedes usar recv y send tanto en servidor como en cliente. Está así para que no quede cutre pero tampoco liar a meter hilos en el ejemplo. 3.1.-Código de un chat cómo cliente int main() { WSADATA wsa; SOCKET sock; struct hostent *host; struct sockaddr_in direc; int conex; char Buffer[1024]; int len; //Inicializamos WSAStartup(MAKEWORD(2,2),&wsa); //resolvemos el nombre de dominio localhost, esto se resolverá a 127.0.0.1 host=gethostbyname("localhost"); //creamos el socket sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if (sock==-1) { printf("Error al crear el socket"); return -1; } //Definimos la dirección a conectar que hemos recibido desde el gethostbyname //y decimos que el puerto al que deberá conectar es el 9999 con el protocolo ipv4 direc.sin_family=AF_INET; direc.sin_port=htons(9999); direc.sin_addr = *((struct in_addr *)host->h_addr); memset(direc.sin_zero,0,8); //Intentamos establecer la conexión conex=connect(sock,(sockaddr *)&direc, sizeof(sockaddr)); if (conex==-1) //si no se ha podido conectar porque no se ha encontrado el host o no //está el puerto abierto { printf("No se ha podido conectar\n"); return -1; } printf("[MiniXat para tutorial de sockets MazarDZone Foro]\n"); printf("[escribe el texto a enviar o 'salir' para salir ]\n"); while (len!=-1 && strcmp(Buffer,"salir")!=0) //mientras el socket no se haya desconectado //y no se escriba salir { printf("Texto a enviar:"); fgets(Buffer,1023,stdin); //pedir texto a enviar por pantalla len=send(sock,Buffer,strlen(Buffer),0); //enviar el texto que se ha introducido } return 0; } 3.2.-Código de un chat cómo servidor #include //la cabezera para usar las funciones de winsock #include /*linkamos a la libreria del winsock, también puedes hacerlo desde project->settings->link si usas ms visual c++ */ #pragma comment(lib,"ws2_32.lib") int main() { WSADATA wsa; SOCKET sock; struct sockaddr_in local; int len=0; char Buffer[1024]; //Inicializamos WSAStartup(MAKEWORD(2,0),&wsa); //Creamos el socket sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //defnimos dirección por defecto, ipv4 y el puerto 9999 local.sin_family = AF_INET; local.sin_addr.s_addr = INADDR_ANY; local.sin_port = htons(9999); //asociamos el socket al puerto if (bind(sock, (SOCKADDR*) &local, sizeof(local))==-1) { printf("error en el bind\n"); return -1; } //ponemos el socket a la escucha if (listen(sock,1)==-1) { printf("error en el listen\n"); return -1; } len=sizeof(struct sockaddr); //hay una conexión entrante y la aceptamos sock=accept(sock,(sockaddr*)&local,&len); printf("[SERVIDOR MiniXat para tutorial de sockets MazarDZone Foro]\n"); printf("[Cuando se vaya recibiendo texto aparecera en pantalla ]\n"); while (len!=0) //mientras estemos conectados con el otro pc { len=recv(sock,Buffer,1023,0); //recibimos los datos que envie if (len>0) //si seguimos conectados { Buffer[len]=0; //le ponemos el final de cadena printf("Texto recibido:%s",Buffer); //imprimimos la cadena recibida } } return 0; } En línea YES! I walked through the dark ways of madness and Im still alive!! LixKeÜ Usuario nuevo * Mensajes: 6 Ver Perfil Re: [Tutorial]Winsock en c « Respuesta #3 : 23 de Diciembre de 2006, 06:27:49 » Grasias por el coder MazarD. Aqui dejo un video tutorial para enseñar a aquellos amigos que resien enpiezan con c++, como yo y enseñarles como compilar este coder. http://beto123456789.webcindario.com/MAZARD.RAR Espero que les guste el video