Resumen Socket Un socket es un canal de comunicación de datos. Una vez que dos procesos se conectan a través de n soket, usan un descriptor de socket para leer y escribir datos en el socket. Para obtener uno de estos descriptores usamos la llamada de sistema socket. Esta llamada tiene tres argumentos que son: int domain int type int protocol Hay vario tipos posibles de dominios para los sockets. Estan definidos en /usr/include/sys/socket.h. Podemos encontrarlos directamente en /usr/includes/sys/socket.h o en versiones posteriores de la biblioteca de C, en el específico del sistema operativo /usr/include/bits/socket.h. AF_UNIX: Protocolos internos de UNIX. AF_INET: Protocolos de internet ARPA. AF_ISO: Protocolo de la organización de estándares internacional. AF_NS: Protocolos del sistema de red de Xerox. Casi siempre vamos a querer usar el protocolo AF_INET. Este tipo de socket es el más flexible, ya que nos permite usar comunicación local y de red. Hay varios tipos de sockets: SOCK_STREAM: Proporciona una conexión fiable, secuencial y en dos direcciones. SOCK_DGRAM: Una conexión poco fiable. Se usa con los sockets UDP, que se explicaron en su debido momento. SOCK_RAW: Usados para protocolos de red internos. SOCK_SEQPACKET: Sólo se usan en el protocolo AF_NS. SOCK_RDM: no implementados. Usaremos sockets de tipo SOCK_STREAM en el ejemplo, ya que nos permite una comunicación en dos direcciones entre procesos que están conectados mediante el sockets. Puede haber otras situaciones que requieran configuraciones de este parámetro. El tercer y último argumento usamos el valor cero en este parámetro en nuestro ejemplo que daremos. La llamada de sistema devuelve un descriptor de socjet que podremos usarlo con las demas llamadas de sistema socket o un -1 que indica que no se pudo asignar un socket. BIND La función bind asocia un proceso con un socket. Usamos normalmente bind en procesos servidor para configurar un socket para conexiones de clientes entrantes. Los argumentos de la función de sistema bind son: 1 int socket struct sockaddr * mi_direccion int largo_mi_direccion El promer argumento es el valor de socket devuelto por la llamada previa a la funció socket. El segundo argumento es la direccion de una estructura sockaddr.- el tercero es el tamaño de la estructura de datos a la que señala sockaddr. Se necesita por que diferentes protocolos usan diferentes tipos de estructuras sockaddr. Llamadas de sistema de conexión SI estamos escribiendo una aplicación cliente, el siguiente paso es establecer la conexión con el servidor con el que se quiere realizar la conexión. Por otro lado, si estamos escribiendo una aplicación servidor, vamos a querer configurar el socket para esperar conexiones de aplicaciones clientes. Las dos primeras llamadas de sistema listen y accept se usan en programación de servidores. En aplicaciones cliente sólo necesitamos una llamada de sistema para la conexión. Esta llamada recibe el nombre de connect. LISTEN Después de haber creado el socket y haberlo asociado con el proceso usando bind un proceso de tipo servidor puede llamar a la función listen para escuchar conexiones de sockets entrantes. Los argumentos de la llamada listen son: int socket int tamaño_cola_entrada El primer argumento de listen es el valor entero del socket devuelto por la llamada previa a la función socket. El segundo establece el tamaño de la cola entrante. Un proceso servidor usa frecuentemente la llamada de sistema fork para crear un duplicado de sí mismo que administra las llamadas de socket entrantes. este método cobra mucho sentido si esperamos múltiples conexiones de clientes simultáneas. Sin embargo para la mayoría de los programas normalmente es más adecuado y sencillo procesar las conexiones entrantes de una en una y establecer el tamaño de la cola con un valor grande. ACCEPT Según las llamadas entrantes llegan a un socket que está escuchando, se pondrán a la cola hasta que el programa servidor esté listo para procesarlas. Cuando el servidor está listo para procesar una neva conexión, usará la llamada de sistema accept para recuperar na llamada pendiente de la cola del socket. La llamada accept devolverá un nuevo descriptor de socket. Este se usará para la comunicación entre las aplicaciones cliente y servidor. Al mismo tiempo el socket original es todavía capaz de escuchar nuevas llamadas entrantes. 2 Los argumentos de la llamada de sistema accept son: int socket struct sockaddr * mi_direccion int *tamaño_mi_direccion El primer argumento de accept es el descriptor de socket del socket que escucha. El segundo es un puntero al área de datos que se rellena con la información sobre la conexión entrante. El tercero es un puntro a un entero que se establece con el número máximo de bytes que puede acomodar mi_direccion. Si la llamada accept debe rellenar mi_direccion con menos datos, tamaño_mi_direccion se cambiará al tamaño de los datos. Algunos programas servidor no usan la llamada accept para administrar las peticiones entrantes. CONNECT La llamada de sistema connect se usa para conectar un socket local con un servidor remoto. Un uso típico es la de especificar la información de un computador host de un proceso destino que se está ejecutando con un computador remoto. Los argumentos son: int socket struct sockeaddr * direccion_servidor int tamaño_direccion_servidor El primer argumento está definido opr el valor de retorno a la función socket. El segundo se usa para especificar la dirección del protocolo. TRANSFERENCIA DE DATOS Una vez que se ha establecido la conexión, es el momento de comenzar a transferir datos entre el servidor y el cliente. Hay dos llamadas de sistema para este cometido. Recv se usa para recibir datos, y send para enviarlos. RECV Usamos la función recv para recibir mensajes de un socket conectado. Los argumentos de recv son: int socket void *buf int tama_buf unsigned int flags El socket definido por el primer argumento ya debe haber sido conectado con un puerto usando connect. El segundo argumento es un puntero a un bloque de memoria donde 3 se almacenan los mensajes recibidos. El tercero especifica el tamaño en bytes del bloque de memoria reservado. El cuatro contiene indicadores de operación podemos combinar valores con el operador booleano y 1 en el cuarto argumento. Nosotros usaremos siempre un valor cero para los indicadores de los ejemplos. MSG_OOB : Procesa datos fuera de banda (útil para administrar mensajes de control de alta prioridad). Normalmente usamos cero para un comportamiento normal. MSG_PEEK: Recoge un mensaje entrante sin leerlo. MSG_WAITALL: Espera a que el buffer de datos recibidos esté completamente lleno antes de volver. SEND Usaremos la llamada de sistema send para pasar los datos a otro programa usando un socket. Tanto la aplicación servidor como el cliente usan la función send. Las aplicaciones cliente usan para enviar peticiones de servicio a procesos servidor remotos y los procesos servidor la usan para devolver datos a los clientes. Los argumentos son: int socket const void * datos_mensaje int largo_datos_mensaje unsigned int flags El primer argumento es el valor de socket devuelto por la llamada a la función socket. El segundo contiene cualqueir dato que tenga que ser transferido. El tercero especifica el tamaño en bytes de los datos del mensaje. El cuarto es siempre cero en nuestro ejemplo pero se pueden usar las siguientes constantes: MSG_OOB: Proceso datos fuera de banda. MSG:DONTROUTE: No usa enrutamiento. 4 EJEMPLO DE SERVIDOR: #include<stdio.h> #include<sys/socket.h> #include<netinet/in.h> #include<netdb.h> ... int puerto = 9000; main() { struct sockaddr_in s; struct sockaddr_in p; int sd; int tsd; int tama_dire; char buf[256]; int i, len; sd = socket(AF_INET, SOCK_STREAM, 0); bzero(&s, sizeof(s)); s.sin_family = AF_INET; s.sin_addr.s_addr = INADDR_ANY; s.sin_port = htons(puerto); bind(sd, (struct sockaddr *)&s, sizeof(s)); listen(sd, 20) while(1) { tsd = accept(sd, (struct sockaddr *)&p, &tama_dire); recv(tsd, buf, 256, 0); printf(“Recibido del cliente: %s”, buf); //convertiremos los caracteres en mayúsculas len = strlen(buf); for (i =0; i<len; i++) buf[i] = toupper(buf[i]); send(tsd, buf, len, 0); 5 close(tsd); } } EJEMPLO DE CLIENTE: #include<stdio.h> #include<sys/socket.h> #include<netinet/in.h> #include<netdb.h> ... int puerto = 9000; char *host_name = “127.0.0.1”; main() { char buf[256]; char mensaje[256]; int sd; struct sockaddr_in p; struct hostent *nombre_server; char * str =”Una cadena para comprobar”; nombre_server = gethostbyname(host_name); bzero(&s, sizeof(s)); p.sin_family = AF_INET; p.sin_addr.s_addr = htonl(INADDR_ANY) p.sin_add-s_addr = ((struct in_addr *)(nombre_server->h_addr))->d_addr; p.sin_port = htons(puerto); sd = socket(AF_INET, SOCK_STREAM, 0); connect(sd, (void *)&p, sizeof(p)); send(sd, str, strlen(str),0); recv(sd, buf, 256,0); printf(“Mensaje recibido del servidor %s”,buf); close(sd); } 6