Enviar y recibir estructuras en lenguaje C a través de sockets en Linux

Visitas: 1078  
Tiempo total: 24 días con 16:50:19 hrs  

El código de esta publicación es en lenguaje C puro y sirve para establecer una comunicación cliente – servidor utilizando TCP a través de cualquier interface de red. Muestra funciones generalizadas para enviar cualquier tipo de estructura convirtiéndola de primero a cadenas de caracteres para después enviarla a través de la red y, leerla convirtiéndola de nuevo en la estructura original.

Cliente

El primer paso es configurar la conexión en el programa cliente, con el siguiente método se edita la IP y el puerto en el cual el servidor está funcionando:

/* ------------------ variables globales ------------------ */
char ip[20] = "0.0.0.0";
int puerto = 10475;

/* Configurar conexion */
void configurar_conexion(){
int menu = 0;
int salir = 0;
while(salir == 0){
printf("\n");
printf("********** Menu: inicializacion **********\n");
printf("Ip servidor: %s\n", ip);
printf("Puerto: %d\n", puerto);
printf("\n");
printf("Menu:\n");
printf("1. Ip servidor\n");
printf("2. Puerto\n");
printf("3. Salir\n");
if (scanf("%d", (&menu)) == 0){
while (getchar() != '\n'){
printf("Error, ingrese numero: \n");
scanf("%d", (&menu));
}
}
if(menu==1){
printf("Ingrese Ip:\n");
scanf("%s", ip);
}else if(menu==2){
printf("Ingrese Puerto:\n");
if (scanf("%d", (&puerto)) == 0){
while (getchar() != '\n'){
printf("Error, ingrese numero: \n");
scanf("%d", (&puerto));
}
}
}else if(menu==3){
salir=1;
}
}
}

El siguiente paso es la inicialización del socket a través del descriptor, en el código a continuación puedes observar el Descriptor global de la conexión, así también ver las variables configuradas en el método anterior:

/* socket servidor */
int Descriptor;

/* inicializacion de socket */
void inicializacion(){
/* variables de conexion */
struct in_addr ipv4addr;
inet_pton(AF_INET, ip, &ipv4addr);
/* Abriendo socket */
Descriptor = socket (AF_INET, SOCK_STREAM, 0);
if (Descriptor == -1)
printf ("Error - Abriendo socket\n");
/* Inicializando el servicio */
struct sockaddr_in Direccion;
Direccion.sin_family = AF_INET;
Direccion.sin_port = (unsigned short) puerto;
Direccion.sin_addr = ipv4addr;
if (connect (Descriptor, (struct sockaddr *)&Direccion,sizeof (Direccion)) == -1){
printf ("Error - Inicializando el servicio\n");
}
}

Se Finaliza la conexión con:

/* finaliza socket */
void finalizacion(){
/* cerrando socket */
close (Descriptor);
}

Ahora que tenemos la conexión, independientemente de cómo se configura el servidor procedemos a enviar la información, y por cada envío el servidor nos debe de responder de acuerdo ha como implementemos nuestro código. Este método, envía la estructura paquete:

void enviar(struct paquete paquete_a_enviar){
/* variable de salida */
int salir=0;
/* Escribiendo cadena de 5 caracteres */
int Escrito = 0; /* Núm. de caracteres que ya se han escrito */
int Aux = 0; /* Número de caracteres escritos en cada pasada */
int Longitud = sizeof(struct paquete); /* Total de caracteres a escribir */
/* buffer */
unsigned char buf[sizeof(struct paquete)];
memcpy(buf, &paquete_a_enviar, sizeof(struct paquete));
/* Bucle mientras no se hayan escrito todos los caracteres deseados */
while (Escrito < Longitud && salir == 0){ /* Se escriben los caracteres */ Aux = write (Descriptor, buf + Escrito, Longitud - Escrito); /* Si hemos conseguido escribir algún carácter */ if (Aux > 0){
/* Incrementamos el número de caracteres escritos */
Escrito = Escrito + Aux;
}else{
/* Si no hemos podido escribir caracteres,
comprobamos la condición de socket cerrado */
if (Aux == 0){
printf ("Socket cerrado - escritura\n");
salir = 1;
}else{
/* y la condición de error */
if (Aux == -1){
printf ("Error - escritura\n");
salir = 1;
}
}
}
}
}

Como es solo un tipo de paquete el que se envía, debe de ser similar a los paquetes TCP que la red gestiona para tener una generalización de la información, entonces en mi caso el paquete enviado es:

/* paquete */
struct paquete{
int tipo; /* tipo de struct */
char campo1[50];
char campo2[50];

char campo7[50];
char directorio[100];
char mensaje[250];
};

El entero tipo es de acuerdo a la estructura que representa, cada número puede representar una diferente acción tanto en el servidor como en la aplicación cliente. En este caso, mi estructura consistía en un chat de dos o más personas que enviaban distintos tipos de información.

Ahora, la función para recibir la información (Se puede observar, la conversión a estructura desde la cadena de caracteres):

struct paquete recivir(){
struct paquete paquete_recivido;
/* leyendo cadena de 5 caracteres */
int Leido = 0;/* Número de caracteres leídos hasta el momento */
int Aux = 0; /* Guardaremos el valor devuelto por read() */
int Longitud = sizeof(struct paquete); /* Número de caracteres a leer */
/* buffer */
unsigned char buf[sizeof(struct paquete)];
/* Bucle hasta que hayamos leído todos los caracteres que estamos esperando */
while (Leido < Longitud){ /* Se leen los caracteres */ Aux = read (Descriptor, buf + Leido, Longitud - Leido); /* Si hemos conseguido leer algún carácter */ if (Aux > 0){
/* Se incrementa el número de caracteres leídos */
Leido = Leido + Aux;
}else{
/* Si no se ha leído ningún carácter, se comprueba
la condición de socket cerrado */
if (Aux == 0){
printf ("Socket cerrado - lectura\n");
}else{
/* y la condición de error */
if (Aux == -1){
printf ("Error - lectura\n");
}
}
}
}
memcpy(&paquete_recivido, buf, sizeof(buf));
return paquete_recivido;
}

Con esto finalizamos la configuración en el lado del cliente, a continuación lo que es necesario para compilar las aplicaciones. Debido a que en el lado del servidor se deben de estar comprobando si existen mensajes cada determinado tiempo, se debe de agregar una librería llamada pthread.h en el momento en que se compila la aplicación:

cc servidor.c -o servidor –pthread
cc cliente.c -o cliente –pthread

Otro paso importante, es iniciar el servicio que utilizara el protocolo TCP, el puerto a utilizar se indicara automáticamente por la aplicación. Para agregar el servicio, con los siguientes comandos:

sudo gedit /etc/services
jose 60200/tcp

y

sudo gedit /etc/hosts
127.0.1.1 jose

Servidor

Con este servicio (jose) procedemos a crear el servidor. El sistema operativo en el que cree este código es Ubuntu 13.04, y mi editor fue Geany. A continuación, las variables globales de la aplicación:

char servicio[50] = "jose";
char protocolo[50] = "tcp";
char interface[50]="eth0";
/* variable socket servidor */
int Descriptor;
/* variable socket cliente */
int Descriptor_Cliente;
/* variables a mostrar */
char ip[20];
int puerto;

Como observamos, establecemos el nombre de nuestro servicio y el protocolo, la interface de red sirve para obtener automáticamente la IP, y podemos observar también que utilizamos dos variables Descriptor, una para escuchar a los clientes y otra para enviar la información a través de los sockets.

La estructura utilizada en el servidor, debe de ser la misma utilizada en el cliente. El primer paso es establecer la IP a partir de la interface que vamos a utilizar con el siguiente método:

void establecer_ip(){
/* mostrando parametros */
struct ifreq ifr;
/* I want to get an IPv4 IP address */
ifr.ifr_addr.sa_family = AF_INET;
/* I want IP address attached to "eth0" */
strncpy(ifr.ifr_name, interface, IFNAMSIZ-1);
ioctl(Descriptor, SIOCGIFADDR, &ifr);
/* display result */
sprintf(ip,"%s",inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));
}

El segundo paso es inicializar el servidor, publicarlo en la IP y puerto generados automáticamente:

void inicializacion(){
/* Se obtiene informacion del servicio a utilizar */
/* Estructura devuelta */
struct servent *Puerto;
/* La llamada a la función */
if ((Puerto = getservbyname (servicio, protocolo))==NULL)
printf("Error - servicio no disponible\n");
/* Abriendo socket */
Descriptor = socket (AF_INET, SOCK_STREAM, 0);
if (Descriptor == -1)
printf ("Error - Abriendo socket\n");
/* Inicializando el servicio */
struct sockaddr_in Direccion;
Direccion.sin_family = AF_INET;
Direccion.sin_port = Puerto->s_port;
Direccion.sin_addr.s_addr =INADDR_ANY;
if (bind (Descriptor, (struct sockaddr *)&Direccion, sizeof (Direccion)) == -1){
printf ("Error - Inicializando el servicio\n");
}
/* mostrando parametros */
establecer_ip();
puerto= (int) Puerto->s_port;
/* mostrando informacion */
printf("********** Chat servidor **********\n");
printf("Detalles:");
printf("Servidor: %s\n", ip);
printf("Puerto: %d\n", puerto);
printf("\n");
}

Se Finaliza la conexión con:

void finalizacion(){
/* cerrando socket */
close (Descriptor_Cliente);
close (Descriptor);
}

En el método de inicialización, se puede observar cómo se utiliza la variable Descriptor para atender las peticiones externas, este es el descriptor del servidor. Ahora, para enviar y recibir la información que por generalización es el mismo método y función utilizadas en la aplicación cliente, con la única diferencia que se tiene que reemplazar la variable Descriptor por Descriptor_cliente, para crear una conexión distinta a la que sirve para escuchar los envíos de información de parte del cliente.

Para implementar la recepción de información, podemos utilizar el siguiente método:

void leer(){
/* paquete recivido */
struct paquete paquete_recivido=recivir();
/* respuesta de servidor */
if ( paquete_recivido.tipo == 0){
printf("creando usuario...\n");
crear_usuario(paquete_recivido);
printf("Finalizado...\n");
}else if (paquete_recivido.tipo == 2){
printf("validando login de usuario...\n");
login_usuario(paquete_recivido);
printf("Finalizado...\n");
}else if (paquete_recivido.tipo == 3){
...
}

Lo que hace es, leer cualquier paquete enviado por cualquier cliente y a partir del tipo de paquete, llamar al método indicado, pero con este método surge la necesidad de entrar a un ciclo para leer la información recibida continuamente, esto con el siguiente método que hace uso de un ciclo while para leer continuamente la información enviada en el Socket por el cliente:

/* maximo de clientes en el sistema */
int Max_clientes = 1;

void correr(){
int salir = 0;
while (salir==0){
/* Atendiendo llamadas al servicio */
if (listen (Descriptor, Max_clientes) == -1){
printf ("Error - Atendiendo llamadas al servicio\n");
}
/* Atendiendo clientes */
struct sockaddr Cliente;
int Longitud_Cliente;
Descriptor_Cliente = accept (Descriptor, &Cliente, (socklen_t *__restrict) &Longitud_Cliente);
if (Descriptor_Cliente == -1){
printf ("Error - Atendiendo clientes\n");
}
leer();
}
}

Para finalizar, se puede leer la información cada X tiempo haciendo uso de la librería mencionada anteriormente, esto es con el uso de una línea de tiempo:

void * tiempo(void * var){
int out=0;
while(out==0){
/*
*
*
*
*
Implementación
*
*
*
*
*/
sleep(0.1);
}
return NULL;
}

void linea_de_tiempo(){
pthread_t linea_tiempo; // Variable que almacena la información sobre el thread
if(pthread_create(&linea_tiempo, NULL, tiempo, NULL)) {
fprintf(stderr, "Error - creando tiempo\n");
}
}
Rating: 5

Para recibir boletines de información, por favor escribe tu correo electrónico:

Por favor ingrese un correo electrónico valido.
Registrado correctamente!