Sockets Unidad 4 Socket Es un canal de comunicación bidireccional que permite que los procesos (que están en la misma computadora o en diferentes computadoras) se comuniquen entre si. Tipos de sockets sockets de Internet: Comunican procesos en AF_INET diferentes máquinas. sockets unix: AF_UNIX Comunican procesos en la misma máquina. Sockets 1.- Sockets orientados a la conexión: SOCK_STREAM (sockets de flujo) Ambos procesos deben conectarse entre ellos con un socket y hasta que no esté establecida correctamente la conexión, ninguno de los dos puede transmitir datos. Usa protocolo TCP. 2.- Sockets orientados a la no conexión: SOCK_DGRAM (sockets de datagramas) No es necesario que los programas se conecten. Cualquiera de ellos puede transmitir datos en cualquier momento, independientemente de que el otro programa esté "escuchando" o no. Usa protocolo UDP. Modelo cliente-servidor Proceso Servidor: Provee un servicio a otro(s) proceso(s) Generalmente para proveer datos. Para establecer la conexión inicialmente, el servidor permanece arrancado y pasivo en espera de que otro proceso quiera conectarse a él. Proceso Cliente: Es el que solicita conexión con el servidor, normalmente para solicitarle datos. Conexión entre cliente y servidor Para poder realizar la conexión entre ambos procesos se requiere: Dirección IP: El cliente debe conocer a qué ordenador desea conectarse. Servicio (puerto) que queremos crear / utilizar: En una misma computadora pueden estar corriendo varios programas servidores. Cuando un cliente desea conectarse, debe indicar qué servicio quiere. Cada servicio dentro de la computadora debe tener un número único que lo identifique: Mayor a 1024, hasta 65535. Los números menores a 1024 están reservados para servicios habituales del sistemas operativo. Pasos que debe seguir un programa Servidor: 1.- Apertura de un socket, mediante la llamada socket(). 2.- Avisar al sistema operativo de que hemos abierto un socket y queremos que asocie nuestro programa a dicho socket: Con la llamada bind(). En esta llamada se indica el número de servicio al que se quiere atender. 3.- Avisar al sistema de que comience a atender dicha conexión de red. Con la llamada listen(). El S.O puede hacer una cola de clientes. 4.- Aceptar las conexiones de clientes al sistema operativo. Con la llamada accept(). El S.O obtiene siguiente cliente de la cola. Si no hay clientes se quedará bloqueado hasta que algún cliente se conecte. 5.- Escribir y recibir datos del cliente, por medio de las llamadas write() y read(), o bien con send() y recv(). Pasos que debe seguir un programa Cliente: 1.- Apertura de un socket, como el servidor, por medio de la llamada socket(). 2.- Solicitar conexión con el servidor por medio de la llamada connect(). Dicha llamada bloquerá al cliente hasta que el servidor acepte la conexión o bien si no hay servidor regresará un error. En esta llamada se da la dirección IP del servidor y el número de servicio que se desea. 3.- Escribir y recibir datos del servidor por medio de las funciones write() y read() o bien con send() y recv(). 4.- Cerrar la comunicación por medio de close(). Llamadas al sistema #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); Crea un socket y regresa su descriptor de archivo. domain Familia de direcciones: AF_INET o AF_UNIX type Indica si el socket es orientado a conexión SOCK_STREAM o no lo es SOCK_DGRAM. protocol Indica el protocolo a emplear. Habitualmente se pone 0 (el socket elige el protocolo) int bind(int sockfd, struct sockaddr *my_addr, int addrlen); Notifica al S.O de que se ha abierto un socket y se le solicita que lo asocie con nuestro proceso. sockfd Descriptor de socket obtenido con socket() My addr La estructura sockaddr lleva varios campos, entre los que es obligatorio rellenar: sin_family Es el tipo de conexión (igual que el primer parámetro de socket(). ) sin_port número correspondiente al servicio. sin_addr.s_addr Es la dirección del cliente al que el servidor atenderá. Colocando en ese campo el valor INADDR_ANY, atenderá a cualquier cliente. addrlen Longitud de la estructura struct sockaddr Ejemplo: struct sockaddr_in my_addr; … my_addr.sin_family = AF_INET; // Ordenación de bytes de la máquina my_addr.sin_port = htons(3490); // short, Ordenación de bytes de la red my_addr.sin_addr.s_addr = INADDR_ANY; memset(&(my_addr.sin_zero), '\0', 8); // Poner a cero el resto de la estru int addrlen=sizeof(struct sockaddr); if (bind(sockfd, (struct sockaddr *)&my_addr, addrlen) == -1) { perror("bind"); exit(1); } int listen(int sockfd, int backlog); Notiica al S.O que comience a atender una conexión entrante. El servidor debe estar atento a conexiones entrante de un cliente. sockfd Descriptor de socket (obtenido con socket() ) backlog Número de conexiones permitidas en la cola de entrada (máximo de clientes en la cola). int accept(int sockfd, void *addr, int *addrlen); - Acepta conexiones de otras máquinas. - Extrae la primera conexión de la cola de conexiones entrantes. - Devuelve un nuevo descriptor de socket con el que se atenderá la conexión, recibiendo ( recv() ) o enviando ( send() ) sockfd Descriptor de socket obtenido con socket() struct sockaddr_in Será llenado por el S.O contendrá el ip y puerto de la conexión entrante. addrlen Será llenado por el S.O y tendrá sizeof(struct sockaddr_in) int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); Solicita conexión con el servidor. El cliente se bloqueará hasta que el servidor acepte la conexión de lo contrario regresará error. sockfd Descriptor de socket obtenido con socket() serv_addr Dirección IP del servidor y el número de servicio que se desea. Longitud de la estructura struct sockaddr. addrelen int send(int sockfd, void *msg, int len, int flags); int recv(int sockfd, void *msg, int len, int flags); Archivos de linux implicados Para la programación de socket no es estrictamente necesario ningún archivo. Sabiendo la dirección IP y el número de servicio, se ponen directamente en código y todo resuelto /etc/hosts Contiene una lista de nombres de computadoras conectados en red y dirección IP de cada uno. Habitualmente en el /etc/hosts del cliente se suele colocar el nombre del servidor y su dirección IP. La llamada gethostbyname(), a la que pasándole el nombre de la máquina como una cadena de caracteres, devuelve una estructura de datos entre los que está la dirección IP. Ejemplo: En el programa cliente: struct hostent *he; struct sockaddr_in their_addr; …… if ((he=gethostbyname(argv[1])) == NULL) { // obtener información de máquina perror("gethostbyname");exit(1); } if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } their_addr.sin_family = AF_INET; // Ordenación de bytes de la máquina their_addr.sin_port = htons(PORT); // short, Ordenación de bytes de la red their_addr.sin_addr = *((struct in_addr *)he->h_addr); memset(&(their_addr.sin_zero), '\0', 8); // poner a cero el resto de la estructura if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) { perror("connect"); exit(1); }