jueves, 13 de septiembre de 2012

Curso de sockets. Capitulo 1

Capítulo perteneciente al curso de programación C/C++  LordPakus

Hola a todos,

Por petición popular ( y tal y como prometí) os adjunto un tutorial del uso de sockets en C sobre windows.

Los sockets no son más que la herramienta que nos da el sistema operativo para conectarnos a una red desde nuestro programa. Normalmente son dependientes de la API del SO, así que me restringo solamente a sistemas windows. Si algún valiente quiere hacer la versión para Linux, adelante!

Casi todas las estructuras de red actuales se basan en un configuración cliente-servidor. Es decir, tenemos un ordenador que se limita a escuchar por un puerto y otro ordenador que "ataca" a ese servidor en ese puerto. A través de esa información se realiza la comunicación.

Al contrario que en otros lenguajes, el tema de red en C no es ni fácil ni intuitivo (algo malo tenia que tener :D ), así que me limitaré a copiaros el código para que podáis hacer vuestras pruebas. cualquier duda que tengáis hacedmela llegar.

En breves dias encapsularé el código en C++ para que sea más legible.

Espero que os guste, nos vemos:

main.c



#include <iostream>
#include <windows.h>
#include <stdlib.h>
#include <string.h>

#pragma comment(lib, "Ws2_32.lib")
#define PUERTO 8080

WSADATA wsa;

int error()
{
    std::cout << "Error #" << GetLastError() << std::endl;
    WSACleanup();

    getchar();

    return 0;
}

int InitServer(sockaddr_in *local, SOCKET * servidor, int puerto)
{
local->sin_port = htons(puerto); // Puerto a la escucha.
local->sin_family = AF_INET; // Debe ser AF_INET.
local->sin_addr.S_un.S_addr = INADDR_ANY; // Usar cualquier dirección.

*servidor = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   // Protocolo TCP
   
if(*servidor == INVALID_SOCKET)
return error();

if(bind(*servidor, (sockaddr*)local, sizeof(sockaddr)) == SOCKET_ERROR)
return error();

if(listen(*servidor, 1) == SOCKET_ERROR)     // Colocamos el servidor a la escucha.
return error();

std::cout << "Servidor a la escucha por el puerto " << puerto << ", esperando conexión." << std::endl;

return 0;
}

void WaitConnection(SOCKET *servidor, SOCKET * nueva_conexion )
{
do
{
*nueva_conexion = accept(*servidor, NULL, NULL); // Esperamos una conexión entrante y la aceptamos.
} while(*nueva_conexion == SOCKET_ERROR);
}

void SendMessage(SOCKET conexion,char *str)
{
send(conexion,str,strlen(str),0);
}

int WaitMessage(SOCKET conexion, char *bufout)
{
int bytes_recv;
char buffer[256];

memset(buffer, 0, sizeof(buffer)); // Limpiamos el buffer temporal
memset(bufout, 0, sizeof(buffer)); // Limpiamos el buffer de salida.

do
{
bytes_recv = recv(conexion, buffer, sizeof(buffer), 0);   // Esperamos para recibir datos...
} while(bytes_recv == 0 && bytes_recv != SOCKET_ERROR);

//Copiamos el buffer temporal en el de salida
memcpy(bufout,buffer,bytes_recv);

return bytes_recv;

}

int Server(void)
{
int res;
SOCKET servidor, nueva_conexion;
sockaddr_in local;
char buffer[256];
int bytes;

//Iniciamos el servidor
res = InitServer(&local,&servidor, PUERTO);

if ( res )
return res;

//Esperamos que se nos conecte un cliente
WaitConnection(&servidor,&nueva_conexion);

SendMessage(nueva_conexion,"Servidor C++!");

bytes = WaitMessage(nueva_conexion,buffer);

if(bytes > 0)
std::cout << "Buffer: " << buffer << " - Bytes recibidos: " << bytes << std::endl;

closesocket(nueva_conexion);                                    // Lo desconectamos!

return 0;
}

int InitClient(sockaddr_in *conexion,SOCKET * cliente, char *ip, int puerto )
{
conexion->sin_family = AF_INET;
conexion->sin_port = htons(PUERTO);                                    // Puerto donde nos conectaremos

conexion->sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

//Todo: Implementar tb por nombre de host
// conexion->.sin_addr = *((in_addr*)gethostbyname("localhost")->h_addr);  // Host a donde nos conectaremos

*cliente = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);        // Protocolo TCP

if(*cliente == INVALID_SOCKET)
return error();

if(connect(*cliente, (sockaddr*)conexion, sizeof(sockaddr)))      // Conectamos con el servidor
return error();

return 0;
}

int Client(void)
{
sockaddr_in conexion;
SOCKET cliente;
char buffer[256];
int bytes;
int res;

res = InitClient(&conexion,&cliente,"127.0.0.1",PUERTO);

if(res)
return res;
 
SendMessage(cliente,"Hola, soy un cliente en C++!");

bytes = WaitMessage(cliente,buffer);

if(bytes > 0)
std::cout << "Buffer: " << buffer << " - Bytes recibidos: " << bytes << std::endl;

closesocket(cliente);                                                       // Finalmente desconectamos...

return 0;
}

int main()
{
char opcion;
int res;

if(WSAStartup(MAKEWORD(2,2), &wsa))
        return error();

do
{
std::cout << "1-Servidor o 2-Cliente?" << std::endl;
opcion = getchar();
}
while( (opcion != '1') && (opcion != '2'));

if(opcion == '1')
{
std::cout << "Iniciando servidor" << std::endl;
res = Server();
  }
else
{
std::cout << "Iniciando cliente" << std::endl;
res = Client();
}

if(res)
return res;

    WSACleanup();
system("PAUSE");
return 0;
}



4 comentarios :

  1. Muchas gracias por los tutoriales.
    Tengo algunas dudas, espero me pueda orientar un poco.
    Le comento mi situación: estoy estudiando Ingeniería de Sistemas, y en varios de los cursos que llevo y llevaré se trata sobre programación. Sinceramente, creo que es lo que más me agrada de la carrrera. A nosotros nos dijeron que aprenderemos muchas cosas y que posteriormente, será recomendable que estudiemos alguna especialización en aquello que más nos interese.
    He llevado cursos básicos sobre C++ y Visual C++ (creando proyectos del tipo "Windows Forms" usando Visual Studio), y sinceramente, los temas no son difíciles como lo creía en un inicio. Cada vez que tenía dudas he preguntado en algunos foros y me respondían muy bien. El punto es que, aunque haya ido un poco más allá de los temas que me correspondían en clases, no he ido tan allá como debería haber sido.
    A lo que me refiero es que sólo he aprendido lo básico sobre C++ y ahora estoy llevando Java. Y como que no hay mucha diferencia entre estos lenguajes. El punto es que, debemos averiguar sobre muchos temas nuevos y los exámenes son muy teóricos. Eso ha hecho que de alguna forma haya dejado de practicar como lo hacía antes.
    Es decir, los ejercicios sobre realizar algunos cálculos ya no me parecen muy interesantes... y justo ahora mismo he vuelto a recordar aquello por lo que siempre tuve curiosidad pero que lo dejé por creerlo muy lejano.
    Es sobre el tema de Sockets, si es que así se le puede llamar: lo que quisiera aprender es sobre cómo crear programas que hagan uso del internet. Es decir, por ejemplo, cómo crear un muy básico chat o algún juego muy básico en el que puedan competir 2 personas online (cada uno con el mismo programa pero viéndolo de distinta forma).
    Lo más probable es que ello involucre muchos temas, pero quisiera empezar a comprenderlos. Tal vez usted pueda darme algunas recomendaciones sobre ello. Y quisiera aprender este tema en Java, ya que, aunque me interese también en C++, actualmente estoy llevando un curso sobre Java y no me gustaría llegar a confundirme.

    Gracias nuevamente y espero me pueda ayudar.

    ResponderEliminar
  2. Realmente la programación de juegos en red puede llegar a ser extremadamente compleja.
    Si lo que quieres es aprender sobre el tema hay una libreria que está muy muy bien que se llama rakNet (http://www.jenkinssoftware.com/) que te permite trabajar con red a todos los niveles dejando el código muy limpio y sencillo.
    Pruebala y ya me contarás

    ResponderEliminar
  3. Animate a hacerlo en Linux!

    No entendí nada de la guía Beej...




    ya dejé Sistemas Operativos para el próximo año..


    no explican absolutamente nada

    ResponderEliminar
    Respuestas
    1. Buenas,

      Realmente en linux no es tan diferente a Windows.

      A ver si me saco tiempo de la manga en algun momento y hago algun tutorial para Linux.

      Eliminar

Entradas populares