Tutorial Programacion de Sockets en C
por Octalh
www.aztekmindz.org
Hola a todos nuevamente, después de unos días me alegra informarles que ya esta lista la segunda entrega de este pequeño tutorial para incursionar en la programación con sockets.
En la entrega anterior aprendimos los principales tipos de sockets y como programar nuestro primer cliente, es decir el programa que conecta al servidor.
En esta ocasión vamos a aprender a programar el servidor, que es el programa que atenderá al cliente.
Pero antes de empezar a tocar la programación es bueno comenzar explicando brevemente que es el protocolo TCP y como trabaja de la mano del protocolo IP
TCP (Transmission Control Protocol, en español Protocolo de Control de Transmisión) es uno de los protocolos fundamentales en Internet. Fue creado entre los años 1973 - 1974 por Vint Cerf y Robert Kahn. Muchos programas dentro de una red de datos compuesta por ordenadores pueden usar TCP para crear conexiones entre ellos a través de las cuales puede enviarse un flujo de datos. El protocolo garantiza que los datos serán entregados en su destino sin errores y en el mismo orden en que se transmitieron. También proporciona un mecanismo para distinguir distintas aplicaciones dentro de una misma máquina, a través del concepto de puerto. TCP da soporte a muchas de las aplicaciones más populares de Internet, incluidas HTTP, SMTP, SSH y FTP.
Una dirección IP es un número que identifica de manera lógica y jerárquicamente a una interfaz de un dispositivo (habitualmente una computadora) dentro de una red que utilice el protocolo de Internet (Internet Protocol), que corresponde al nivel de red o nivel 3 del modelo de referencia OSI. Dicho número no se ha de confundir con la dirección MAC que es un número físico que es asignado a la tarjeta o dispositivo de red (viene impuesta por el fabricante), mientras que la dirección IP se puede cambiar.
Ahora que sabemos lo necesario sobre TCP \ IP entendemos que TCP es el protocolo que transportara nuestros datos a través del socket, mientras que IP sirve para saber de donde y a donde enviar dichos datos a través de la red.
Ambos protocolos trabajan en conjunto para poder establecer una conexión entre Cliente y Servidor.
Esto lo podemos entender mejor apreciando el siguiente diagrama.

Si ya se que esto no se acerca ni tantito a un diagrama de TCP / IP three-way handshake pero para darnos idea de cómo funciona la interacción (conmutación de datos) entre ambos equipos esta bien.
Puesto que ya tenemos algo de teoría y un entendimiento un poco mas amplio de cómo funciona una conexión en TCP ahora procederemos a Programar Nuestro servidor.
Dado que es muy similar la programación del servidor a la forma en la que programamos el cliente, a excepción de de la forma en la que se declara la estructura del socket y el hecho de que ahora nos pondremos en escucha por un puerto determinado envés de conectar a una IP, solo listare esa sección del código para ahorrar espacio.
char Buffer[1023];
Declaramos el tamaño del Buffer
conexloc.sin_family = AF_INET;
Definimos la version 4 de IP
conexloc.sin_addr.s_addr = INADDR_ANY;
Definimos IP local
conexloc.sin_port = htons(9999);
Definimos puerto (9999) por el que escuchara el socket utilizando un “short de máquina a short de la red” (htons). Esto lo hacemos para ordenar la forma en la que enviaremos y recibiremos los datos por el puerto del socket, mas información buscar en google “Big-Endian”.
bind(locsock, (sockaddr*)&conexloc, sizeof(conexloc);
Una ves creado el socket asociamos el descriptor de fichero “locsock” a un puerto, para eso utilizamos la función “bind”, después asignamos la dirección IP pasándola como puntero a “sockaddr”.
listen(locsock, 5);
La función listen sirve para poner el socket en escucha por un puerto determinado, en nuestro caso el puerto “9999”que fue definido al socket en la función “bind”, utilizando el descriptor de fichero “locsock” que “socket()” nos dio. El último número es la cantidad de conexiones que podemos tener como máximo en la cola de espera.
conm=sizeof(struct sockaddr);
Definimos longitud de “sockaddr”
locsock=accept(locsock,(sockaddr*)&conexloc,&conm);
Aceptamos la conexión con la función “accept” utilizando el descriptor de fichero “locsock” para transmitir los datos entre ambas computadoras mediante nuestra estructura “SOCKADDR_IN” definida anteriormente en “conexloc”.
conm=recv(locsock,Buffer,sizeof(Buffer),0);
La función “recv” se encarga de recibir los datos a través del descriptor de fichero “locsock”, mientras que “Buffer” es donde se va almacenar la información recibida y posteriormente con “sizeof” medimos la longitud total del buffer recibido.
Dado que “recv” devuelve un valor de cero en caso de existir un error se utiliza un bucle while para verificar que seguimos conectados con el equipo remoto.
Código fuente completo del servidor:
using namespace std;
WSADATA wsadata; //Declaramos WSADATA
struct hostent *host;
//Declaramos estructura hostent donde almacenaremos la IP que nos devuelva gethostbyname
SOCKADDR_IN conexloc;
/*Declaramos una estructura SOCKADDR_IN para no tener que definir una IP y un puerto
en cada paquete que enviemos. De esa forma todo viajara encapsulado utilizando la estructura
SOCKADDR_IN.
*/
SOCKET locsock; // Declaramos el descriptor de fichero que nos de el socket
char Buffer[1023]; // Declaramos el tamaño del Buffer
int WSAInicio() { //declaramos procedimiento
int wasa = WSAStartup(MAKEWORD(2,0),&wsadata); //Indicamos versión 2.0 del socket
if (wasa != 0) { // Si existen errores…
printf("%s","Error iniciando WSAStartup \n"); //Mostramos un mensaje
WSACleanup(); //Limpiamos WSADATA
return 1; // Retornamos 1 dado que la funcion fallo
}
return 0; // Si se inicio todo bien retornamos 0
}
int definirsocket() {
locsock = socket(AF_INET/* IP V4 */, SOCK_STREAM, 0); // Indicamos que usaremos un socket Stream(TCP)
if (locsock == INVALID_SOCKET) { // Si existen errores…
printf("%s","Error definiendo socket \n"); //Mostramos un mensaje
WSACleanup(); //Limpiamos WSADATA
return 1; // Retornamos 1 dado que la funcion fallo
}
return 0; // Si se inicio todo bien retornamos 0
}
int estructsocket() { // Definimos procedimiento
conexloc.sin_family = AF_INET; // Ordenación de Maquina
/*
Definimos la version 4 de IP
*/
conexloc.sin_addr.s_addr = INADDR_ANY;
/*
Definimos IP local
*/
conexloc.sin_port = htons(9999);
/* Definimos puerto (9999) por el que escuchara el socket utilizando un “short de máquina a short de la red” (htons)
Esto lo hacemos para ordenar la forma en la que enviaremos y recibiremos los datos por el puerto del socket, mas información buscar en google “Big-Endian”.
*/
if (bind(locsock, (sockaddr*)&conexloc, sizeof(conexloc)) == SOCKET_ERROR) { // Si existen errores…
/* Una ves creado el socket asociamos el descriptor de ficher "locsock" a un puerto, para eso utilizamos la función "bind"
Después asignamos la dirección IP pasándola como puntero a "sockaddr".
*/
printf("%s","Error definiendo socket \n"); //Mostramos un mensaje
WSACleanup(); //Limpiamos WSADATA
return 1; // Retornamos 1 dado que la funcion fallo
} else {
if (listen(locsock, 5) == SOCKET_ERROR) { // Si existen errores…
/* La función listen sirve para poner el socket en escucha por un puerto determinado, en nuestro caso el puerto 9999
que fue definido al socket en la función "bind", utilizando el descriptor de fichero "locsock" que "socket()" nos dio
El ultimo numero es la cantidad de conexiones que podemos tener como máximo en la cola de espera.
*/
printf("%s","Error Al ponerse en escucha \n"); //Mostramos un mensaje
WSACleanup(); //Limpiamos WSADATA
return 1; // Retornamos 1 dado que la función fallo
} else {
printf("%s","Esperando conexiones por puerto 9999 \n"); //Mostramos un mensaje
return 0; // Si se inicio todo bien retornamos 0
}
}
}
void conexion(){
int conm; //Declaramos variable para definir longitud de la estructura "sockaddr"
conm=sizeof(struct sockaddr); // definimos longitud de "sockaddr"
locsock=accept(locsock,(sockaddr*)&conexloc,&conm); // Conexion establecida
/* Aceptamos la conexión con la funcion "accept" utilizando el descriptor de fichero "locsock" para transmitir los datos entre ambas computadoras
mediante nuestra estructura "SOCKADDR_IN" definida anteriormente en "conexloc"
*/
printf("%s","Conexion establecida \n"); //Mostramos un mensaje
while (conm!=0){ //mientras estemos conectados..
conm=recv(locsock,Buffer,sizeof(Buffer),0); //recibimos los datos que envíe
/* La función "recv" se encarga de recibir los datos a través del descriptor de fichero "locsock"
Buffer es donde se va almacenar la información recibida y posteriormente con "sizeof" medimos la longitud total del buffer recibido
Dado que "recv" devuelve un valor de cero en caso de existir un error se utiliza un bucle while para verificar
que seguimos conectados con el equipo remoto
*/
if (conm>0){ //si seguimos conectados al cliente
printf("Datos recibidos:%s",Buffer); //imprimimos los datos recibidos
}
}
}
void sockets(){ // Procedimiento que iniciara el socket secuencialmente.
if((WSAInicio()) == 0) { // Si se inicio WSAInicio sin errores…
if((definirsocket()) == 0) { // Si se inicio definirsocket sin errores…
if((estructsocket()) == 0) { // Si se inicio estructsocket sin errores…
conexion(); // Iniciamos el procedimiento "conexion"
}
}
}
}
int main(int argc, char *argv[])
{
sockets(); // Iniciamos el Socket
}
Bien ahora que ya tienes El cliente y el Servidor, llego el momento de ejecutar el Servidor seguido del cliente y voila!!!

Felicidades ya tienes tu primer aplicación con sockets programada en C.
Hasta aquí termina este tutorial, espero que te haya gustado y te sea de utilidad, cualquier sugerencia puedes contactarme vía E-Mail: octalh@gmail.com
Solo me queda decirte que ahora te toca perfeccionar el cliente para que interactué de igual forma que lo hace el servidor
Tutorial elaborado para: www.aztekmindz.org
Descargar Projecto Fuentes y Compilado
esta bueno!!