|
En este artículo explicaré el funcionamiento básico de los sockets en Linux y veremos dos ejemplos prácticos de su uso. No me meteré en temas de eficiencia o temas demasiado técnicos en la explicación del protocolo IP y de TCP ni UDP. El usuario debe tener unos conocimientos básicos en la programación en Linux así como de redes.
Para poder probar estos ejemplos necesitas un sistema Linux o Unix. Un compilador de C, yo los he probado tanto con cc como con gcc.
Seguro que más de una vez habrás querido hacer un programita que se pueda comunicar con otros programitas a través de la red o internet, pues bien, este artículo te explica cómo hacer eso utilizando los sockets y el protocolo UPD/IP y TCP/IP.
Conceptos básicos Socket: es el componente básico para la intercomunicación de procesos a través de la red. La interfaz del socket proporciona acceso a los protocolos de transporte. Hay varios tipos de sockets, estos tipos definen las propiedades de la comunicación visibles por el usuario. La comunicación entre procesos sólo se puede realizar con sockets del mismo tipo. Tipos de sockets:
- STREAM SOCKET: proporciona comunicación bidireccional, alcanzable, secuencial y no duplica el flujo de datos. Este tipo de comunicación es similar a una conversación telefónica. Bajo Internet (AF_INET), el tipo del socket es SOCK_STREAM, y TCP.
- DATAGRAM SOCKET: soporta flujo bidireccional de mensajes. Un proceso utilizando un datagram socket puede recibir mensajes en un orden diferente al de la secuencia de envío e incluso pueden llegarle duplicados. Es similar a enviar una carta. Bajo Internet (AF_INET), el tipo de socket es SOCK_DGRAM, y usa UDP.
- RAW SOCKET: proporciona acceso a protocolos de capas inferiores. Estos protocolos normalmente son orientados a datagramas, pero sus características dependen de la interfaz proporcionada por el protocolo. Los protocolos RAW no se utilizan en la mayoría de las aplicaciones. Estos proporcionan soporte para la investigación y desarrollo de nuevos protocolos o para el acceso a ciertas facilidades de los protocolos existentes.
Para crear un socket se utiliza la función socket(),
s = socket(dominio, tipo, protocolo);
|
Si no se especifica el protocolo ( valor = 0) el sistema selecciona el protocolo que soporte el tipo del socket origen. El dominio está especificado en una de las constantes definidas en <sys/socket.h>. Para el dominio UNIX la constente es AF_UNIX, para el dominio INTERNET la constente es AF_INET. Estas constantes indican el formato de la dirección para interpretar los nombres.
Los tipos de los sockets están definidos en <sys/socket.h>. SOCK_STREAM, SOCK_DGRAM y SOCK_INET son soportados por AF_INET y AF_UNIX.
s= socket(AF_INET, SOCK_STREAM, 0);
|
Una vez creado el socket debemos darle un nombre para que las otras aplicaciones puedan conectarse al mismo. Este nombre debe ser único.
La función bind(s, nombre, longitud_nombre) dará un nombre a nuestro socket s creado anteriormente.
... struct sockaddr_in bs, des;
bind(s,(struct sockaddr*)&bs, sizeof(bs))
des.sin_family = AF_INET;
des.sin_addr.s_addr = inet_addr(argv[1]);
des.sin_port = htons(atoi(argv[2]));
...
|
Creo que es mejor pasar directamente a los ejemplos porque se entienden muy bien y los podrás probar y ver como funcionan.
Los ejemplos En el primer ejemplo utilizamos UDP (User Datagram Protocol) que es un protocolo orientado a datagramas y simple (cada operación de salida generada por un proceso produce exactamente un datagrama UDP). Es un protocolo no orientado a conexión y no se garantiza que alcance su destino.
Cliente ECO con UDP
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/**********************************************************/
/* función MAIN */
/* Orden Parametros: IP destino, puerto , mensaje */
/* */
/**********************************************************/
main(int argc, char *argv[]) {
int s;
struct sockaddr_in bs,des;
char resp[255];
int *sd;
if (argc == 4) {
// Creamos el socket
s = socket(AF_INET,SOCK_DGRAM,0);
if (s != -1) {
bs.sin_family = AF_INET;
bs.sin_port = htons(0); //Coge cualquier puerto disponible en la máquina
bs.sin_addr.s_addr = htonl(INADDR_ANY); //Coge cualquier IP de la máquina
//Asigna un nombre local al socket
if( bind(s,(struct sockaddr*)&bs, sizeof(bs)) != -1) {
des.sin_family = AF_INET;
des.sin_addr.s_addr = inet_addr(argv[1]);
des.sin_port = htons(atoi(argv[2]));
//Envia el string
sendto(s,argv[3],strlen(argv[3])+1,0,(struct sockaddr*)&des,sizeof(des));
printf("\n\n->Enviando: %s, a: %s en el puerto: %s \n",argv[3], argv[1], argv[2]);
//Recibe el string del servidor
recvfrom(s,resp, sizeof(resp) ,0,(struct sockaddr*)&des, sd);
printf("<-Recibido: %s\n",resp);
//Cierra el socket
close(s);
} else {
printf("ERROR al nombrar el socket\n");
}
} else {
printf("ERROR: El socket no se ha creado correctamente!\n");
}
} else {
printf("\n\n\aEl número de parámetros es incorrecto\n\n");
}
}
|
Servidor de ECO UDP
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/**********************************************************/
/* función MAIN */
/* Orden Parametros: Puerto */
/* */
/**********************************************************/
main(int argc, char *argv[]) {
int s;
struct sockaddr_in bs, in;
char entrada[255];
int sd;
if (argc == 2)
{
// Creamos el socket
s = socket(AF_INET,SOCK_DGRAM,0);
if (s != -1) {
bs.sin_family = AF_INET;
bs.sin_port = htons(atoi(argv[1])); //Asigna el puerto especificado por la línea de comandos
bs.sin_addr.s_addr = htonl(INADDR_ANY); //IP cualquiera de la máquina
//Asigna un nombre local al socket
if( bind(s,(struct sockaddr*)&bs, sizeof(bs)) != -1) {
printf("\n\aServidor ACTIVO escuchando en el puerto: %s\n",argv[1]);
//El while permite atender a múltiples clientes
while (1) {
//Recibe la cadena del cliente
if ( recvfrom(s,entrada, sizeof(entrada) ,0,(struct sockaddr*) &in, &sd)== -1)
perror("Error en recvfrom");
//Devuelve la cadena al cliente
if (sendto(s,strcat(entrada,"\0"),strlen(entrada)+1,0,(struct sockaddr*) &in, sizeof(in)) == -1)
perror("Error en sendto");
}
//Se cierra el socket
close(s);
} else {
printf("ERROR al nombrar el socket\n");
}
} else {
printf("ERROR: El socket no se ha creado correctamente!\n");
}
} else {
printf("\n\n\aEl número de parámetros es incorrecto\n\n");
}
}
|
Cliente de ECO TCP
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/**********************************************************/
/* función MAIN */
/* Orden Parametros: IP destino, puerto , mensaje */
/* */
/**********************************************************/
main(int argc, char *argv[]) {
int s;
struct sockaddr_in bs,des;
char resp[255];
int *sd;
if (argc == 4) {
// Creamos el socket
s = socket(AF_INET,SOCK_STREAM,0);
if (s != -1) {
bs.sin_family = AF_INET;
bs.sin_port = htons(0); //Asigna un puerto disponible dela máquina
bs.sin_addr.s_addr = htonl(INADDR_ANY); //Asigna una IP de la máquina
//Asigna un nombre local al socket
if( bind(s,(struct sockaddr*)&bs, sizeof(bs)) != -1) {
//Se prepara el nombre de la máquina remota
des.sin_family = AF_INET;
des.sin_addr.s_addr = inet_addr(argv[1]);
des.sin_port = htons(atoi(argv[2]));
//Establece la conexión con la máquina remota
connect(s,(struct sockaddr*)&des,sizeof(des));
//Envía el mensaje
send(s,argv[3],strlen(argv[3])+1,0);
printf("\n\n->Enviando: %s, a: %s en el puerto: %s \n",argv[3], argv[1], argv[2]);
//Recibe la respuesta
recv(s,resp, sizeof(resp) ,0);
printf("<-Recibido: %s\n",resp);
//Se cierra la conexión (socket)
close(s);
} else {
printf("ERROR al nombrar el socket\n");
}
} else {
printf("ERROR: El socket no se ha creado correctamente!\n");
}
} else {
printf("\n\n\aEl número de parámetros es incorrecto\n\n");
}
}
|
Servidor de ECO TCP
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAX_CONN 10 //Nº máximo de conexiones en espera
/**********************************************************/
/* función MAIN */
/* Orden Parametros: Puerto */
/* */
/**********************************************************/
main(int argc, char *argv[]) {
int s,s_aux;
struct sockaddr_in bs,in, out_s;
char entrada[255];
int sd;
if (argc == 2) {
// Creamos el socket
s = socket(AF_INET, SOCK_STREAM,0);
if (s != -1) {
bs.sin_family = AF_INET;
bs.sin_port = htons(atoi(argv[1]));
bs.sin_addr.s_addr = htonl(INADDR_ANY);
//Asigna un nombre local al socket
if( bind(s,(struct sockaddr*)&bs, sizeof(bs)) != -1) {
printf("\n\aServidor ACTIVO escuchando en el puerto: %s\n",argv[1]);
//Espera al establecimiento de alguna conexión
listen(s, MAX_CONN);
//Permite atender a múltiples usuarios
while (1) {
//Establece una conexión
s_aux = accept (s,(struct sockaddr*) &in, &sd);
//Recibe el mensaje del cliente
if ( recv(s_aux,entrada, sizeof(entrada) ,0)== -1)
perror("Error en recvfrom");
//Envia el mensaje al cliente
if (send(s_aux,strcat(entrada,"\0"),strlen(entrada)+1,0) == -1)
perror("Error en sendto");
//Cierra la conexión con el cliente actual
close(s_aux);
}
//Cierra el servidor
close(s);
} else {
printf("ERROR al nombrar el socket\n");
}
} else {
printf("ERROR: El socket no se ha creado correctamente!\n");
}
} else {
printf("\n\n\aEl número de parámetros es incorrecto\n\n");
}
}
|
Tienes que crear 4 ficheros (clienteUDP.c, servidorUDP.c clienteTCP.c, servidorTCP.c) y copiar el código anterior en su respectivo fichero. Los compilas y ya puedes probarlos.
Los clientes también los puedes probar con cualquier servidor de eco de internet de que tengas instalado.
Nota de compilación: En algunas máquinas Unix es necesario indicarle al compilador con qué librerias debe linkar para resolver las referencias externas producidas por la utilización de funciones de manejo de sockets. Ejemplo: $gcc clienteUDP.c -o clienteUDP -lsocket -lnsl En Linux no suele ser necesario la utilización de -lsocket –lnsl
udp tcp sockets |