Programación‎ > ‎C‎ > ‎6 - [Artículos]‎ > ‎

Introdución a los sockets en Ansi C

Tutorial sobre sockets en Ansi C by Sagrini [21 - 02 - 11]

[No he formateado todo el tutorial al subirlo, recomiendo descargar el original bien formateado...]


COMENZANDO CON LOS SOCKETS EN ANSI C

Sagrini : elhacker.net

1.) Introdución.

2.) Funciones principales I.

3.) Funciones principales II.

4.) Uso de sockets en Windows.

5.) Final.


1.) Introdución

Me decidí a escribir este manual porque mucha gente

en el foro tiene algunas duda con el uso de

sockets en Ansi C. Todas las dudas en el foro.

Comento que necesitaréis algo de experiencia en C, no

os voy a explicar lo que es “int main ()”.


Las liberías a usar son:

#include <arpa/inet.h> //struct sockaddr_in

#include <sys/socket.h> //Socket, Connect...


2) Funciones principales I

2.1) Función socket ();

La primera función que vamos a “estudiar” es socket. Esta función inicializa el socket para su uso...

Lo que hace es meter en una variable tipo INT un

descriptor para usarlo en otras funciones. Uso:


int sockfd = socket (2, 1, 0);


Bueno, la cosa es declarar una variable y pasarla como return a la función socket. ¡Distintos tipos de

argumentos se pueden ver en Google! Estos ahora

mismo son suficientes, pero el saber no mata...


2.2 ) Función close ();

Esto se usa para cerrar el socket una vez usado.

Si lo dejamos abierto, podríamos tener algunos

problemas luego...

Es muy sencillo. Le pasas como argumento el

descriptor de socket. En nuestro caso es “sockfd”.

Evidentemente se debe cambiar en cada uno...


int sockfd = socket (2, 1, 0);

close (sockfd);


2.3 ) Estructura struct sockaddr // sockaddr_in

En esta estructura se usa para guardar los datos

de las conexiones (Ej: ip, puerto, familia...). Lo

explicaremos todo...

Bueno, se declara así.


struct sockaddr_in host;

struct sockaddr_in client;


Struct sockaddr” se usa por compatibilidad...

Bueno, ya vereis... Una vez declarada la estructura y el descriptor de socket, rellenamos la estructura...


host.sin_addr.s_addr = 0; // Así rellenamos la dirección con nuestra IP en orden de bits de red. Para escuchar.

host.sin_port = htons (31337); // HTONS pasa un numero (puerto) a orden de bits de red. Es para que el PC lo entienda.

host.sin_family = AF_INET; // Esta es la familia de Ipv4. Yo la suelo dejar así.

memset (host.sin_zero, 0, 8); // Rellena el resto de la estructura con ceros. Así.


Esta se usa para escuchar. Para conectar...


host.sin_addr.s_addr = arpa_inet (“127.0.0.1”); // IP

host.sin_port = htons (31337); // HTONS pasa un numero (puerto) a orden de bits de red. Es para que el PC lo entienda.

host.sin_family = AF_INET; // Esta es la familia de Ipv4. memset (host.sin_zero, 0, 8); // Rellena el resto con ceros.


Estas estructuras son muy importantes, y

contienen los datos que usamos.


2.4 ) Función connect ();

Esto se usa para conectarnos a cualquier IP. Usa

las estructuras antes vistas. ¿Os acordais de lo que dije antes de la compatibilidad? Es para esto...


Sus argumentos son:

connect (sockfd, (struct sockaddr*)&host, sizeof (struct sockaddr*));


Poco a poco. “sockfd” es el descriptor que se usa

para guardar el descriptor con el que mandamos y

recibimos datos.

Lo siguiente es un “typedef”. Es hacer una

variable otro tipo de variable. Se hace por

compatibilidad (por tercera vez). Se le pasa una

dirección a una estructura, no una estructura

completa...

Lo tercero es el tamaño de la estructura. Se hace

fácil con sizeof. Es por seguridad.


2.5 ) Función bind ();

Esto es diferente a conectar. ¡No os lieis!

Esto se usa para preparar un socket para escuchar

en nuestra máquina. Usa tanto las estructuras de antes como lo de la compatibilidad.

Sus argumentos son:


bind (sockfd, (struct sockaddr*)&host, sizeof (struct sockaddr*));


Son exactamente iguales que los de connect. Sólo

se elimina “newsock”, pues aquí seguimos usando

sockfd”. Aún necesitamos usar dos funciones más.


2.6 ) Función listen ();

Esto es otro paso para escuchar. Sus argumentos

son:


listen (sockfd, 1);


Sockfd” es el socket que hemos usado antes. Lo

segundo es el número de conexiones que puede

aceptar. En este manual lo dejamos en una.


2.7 ) Función accept ();

Esto es el último paso para aceptar una conexión.

Aquí otros PCs se conectan.



int a = sizeof (struct sockaddr*);

struct sockaddr_in client;

int newsock = accept (sockfd, (struct sockaddr*)&client, &a);


En esta función hay que declarar “a”, que

contiene el tamaño de “struct sockaddr*”. A “accept”, que se le pasa la dirección de la

variable, no la variable.

Ahora se usa “newsock”, que luego usamos para

enviar y recibir datos. A partir de aquí podéis

cerrar “sockfd”, pues ya no lo usamos más.

Client se usa para guardar los datos del quien se

conecta. Luego podeis escribir las IPs de quien se

conecta a vosotros, el puerto que usa, etc... Se

declara aparte. Aclaro que aquí el programa se para hasta que alguien se conecta.


2.0 ) Código de demostración.

#include <stdio.h> //Funciones basicas

entrada/salida

#include <string.h> //StrCmp

#include <stdlib.h> //Exit y otras

#include <arpa/inet.h> //struct sockaddr_in

#include <sys/socket.h> //Socket, Connect...

struct sockaddr_in host, client; //Declaraciones

int a=sizeof (struct sockaddr);

int newsock;

int sockfd;

int main (int argc, char *argv [])

{

printf ("Code 1.0 ­ By Sagrini (2010)\n");

if ((sockfd=socket (2, 1, 0))==­1)

{

printf ("Error abriendo socket...\n\n");

return ­1;

}

host.sin_port=htons(31337);

host.sin_family=AF_INET;

host.sin_addr.s_addr=0;

memset (host.sin_zero, 0, 8);

if(bind(sockfd,(struct

sockaddr*)&host,sizeof(host))==­1)

{

printf ("Error haciendo binding...\n\n");

return ­1;

}

if(listen(sockfd,5)==­1)

{

printf ("Error escuchando...\n\n");

return ­1;

}

if((newsock=accept(sockfd, (struct

sockaddr*)&client, &a))==­1)

{

printf ("Error esperando conectando...\n\n");

return -1;

}

printf ("Got connection from %s:%d\n", inet_ntoa

(client.sin_addr), ntohs (client.sin_port));

close (sockfd);

close (newsock);

return 0;

}


3 ) Funciones principales II

3.1 ) Función recv ();

Otra funcion muy importante es RECV. Es sencillamente para recibir datos.. Se usa una vez conectado o con algun cliente conectado a ti.


char buffer [1024];

recv (newsock, &buffer, sizeof (buffer), 0);


Bueno, la cosa es pasarle el descrpitor de

socket, un buffer, su tamaño, y un cero. Para el tamaño se usa sizeof. Es una medida de seguridad.


3.2 ) Función send ();

La siguiente funcion es SEND. Evidentemente se usa para mandar datos. También se usa una vez conectado o con algun cliente conectado a ti.


char buffer [1024];

send (newsock, &buffer, sizeof (buffer), 0);


Usa los mismos argumentos que recv (). Se usan conjuntamente.


3.3 ) Función inet_addr ();

Bueno, esta función se usa para pasar una IP a

orden de bits de red. La hemos visto antes en la estructura sockaddr_in. Su argumento es una cadena

con una IP. Devuelve otra cadena.


3.4 ) Función htons ();

Bueno, esta función se usa para pasar un puerto a

orden de bits de red. La hemos visto antes en la

estructura sockaddr_in. Su argumento es un número.


3.5 ) Función inet_ntoa ();

Esta función es contraria a inet_addr. Pasa los bytes en orden de red a cadena. Su argumento es un puntero a la estructura struct sockaddr*.sin_addr.


3.5 ) Función inet_ntoa ();

Esta función es contraria a htons. Pasa los bytes en orden de red a número. Su argumento es un puntero a la estructura struct sockaddr*.sin_port.


3.0 ) Código de demostración

#include <sys/socket.h>

#include <arpa/inet.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <signal.h>

#include <time.h>

int socketfd, newsocket;

char *filename;

void shutup (int signal)

{

FILE *log;

log=fopen (filename, "a+");

times ();

printf ("Shutting down...\n\n");

fprintf (log, "Shutting down...\n\n");

fclose (log);

close (newsocket);

close (socketfd);

exit (0);

}

int times ()

{

FILE *log;

time_t now=time (0);

struct tm *ahora;

char buffer [40];

ahora=localtime ((const time_t*)&now);

strftime (buffer, 40, "%d/%m/%Y %H:%M:%S" ,

ahora);

log=fopen (filename, "a+");

printf ("%s ", buffer);

fprintf (log,"%s ", buffer);

fclose (log);

return 0;

}

int main (int argc, char *argv [])

{

time_t now=time (0);

struct tm *ahora;

char hora [40];

ahora=localtime ((const time_t*)&now);

strftime (hora, 40, "%d/%m/%Y %H:%M:%S" , ahora);

printf ("SmallServ 2.0 ­ By Sagrini ­ Sagrini

2010 ­ %s\n", hora);

if (getuid()!=0)

{

printf ("This proccess must be run by

root.\n\n");

return 1;

}

if (argc<3)

{

printf ("Use: %s <PORT> <LOG>

[MODE]\nMode:\t\t0) Fork the proccess to background

[DEFAULT].\n\t\t1) Run in the terminal.\n\n", argv

[0]);

return 1;

}

int cont;

FILE *log;

struct sockaddr_in client, host;

char buffer [1024];

int size=sizeof (client);

filename = argv [2];

socketfd=socket (2, 1 , 0);

host.sin_family=AF_INET;

host.sin_port=htons (atoi (argv [1]));

host.sin_addr.s_addr=0;

bind (socketfd, (struct sockaddr*)&host, sizeof

(struct sockaddr));

listen (socketfd, 3);

log=fopen (filename, "a+");

times ();

if (argv [3] != NULL && atoi (argv [3]) == 1)

printf ("\nStarting up...\n\n");

fprintf (log, "Starting up...\n\n");

fclose (log);

signal (SIGTERM, shutup);

signal (SIGINT, shutup);

if (argv [3] == NULL || atoi (argv [3]) == 0)

daemon (1, 0);

while (1)

{

newsocket=accept (socketfd, (struct

sockaddr*)&client, &size);

log=fopen (filename, "a+");

times ();

printf ("Got connection from %s:%d\n",

inet_ntoa (client.sin_addr), ntohs

(client.sin_port));

fprintf (log, "Got connection from %s:%d\n",

inet_ntoa (client.sin_addr), ntohs

(client.sin_port));

fclose (log);

cont=recv (newsocket, &buffer, 1024, 0);

while (cont>2)

{

printf ("%d", cont);

log=fopen (filename, "a+");

times ();

buffer [cont­1]='\0';

printf ("RECV %d bytes: %s ", cont­2,

buffer);

fprintf (log, "RECV %d bytes: %s\n",

cont­2, buffer);

fclose (log);

cont=recv (newsocket, &buffer, 1024, 0);

}

log=fopen (filename, "a+");

times ();

printf ("Finishing connection from %s:

%d\n\n", inet_ntoa (client.sin_addr), ntohs

(client.sin_port));

fprintf (log, "Finishing connection from %s:

%d\n\n", inet_ntoa (client.sin_addr), ntohs

(client.sin_port));

fclose (log);

close (newsocket);

}

close (socketfd);

return 0;

}


Este code es el mismo que antes, pero completo.

Es el código fuente original.


4 ) Sockets en Windows

4.1 ) Función WSAStartup ();

Microsoft cambia algunas cosas a la hora de programar en Windows. Así de malotes son...

Bueno, una de esas cosas es que a la hora de

usar sockets, tenemos que inicializarlos de otra

forma. Primero, en nuestro code después del típico

if (argc!=3)” tiene que ir algo así.


WSADATA wsa;

WSAStartup(MAKEWORD(2,2),&wsa);


Esto es para implementar algunas cosillas. No me

enrollaré en eso. Basta saber que se usa en todos

los programas con sockets.


4.2 ) Linkeando al compilar

Otra cosita que fastidia un poco es que a la hora

de compilar tenemos que linkear a una librería. En

mi PC se hace así.


gcc ­o code.exe code.c ­lwsock32


En otros Windows cambia. Toca mirar a Google... PD: Hay otras formas más elegantes de hacerlo, pero yo lo hago así.


4.3 ) Declarar y cerrar sockets.

Bueno, seguimos. Otro detalle es que a la hora de

declarar los descriptores de socket tenemos que

usar el tipo SOCKET. Se escribe así, en mayúscula. No

me he molestado en mirarlo, no merece la pena, pero

creo que es lo mismo que “int”. De todos modos de

otra forma da error...

Y a la hora de cerrar tenemos que usar la función

closesocket (); Es exactamente igual que close ();

Sólo cambia el nombre (¿Sólo?).


4.4 ) Recv y Send

Último detalle. En las funciones RECV y SEND

pasamos la dirección de buffer así:


recv (newsock, &buffer, sizeof (buffer), 0);

send (newsock, &buffer, sizeof (buffer), 0);


Pero los de Windows quieren que lo hagamos de

otra forma...

recv (newsock, buffer, sizeof (buffer), 0);

send (newsock, buffer, sizeof (buffer), 0);

Esto se debe a un cambio en la declaración. Si pasamos lo primero mandamos un puntero a un puntero

a un char. En cambio de la segunda forma es un

puntero a un char. Caprichosos...


4.0 ) Código de demostración.

#include <winsock2.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <time.h>

SOCKET sockfd, newsock;

char *filename;

int times ()

{

time_t now=time (0);

struct tm *ahora;

char buffer [40];

ahora=localtime ((const time_t*)&now);

strftime (buffer, 40, "%d/%m/%Y %H:%M:%S", ahora);

printf ("%s ", buffer);

return 0;

}

int main (int argc, char *argv [])

{

WSADATA wsa;

WSAStartup(MAKEWORD(2,2),&wsa);

time_t now=time (0);

struct tm *ahora;

char hora [40];

ahora=localtime ((const time_t*)&now);

strftime (hora, 40, "%d/%m/%Y %H:%M:%S", ahora);

printf ("SmallServ 2.0 ­ By Sagrini ­ Sagrini 2010 ­ %s\n", hora);

if (argc != 2)

{

printf ("Use: %s <port>\n\n", argv [0]);

return 1;

}

int cont;

struct sockaddr_in client, host;

char buffer [1024];

int size=sizeof (client);

sockfd=socket (2, 1 , 0);

host.sin_family=AF_INET;

host.sin_port=htons (atoi (argv [1]));

host.sin_addr.s_addr=0;

bind (sockfd, (struct sockaddr*)&host, sizeof (struct sockaddr));

listen (sockfd, 3);

times ();

printf ("Starting up...\n\n");

while (1)

{

newsock=accept (sockfd, (struct sockaddr*)&client, &size);

times ();

printf ("Got connection from %s: %d\n", inet_ntoa (client.sin_addr), ntohs (client.sin_port));

cont=recv (newsock, buffer, 1024, 0);

while (cont>2)

{

printf ("%d", cont);

times ();

buffer [cont­1]='\0';

printf ("RECV %d bytes: %s\n", cont­2, buffer);

cont=recv (newsock, buffer, 1024, 0);

}

times ();

printf ("Finishing connection from %s:%d\n\n", inet_ntoa (client.sin_addr), ntohs (client.sin_port));

closesocket (newsock);

}

closesocket (sockfd);

return 0;

}

Si os dais cuenta, he borrado algunas funciones. Eso

se debe a que al pasar el code a Windows me daba

pereza cambiar algunas cosas... Todo es culpa de la

tele jeje...


5.) Final

Bueno, esta es la parte que nadie lee en la que

el autor se despide y dice los agradecimientos.

Gracias por leerme jeje...

Primero, gracias a los que están en el foro

echándome una manita de vez en cuando. Gracias al

staff y a el-brujo, por el gran trabajo que estáis

haciendo. Gracias a los users, algunos colaboran

mejor que otros, pero sois parte de la comunidad...

Gracias a mi familia, que no puede faltar, y a

mis amigos. Evidentemente no leerán esto nunca, pero bueno... Muchas gracias por leer. Espero vuestros

comentarios. Toda crítica bienvenida. ¡¡¡ GRACIAS !!!

Sagrini en elhacker.net : sagrini@elhacker.net 

Ċ
Jack Harris,
21 feb. 2011 14:59
Comments