Chat Cliente/Servidor

Anuncio
Chat Cliente/Servidor utilizando Linux, GTK+ y sockets
orientados a conexión.
Resumen:
Este documento describe el desarrollo de una aplicación de Chat tipo Cliente/Servidor. Se utilizan
sockets orientados a conexión bajo la plataforma Linux. La programación se hacen en C utilizando
Glade-2, la librería GTK+ e hilos POSIX.
Objetivo general:
Desarrollar una aplicación gráfica de tipo Cliente/Servidor en Linux utilizando sockets orientados a
conexión.
Objetivos particulares:
•
•
•
Utilizar Glade-2 en el diseño de la Interfaz Gráfica de Usuario en Linux.
Emplear sockets orientados a conexión para desarrollar una aplicación Cliente/Servidor.
Integrar y sincronizar el cliente (GTK+) y el servidor (hilo POSIX) bajo una misma interfaz
gráfica de usuario.
Desarrollo
1.- Diseño de la interfaz gráfica
1.1.- Utilizando Glade-2 crear un nuevo proyecto
GTK+ cuya ventana principal (ver Fig. 1) debe
contener:
• Una barra de herramientas con dos botones:
Acerca de y Salir.
• Dos vistas de texto, una para desplegar
mensajes recibidos y otra para editar los
mensajes a enviar.
• Una entrada de texto para editar las
direcciones IP del servidor al que se le va a
enviar el último mensaje editado.
• Un botón de envío para realizar dicha
acción.
1.2.- Añadir las señales correspondientes
• destroy para la ventana principal
• clicked para ambos botones de la barra de
herramientas
• clicked para el botón Enviar
1.3.- Configurar las vistas de texto con un tamaño
de 300x150 y 300x50 respectivamente.
1.4.- Guardar y Construir.
Universidad del Istmo. Ing. En Computación
Fig. 1 Ventana principal de la aplicación.
M. en C. J. Jesús Arellano Pimentel
2.- Codificación de las retrollamadas correspondientes a las señales (archivo callbacks.c)
2.1.- Retrollamada correspondiente a la señal destroy de la ventana principal
void
on_window_destroy (GtkObject *object, gpointer user_data)
{
if(sd != 0) /*Si el socket se creo, hay que cerrarlo*/
close(sd);
gtk_main_quit();
}
El código de esta retrollamada verifica si el socket fue creado, dese así se cierra para evitar
complicaciones futuras con la invocación a bind. Además se invoca a la función gtk_main_quit(), la cual
termina el ciclo principal de procesamiento de eventos en GTK+.
2.2.- Retrollamada correspondiente a la señal clicked del botón salir de la barra de herramientas
void
on_toolbuttonSalir_clicked (GtkToolButton *toolbutton, gpointer user_data)
{
on_window_destroy((GtkObject *)toolbutton, user_data);
}
Se asume que el nombre de la ventana principal es “window”. Se invoca la retrollamada asociada a la
señal destroy de la ventana principal.
2.3.- Retrollamada correspondiente a la señal clicked del botón Enviar
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
void
on_buttonEnviar_clicked (GtkButton *button, gpointer user_data)
{
int sd;
struct sockaddr_in server_addr;
GtkWidget *entryIP;
char szIPS[16], szMsg[256];
GtkTextIter start, end;
GtkWidget * textviewE = lookup_widget((GtkWidget *)button, "textviewE");
GtkTextBuffer * textbuffer = gtk_text_view_get_buffer((GtkTextView *)textviewE);
gtk_text_buffer_get_start_iter(textbuffer,&start);
gtk_text_buffer_get_end_iter(textbuffer,&end);
entryIP = lookup_widget((GtkWidget *)button,"entryIP");
strcpy(szIPS,gtk_entry_get_text((GtkEntry *)entryIP));
sd = socket(AF_INET, SOCK_STREAM, 0);
bzero((void *)&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(szIPS);
server_addr.sin_port = htons(4200);
bzero((void *)&szMsg, 256 * sizeof (char));
if(connect(sd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){
perror("Error en la llamada a connect");
sprintf(szMsg,"Error en la llamada a connect\nla dirección %s no es válida", szIPS);
Universidad del Istmo. Ing. En Computación
M. en C. J. Jesús Arellano Pimentel
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
}else{
}
}
mostrarMsgr((GtkWidget *)button,"localhost","chat",szMsg);
sprintf(szMsg,"%s %s",szMiIP, szMiNN);
write(sd, szMsg, 256 * sizeof(char));
read(sd, szMsg, 256 * sizeof(char));
strcpy(szMsg,gtk_text_buffer_get_text(textbuffer,&start,&end,FALSE));
write(sd, szMsg, 256 * sizeof(char));
read(sd, szMsg, 256 * sizeof(char));
mostrarMsgr((GtkWidget *)button,szMiIP,szMiNN,szMsg);
close(sd);
En esta retrollamada se implementa el cliente utilizando sockets orientados a la conexión. Las líneas 5
y 6 definen la variable y la estructura necesaria para manipular el socket desde el cliente. En la línea 19
se crea un socket utilizando protocolos de Internet y orientado a conexión. Las líneas de la 21 a la 24
configuran la estructura server_addr para poder realizar la conexión (línea26). Si la conexión es
exitosa se envía el mensaje correspondiente y se muestra el eco del mensaje (líneas de la 31 a la 38); si
la conexión no tiene éxito se presenta un mensaje de error por parte del host local (líneas de la 27 a la
29).
El protocolo para el envío de mensajes al servidor, después de la conexión, tiene el siguiente orden:
✔ Enviar en una misma cadena la dirección IP del cliente y el nombre corto almacenadas en las
variables szMiIP y szMiNN correspondientemente. El contenido de estas variables debe
proporcionarse al ejecutar la aplicación.
✔ Recibir la confirmación del servidor de la información enviada
✔ Enviar el mensaje (máximo 256 caracteres) editado.
✔ Recibir el eco del mensaje enviado
2.4.- Variables y funciones externas al archivo callbacks.c usadas en las retrollamadas.
extern char szMiNN[32], szMiIP[16];
extern int sd;
extern void mostrarMsgr(GtkWidget *window, char *szIP, char *szNN, char *szMsgr);
2.5.- Librerías necesarias en el archivo callbacks.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
3.- Codificación de la función principal (main en main.c)
3.1.- Adicionar las siguientes dos variables
pthread_t th;
pthread_attr_t attr;
Las variables anteriores servirán para implementar un hilo independiente el cual va a ejecutar el código
del servidor.
Universidad del Istmo. Ing. En Computación
M. en C. J. Jesús Arellano Pimentel
3.2.- Previo a la función gtk_init (&argc, &argv); validar los argumentos dados desde la línea de
comandos cuando se ejecute la aplicación y copiarlos en sus respectivas variables. Además se debe
inicializar apropiadamente el ambiente de ejecución multihilo para permitir la ejecución segura entre los
hilos Gdk-Gtk y los hilos POSIX, en este caso el hilo POSIX que implementará al servidor.
if(argc != 3){
printf("Error:\nFaltan argumentos!!\nUso: %s dir_ip nom_corto\n",argv[0]);
exit(1);
}
strcpy(szMiIP,argv[1]);
strcpy(szMiNN,argv[2]);
gdk_threads_init ();
gdk_threads_enter ();
Al ejecutar la aplicación se deberá teclear, además del nombre del ejecutable, la dirección IP de la
máquina que se está utilizando y el nombre corto por el cual queremos que nos identifiquen en el
servidor al cual le enviaremos mensajes.
3.3.- Después de mostrar la ventana principal se debe inicializar la vista de texto donde se desplegarán
los mensajes recibidos de otras máquinas o clientes.
inicializarTVR(window);
La función anterior se describe más adelante.
3.4.- Una vez inicializadas la vista de texto de recepción se debe crear y ejecutar el hilo del servidor.
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&th, &attr, servidor, (void *)window);
La función servidor se describirá posteriormente.
3.5.- Después de la función gtk_main(); cerrar apropiadamente la ejecución multihilo con la invocación de
la siguiente función
gdk_threads_leave ();
4.- Codificación de la función inicializarTVE (en main.c)
Esta función tiene como propósito crear los tag's para formatear de color el texto recibido a desplegar,
así como crear la marca necesaria para situar el desplazamiento de la vista de texto en el último
mensaje recibido. El código es el siguiente:
void inicializarTVR(GtkWidget *window)
{
GtkTextBuffer *textbuffer;
GtkTextIter iter;
GtkWidget *tvR = lookup_widget(window, "textviewR");
textbuffer = gtk_text_view_get_buffer((GtkTextView *)tvR);
Universidad del Istmo. Ing. En Computación
M. en C. J. Jesús Arellano Pimentel
gtk_text_buffer_get_iter_at_offset(textbuffer,&iter,0);
gtk_text_buffer_create_tag(textbuffer,"azul", "foreground", "blue", NULL);
gtk_text_buffer_create_tag(textbuffer,"gris", "foreground", "gray", NULL);
gtk_text_buffer_create_mark(textbuffer, "end", &iter, FALSE);
}
Se asume que la vista de texto para desplegar los mensajes recibidos se llama “textviewR”
5.- Codificación del servidor (en main.c)
1. void * servidor(void *w)
2. {
3.
GtkWidget *window = (GtkWidget *) w;
4.
struct sockaddr_in server_addr;
5.
struct sockaddr_in client_addr;
6.
int sc, size;
7.
char szBuffer[256], szIP[16], szNN[32];
8.
9.
sd = socket(AF_INET, SOCK_STREAM, 0);
10.
11.
bzero((void *)&server_addr, sizeof (server_addr));
12.
server_addr.sin_family = AF_INET;
13.
server_addr.sin_addr.s_addr = INADDR_ANY;
14.
server_addr.sin_port = htons(4200);
15.
if(bind(sd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == ­1){
16.
perror("Error en bind");
17.
exit(1);
18.
}
19.
20.
listen(sd, 5);
21.
22.
size = sizeof(client_addr);
23.
while(1){
24.
sc = accept(sd, (struct sockaddr *)&client_addr, &size);
25.
bzero((void *)&szBuffer, 256 * sizeof (char));
26.
if(sc < 0)
27.
break;
28.
29.
read(sc, szBuffer, 256 * sizeof(char));
30.
sscanf(szBuffer,"%s %s",szIP,szNN);
31.
sprintf(szBuffer,"Ok");
32.
write(sc, szBuffer, 256 * sizeof(char));
33.
read(sc, szBuffer, 256 * sizeof(char));
34.
write(sc, szBuffer, 256 * sizeof(char));
35.
36.
gdk_threads_enter ();
37.
mostrarMsgr(window,szIP,szNN,szBuffer);
38.
gdk_threads_leave ();
39.
40.
close(sc);
41.
}
42.
perror("Error en accept");
43.
close(sd);
44.
45.
return NULL;
46. }
Esta función implementa el hilo encargado de trabajar como servidor. Primero se crea el socket de la
familia de Internet y orientado a conexión (línea 19). Posteriormente se configura la estructura de datos
Universidad del Istmo. Ing. En Computación
M. en C. J. Jesús Arellano Pimentel
asociada al servidor (líneas de la 11 a la 14). Después se hace el bind (línea 15) que en caso de tener éxito
el servidor se queda en modo de espera de conexiones (línea 20) hasta que un cliente intente la conexión y
se acepte (línea 24). El protocolo de recepción y envío de mensajes (líneas de la 29 a la 34) se corresponde
con el mencionado en el punto 2.3 de este documento. Finalmente las líneas 36 y 38 se muestran los
mensajes en la interfaz gráfica. Esto es necesario para sincronizar y ejecutar de forma segura este hilo
POSIX con los hilos Gdk-Gtk.
6.- Codificación de la función mostrarMsgr (en main.c)
1. void mostrarMsgr(GtkWidget *window, char *szIP, char *szNN, char *szMsgr)
2. {
3.
GtkTextBuffer *textbuffer;
4.
GtkTextIter iter;
5.
GtkTextMark *mark;
6.
GtkWidget *tvR;
7.
8.
tvR = lookup_widget(window, "textviewR");
9.
textbuffer = gtk_text_view_get_buffer((GtkTextView *)tvR);
10.
11.
gtk_text_buffer_get_iter_at_offset(textbuffer,&iter,0);
12.
13.
mark = gtk_text_buffer_get_mark (textbuffer, "end");
14.
gtk_text_buffer_get_iter_at_mark (textbuffer, &iter, mark);
15.
16.
gtk_text_buffer_insert_with_tags_by_name(textbuffer,&iter,szNN,­1,"azul",NULL);
17.
gtk_text_buffer_insert(textbuffer,&iter,"­>",­1);
18.
gtk_text_buffer_insert_with_tags_by_name(textbuffer,&iter,szIP,­1,"gris",NULL);
19.
gtk_text_buffer_insert(textbuffer,&iter,"\n",­1);
20.
gtk_text_buffer_insert(textbuffer,&iter,szMsgr,­1);
21.
gtk_text_buffer_insert(textbuffer,&iter,"\n",­1);
22.
23.
gtk_text_view_scroll_mark_onscreen ((GtkTextView *)tvR, mark);
24. }
Esta función tiene el propósito de mostrar todos los mensajes recibidos por el servidor, en azul el
nombre corto seguido de la dirección IP del cliente que envía en color gris, después se muestra el
mensaje enviado (líneas 16 a la 21). Las líneas 13, 14 y 23 se utilizan para visualizar el último mensaje
recibido y presentado en la vista de texto.
7.- Variables globales y librerías necesarias en el archivo main.c
#include <glib.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
char szMiNN[32], szMiIP[16];
int sd = 0;
Universidad del Istmo. Ing. En Computación
M. en C. J. Jesús Arellano Pimentel
8.- Compilación (archivo Makefile).
Para poder enlazar las funciones de la librería pthread.h y en general del uso de procesos ligeros (hilos)
se debe modificar el archivo Makefile generado por Glade-2.
LDFLAGS = ­lpthread
9.- Ejecución de la aplicación.
Finalmente, desde la terminal ejecutar la aplicación, por ejemplo si solo se va a probar en la misma
máquina, entonces la siguiente orden será suficiente:
./chatcs 127.0.0.1 jjap&
La Figura 2 muestra un ejemplo de la ejecución con la aplicación ya terminada.
Fig. 2. Chat C/S enviándose mensajes a si mismo
Universidad del Istmo. Ing. En Computación
M. en C. J. Jesús Arellano Pimentel
Descargar