Wednesday, September 12, 2007

Sockets para linux - cliente

A ver si esta vez ya me animo a ir actualizando el blog. El verano esta acabando, los exámenes están hechos, al fin tengo tiempo libre. Así que... ¿qué mejor para empezar que cumplir con lo prometido? Así que aquí va una de sockets de Berckley, por supuesto en C y en linux (GNU/Linux para los pedantes xD) La presentación... bueno, eso ya lo haré algún día.

El origen del código es una práctica para la asignatura de Sistemas Operativos Distribuidos, de la Facultad de Informática de la UPM. Pero esto es lo de menos, ya que el payload está, sólo interesa la parte de los sockets. Lo relevante es que viene de la implementación con sockets y sin estado. Esto afecta de manera que para cada comunicación se abre una nueva conexión y se cierra al terminar la petición. Algo al estilo HTTP 1.0. Bien, ¡manos a la obra!


Cuerpo del cliente
Este es el código útil que va a hacer cosas y entre ellas, enviar y recibir datos por la red. Sustituir ese código útil en los corchetes es tarea vuestra :P

RDIR *r_opendir(char *dirname) {
mensaje_t mensaje;
int s;
[...]
if ((ptr->s=conectar()) < 0) {
perror("error de conexion");
return NULL;
}
[...]
if ((aux=send(ptr->s, &mensaje, sizeof(mensaje), 0)) < 0)
perror("send mensaje");

if (recv(ptr->s, &mensaje, sizeof(mensaje), 0) < 0)
perror("recv mensaje");

close(s);
return [...];
}

Trivial, ¿verdad? Bueno, si sólo fuera eso sería estupendo, ya estaría todo claro, el código habla por sí mismo: conectar, enviar y recibir. De hecho también se podría usar read(...) y write(...) ya que s no es ni más ni menos que un descriptor de fichero. Pero se observa una sospechosa función conectar(), que como es de prever no viene en ninguna biblioteca y no hay direcciones, ni puertos ni nada de nada. Normalmente todo eso iría en el código principal, pero por hacerlo mas bonito y reusable lo separé en una función aparte, la cual no sería dificil de meter en un .h si fuera necesario.


Función conectar()
Esta es la que hace que el descriptor s contenga un socket listo para ser usado para hacer read y write sobre él, o si se prefiere, send(...) y recv(...).

int conectar(void){
struct sockaddr_in dir;
struct hostent *host_info;
int s;

if (getenv("SERVIDOR") == NULL || getenv("PUERTO") == NULL){
printf("Establecer SERVIDOR y PUERTO en variables de entrono\n");
return -1;
}

if ((s=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("error creando socket");
return -1;
}
host_info=gethostbyname(getenv("SERVIDOR"));
memcpy(&dir.sin_addr.s_addr, host_info->h_addr, host_info->h_length);
dir.sin_port=htons(atoi(getenv("PUERTO")));
dir.sin_family=PF_INET;
if (connect(s, (struct sockaddr *)&dir, sizeof(dir)) < 0) {
perror("error en connect");
close(s);
return -1;
}
return s;
}

Lo primero se definen dos estructuras de datos para contener la informacion del socket a crear y del host destino.
En segundo lugar se comprueba (de manera chapucera) que las variables de entorno que indiquen el server y puerto están puestas. Se podrían usar valores fijos, pedirlos interactivamente, o lo que prefiramos.
Más tarde se crea el socket en sí, con una función sorprendentemente llamada socket(...). Lo creamos del tipo stream y protocolo TCP (sí, un poco de redundancia nunca viene mal, jeje...). Una vez tenemos el socket creado, necesitamos conectarlo y para ello necesitamos una direccion. Como lo que tenemos es un nombre (host.dominio.com) lo resolvemos con gethostbyname(...) y vamos construyendo la estructura que nos define el host destino. El siguiente campo se rellena con el puerto destino, que se ha de pasar a formato de red con htons(...) (por el tema de máquinas big-endian/little-endian). De hecho este nombre tan raro viene de "host to network, short"
Llegados a este punto tenemos el socket creado y una estructura que nos define exactamente dónde conectarlo. Pues una llamada a connect(...) y tenemos un socket listo para usar.

Resumen
El proceso en caso de no haber conexión, en el cliente es:
crear socket -> identificar host+puerto destino -> conectar socket -> enviar/recibir -> cerrar socket.

El caso del servidor lo dejamos para la siguiente entrada.

1 comment:

Irwin said...

Eres un geniò!!!!!!!! gracias me sirviò fulllll