You are on page 1of 15

1

Programación de Sockets en C para
LINUX
SOCKETS
1. Fundamentos
Los sockets son un sistema de comunicación entre procesos de diferentes máquinas de
una red. Más exactamente, un socket es un punto de comunicación por el cual un proceso
puede emitir o recibir información.
Los sockets utilizan una serie de primitivas para establecer el punto de comunicación, para
conectarse a una máquina remota en un determinado puerto que esté disponible, para
escuchar en él, para leer o escribir y publicar información en él, y finalmente para
desconectarse. Con todas las primitivas que ofrecen los sockets, se puede crear un
sistema de diálogo muy completo.
2. Definición

Un socket es un punto final de un proceso de comunicación. Es una abstracción que
permite manejar de una forma sencilla la comunicación entre procesos, aunque estos
procesos se encuentren en sistemas distintos, sin necesidad de conocer el funcionamiento
de los protocolos de comunicación subyacentes.

Figura 1. Abstracción del proceso de comunicación entre dos máquinas.
Es así como estos “puntos finales” sirven de enlaces de comunicaciones entre procesos.
Los procesos tratan a los sockets como descriptores de ficheros, de forma que se pueden
intercambiar datos con otros procesos transmitiendo y recibiendo a través de sockets.
3. Tipos de Sockets
El tipo de sockets describe la forma en la que se transfiere información a través de ese
socket. Existen muchos tipos de sockets, sin embargo, los más populares son:
o
o

Stream (TCP)
Datagram (UDP)

lo que implica un cierto tiempo empleado en el establecimiento de la conexión. Mientras uno de los sockets atiende peticiones de conexión (servidor).2. en el mismo orden en que se van recibiendo. Socket Stream Son un servicio orientado a la conexión. el par de sockets funciona como los streams: todos los datos se leen inmediatamente. por eso se dice que están libres de errores. Socket Datagram Son un servicio de transporte no orientado a la conexión. Se utilizan sobre todo para la depuración del código de los protocolos. Una vez que los dos sockets estén conectados. hay que enviar datos adicionales cada vez que se realice una comunicación.1. que se pueden enviar a una localización determinada. cada vez que se envía un datagrama. Diferencias entre Socket Stream y Datagrama El problema aparece al momento de decidir por cual protocolo o tipo de socket usar. mientras que TCP no tiene límite. aunque hay algunas diferencias entre los protocolos que sirven para ayudar en la decisión y utilizar un determinado tipo de socket. pero en su utilización no está garantizada la fiabilidad. se pueden utilizar para transmitir datos en ambas direcciones. luego los mensajes son más grandes que los TCP. En UDP hay un límite de tamaño de los datagramas.2 o Raw (acceso directo al protocolo: root) 3. El protocolo de comunicaciones con streams es un protocolo orientado a conexión. lo que significa que. En UDP. aunque tiene la ventaja de que se pueden indicar direcciones globales y el mismo mensaje llegará a un muchas máquinas a la vez. el otro solicita una conexión (cliente). Las comunicaciones a través de datagramas usan UDP (User Datagram Protocol). éstos serán informados de tal suceso para que tomen las medidas oportunas. . Como se puede ver. Como el protocolo TCP está orientado a conexión. donde los datos se transfieren sin encuadrarlos en registros o bloques. Socket Raw Son sockets que dan acceso directo a la capa de software de red subyacente o a protocolos de más bajo nivel. Si se rompe la conexión entre los procesos. La decisión depende de la aplicación cliente/servidor que se esté desarrollando. asegurándose de esta manera que los datos lleguen al destino en el orden de transmisión. 3. cada vez que se envíen datagramas es necesario enviar el descriptor del socket local y la dirección del socket que debe recibir el datagrama. cuya entrega no está garantizada. hay que enviar también el descriptor del socket local y la dirección del socket que va a recibir el datagrama. que no es necesario emplear en UDP. 3. Son más eficientes que TCP.4. ya que para establecer una comunicación utilizando el protocolo TCP (Transmission Control Protocol). hay que establecer en primer lugar una conexión entre un par de sockets. una vez que se ha establecido la conexión. Los datos se envían y reciben en paquetes. 3.3. Los paquetes pueden ser duplicados. establecido en 64 kilobytes. perdidos o llegar en un orden diferente al que se envió. hay que establecer esta conexión entre los dos sockets antes de nada.

TCP parece más indicado para la implementación de servicios de red como un control remoto (rlogin. . la comunicación con sockets hace uso de una serie de primitivas. 5. telnet) y transmisión de ficheros (ftp). no hay la seguridad de que el paquete llegue o no al destino. bind para escribir en él y publicar información. el usar un flujo TCP en vez de un datagrama UDP es más sencillo y hay menos posibilidades de tener problemas. y está justificado el tiempo adicional que supone realizar la verificación de los datos. que necesitan transmitir datos de longitud indefinida. entre las que destacan socket para establecer el punto de comunicación. en otras palabras. Al contrario. TCP es un protocolo ordenado. lo único que cambia es la forma de “abrir” el descriptor de archivo. connect para conectarse a una maquina remota en un determinado puerto que esté disponible. Figura 4. Modelo de Sockets Como ya se mencionó. garantiza que todos los paquetes que se envíen serán recibidos en el socket destino en el mismo orden en que se han enviado. read para leer de él. Tipos de socket para el protocolo de Internet 4. la comunicación a través de sockets TCP es un mecanismo realmente útil. En la figura 5 se muestra el funcionamiento de una conexión con sockets. Los datagramas son bloques de información del tipo lanzar y olvidar. UDP es menos complejo y tiene una menor sobrecarga sobre la conexión. Con todas estas primitivas se puede establecer un sistema de comunicaciones muy completo. es decir. shutdown o close para desconectarse. Las funciones para manipular estos descriptores de archivo se presentan en la tabla 1. Para la mayoría de los programas que utilicen la red. En resumen. Sin embargo. no garantiza que los datagramas que se hayan enviado sean recibidos en el mismo orden por el socket de recepción. una conexión mediante sockets es idéntica a una comunicación mediante un pipe bidireccional. cuando se requiere un rendimiento óptimo. esto hace que sea el indicado en la implementación de aplicaciones cliente/servidor en sistemas distribuidos montados sobre redes de área local. entre otras que se hacen mención en la tabla 1.3 UDP es un protocolo desordenado. los sockets extienden el concepto de descriptores de archivos para usarlos en conexiones remotas. Funcionamiento Genérico de Sockets Como se ha mencionado.

. Ver figura 6. el cliente intenta encontrar al servidor en la máquina servidora en el puerto especificado. Normalmente. Funciones para manipular los descriptores de archivos para conexiones remotas.4 Tabla 1. un servidor se ejecuta sobre una computadora específica y tiene un socket que responde en un puerto específico. un sistema de comunicación necesita de dos entidades bien diferenciadas: el Servidor y el Cliente. Como se puede observar en la figura 5. escuchando a través del socket a que un cliente haga una petición. El servidor únicamente espera. Para realizar una petición de conexión. En el lado del cliente: el cliente conoce el nombre de host de la máquina en la cual el servidor se encuentra ejecutando y el número de puerto en el cual el servidor está conectado.

Funcionamiento de una conexión socket.5 Figura 5. Figura 7. Ver figura 7. Si todo va bien. Esto se debe a que necesita un nuevo socket (y. si la conexión es aceptada. un socket se crea de forma . Figura 6. el servidor obtiene un nuevo socket sobre un puerto diferente. Por la parte del cliente. en consecuencia. el servidor acepta la conexión. un número de puerto diferente) para seguir atendiendo al socket original para peticiones de conexión mientras atiende las necesidades del cliente que se conectó. Además de aceptar. Servidor acepta la solicitud y establece la conexión con el cliente. Cliente realiza petición de conexión al servidor.

el servidor espera datos mediante un llamado a la función recvfrom.1 APLICACIONES CLIENTE / SERVIDOR BASADAS PROTOCOLOS ORIENTADOS A CONEXIÓN. la cual requiere la dirección del servidor. Ahora el cliente y el servidor pueden comunicarse escribiendo o leyendo en o desde sus respectivos sockets. el cliente asigna un número de puerto local a la máquina en la cual está siendo ejecutado. FUNCIONAMIENTO GENÉRICO DE SOCKETS 6. 6. la aplicación servidor se ejecuta primero y posteriormente la aplicación cliente establece una conexión a través de la cual se intercambian mensajes (requerimientos y respuestas). En lugar de éste. EN SOCKETS Y En esta arquitectura. Figura 1. Por otro lado. 6. El cliente no establece una conexión sino que envía datagramas al servidor mediante llamados a la función sendto(). EN SOCKETS Y En esta arquitectura el llamado es diferente. .2 APLICACIONES CLIENTE / SERVIDOR BASADAS PROTOCOLOS NO ORIENTADOS A CONEXIÓN. La función recvfrom retorna la dirección de red del proceso cliente para poder enrutar correctamente las respuestas. Arquitectura de un llamado a sockets utilizando protocolos orientados a conexión.6 satisfactoria y puede usarlo para comunicarse con el servidor. Es importante darse cuenta que el socket en el cliente no está utilizando el número de puerto usado para realizar la petición al servidor.

Bind Vincula una dirección local IP y un puerto de protocolo a un socket. int socket(int family. FUNCIONES REQUERIDAS PARA UN LLAMADO A SOCKETS Socket Crea un descriptor para que sea usado en la comunicación.7 socket() bind() recvfrom() Cliente socket() Bloqueado mientras se reciben datos de un cliente bind()) Requerimientos sendto() Proceso del requerimiento recvfrom() sendto() Respuesta Figura 2. Recvfrom Recibe el siguiente datagrama entrante y graba la dirección fuente. Connect Conexión con un cliente remoto. Sendto Envía un datagrama. Recv Recibe el siguiente datagrama entrante. Write Manda datos a través de una conexión. Recvms Recibe el siguiente datagrama entrante (variación de recv). int type. usualmente a un dirección previamente grabada. . Select Informa de sockets listos para escribir o leer de ellos. Arquitectura de un llamado a sockets utilizando protocolos no orientados a conexión. Send Envía un datagrama. Listen Pone el socket en modo pasivo y establece el número de conexiones TCP entrantes que el sistema puede encolar. Read Lee los datos entrantes de una conexión. Accept Acepta la siguiente conexión entrante. Sendmsg Envía un datagrama (variación de send). int protocol). Close Termina la conexión y elimina el descriptor.

int addrlen). struct sockaddr *servaddr. Conversiones Existen dos tipos de ordenamiento de bytes: bytes más significativos. struct sockaddr *myaddr. int flags). En esta sección se hablará de las funciones inet_addr() y inet_ntoa(). Imaginémonos que se quiere convertir una variable larga de Ordenación de Bytes para Nodos a una de Orden de Bytes para Redes. Las siguientes funciones son análogas a ésta y se encargan de hacer este tipo de conversiones:     htons() -> ``Nodo a variable corta de Red'' htonl() -> ``Nodo a variable larga de Red'' ntohs() -> ``Red a variable corta de Nodo'' ntohl() -> ``Red a variable larga de Nodo'' Estarás pensando ahora para qué necesitamos todas estas funciones. struct sockaddr *from. char *buff. int nbytes. int send(int sockfd. es que sin_addr y sin_port. las funciones que aquí se describen para realizar estas conversiones. int listen(int sockfd. Existen dos tipos a los cuales seremos capaces de convertir: short y long [6]. int recvfrom(int sockfd. de la estructura sockaddr_in. int sendto(int sockfd. y el porqué de estos ordenamientos. int connect(int sockfd. int flags). char *buff. en los ejemplos. int accept(int sockfd. Direcciones IP En C. cuando se termine de leer este documento todas estas dudas se aclararán (aunque sea un poco). int *addrlen). int nbytes.8 int bind(int sockfd. char *buff. struct sockaddr *peer. int addrlen). algunas máquinas utilizan este tipo de ordenación para guardar sus datos internamente. Éste es llamado “Ordenación de Bytes para Redes”[5]. int addrlen). int flags. deben ser del tipo Ordenación de Bytes para Redes. int *addrlen). y a ese punto se entenderán mucho mejor. 8. Una cosa importante. int nbytes. char *buff. int recv(int sockfd. int flags. . int nbytes. ¿Qué haríamos? Existe una función llamada htonl() que haría exactamente esta conversión. Se verá. struct sockaddr *to. Bien. int backlog). 7. Todo lo que necesitará es leer y practicar mucho. y bytes menos significativos. existen algunas funciones que nos ayudarán a manipular direcciones IP.

siempre que tengamos una estructura "dest" del tipo sockaddr_in*/ Por otro lado. (.. inet_ntoa() convierte a una cadena que contiene una dirección IP en un entero largo.ip).sin_addr). la función inet_addr() convierte una dirección IP en un entero largo sin signo (unsigned long int).h> .. socket() #include <sys/types.9 Por un lado. se mostrará la sintaxis de la función. Además de las que se mencionan aquí. por ejemplo: (.65. Explicación de Funciones Importantes En esta sección. (en la cual se nombrarán algunas de las funciones más utilizadas para la programación en C de sockets).h> #include <sys/socket.. printf("La dirección es: %s\n".. Por ejemplo: (.s_addr = inet_addr("195. ip=inet_ntoa(dest.) dest. (. y algunos pequeños comentarios..) char *ip.sin_addr. las bibliotecas necesarias a incluir para llamarla. existen muchas funciones más.) Se deberá recordar también que la función inet_addr() devuelve la dirección en formato de Ordenación de Bytes para Redes por lo que no necesitaremos llamar a htonl().36..) /*Recordar que esto sería así.12")...

10 int socket(int domain. Por otro lado podremos hacer que nuestra dirección IP y puerto sean elegidos automáticamente: . es un puntero a una estructura sockaddr addrlen.h> #include <sys/socket. se ha producido un error (obsérvese que esto puede resultar útil para rutinas de verificación de errores). Se podrá establecer como AF_INET (para usar los protocolos ARPA de Internet).int addrlen). Analicemos los argumentos:    domain. Éstas son las más usadas. simplemente se puede establecer el protocolo a 0. respectivamente. La llamada bind() se usa cuando los puertos locales de nuestra máquina están en nuestros planes (usualmente cuando utilizamos la llamada listen()). my_addr. devolverá -1 en caso de error. type. el cual podremos usar luego para llamadas al sistema. bind() #include <sys/types. Si nos devuelve -1.h> int bind(int fd. Análogamente socket().int type. protocol. La función socket() nos devuelve un descriptor de socket.int protocol). Analicemos los argumentos:    fd. Existen muchas más. Su función esencial es asociar un socket con un puerto (de nuestra máquina). aunque no se nombrarán aquí. Aquí se debe especificar la clase de socket que queremos usar (de Flujos o de Datagramas). contiene la longitud de la estructura sockaddr a la cuál apunta el puntero my_addr. o como AF_UNIX (si se desea crear sockets para la comunicación interna del sistema). pero no las únicas. Se debería establecer como sizeof(struct sockaddr). Es el descriptor de fichero socket devuelto por la llamada a socket(). Aquí. Las variables que deben aparecer son SOCK_STREAM o SOCK_DGRAM según querramos usar sockets de Flujo o de Datagramas. struct sockaddr *my_addr.

Analicemos los argumentos:    fd.h> int listen(int fd. Análogamente de lo que pasaba con bind(). Es un puntero a la estructura sockaddr la cuál contiene la dirección IP destino y el puerto.h> #include <sys/socket. Devolverá -1 si ocurre algún error.11 server. Debería configurarse como el fichero descriptor del socket. siempre que esté entre 1024 y 65535 (y siempre que no estén siendo usados por otros programas).sin_port = 0.s_addr = INADDR_ANY. listen() #include <sys/types. int addrlen).h> #include <sys/socket. el cuál fue devuelto por la llamada a socket().sin_addr. addrlen.int backlog). Se podrá establecer un puerto. La función connect() se usa para conectarse a un puerto definido en una dirección IP. /* bind() elegirá un puerto aleatoriamente */ server. este argumento debería establecerse como sizeof(struct sockaddr). Veamos los argumentos de listen(): . serv_addr. struct sockaddr *serv_addr.h> int connect(int fd. connect() #include <sys/types. /* pone la Ip del seridor automáticamente */ Un aspecto importante sobre los puertos y la llamada bind() es que todos los puertos menores que 1024 están reservados.

2. exit(-1).h> int accept(int fd.h> #include <sys/socket. antes de que su dirección sea pasada a accept(). La función listen() se usa si se están esperando conexiones entrantes. que fue devuelto por la llamada a listen().-) A continuación. si nosotros aceptamos (asóciese con accept()) . se deberá llamar a accept(). Veamos los argumentos de la función:    fd. ya que esta llamada es un poco diferente de las demás. Es el número de conexiones permitidas. para así aceptar las conexiones entrantes.&sin_size))==-1){ printf("accept() error\n"). addrlen. Es un puntero a una estructura sockaddr_in en la quel se pueda determinar qué nodo nos está contactando y desde qué puerto.. . Es el fichero descriptor del socket. por lo que conviene establecerlo como sizeof(struct sockaddr_in). 4. Cuando alguien intenta conectarse a nuestra computadora. lo cual significa. int *addrlen). Después de llamar a listen().(struct sockaddr *)&client. Se dará un pequeño ejemplo del uso de accept() para obtener la conexión. el cual fue devuelto por la llamada a socket() backlog. listen() devolverá -1 en caso de error. Es muy fácil de entender: alguien sólo podrá conectarse (asóciese con connect()) a nuestra máquina.. /* En la siguiente línea se llama a accept() */ if ((fd2 = accept(fd. Es la longitud de la estructura a la que apunta el argumento addr. accept() #include <sys/types. La secuencia resumida de llamadas al sistema es: 1. addr. Es el fichero descriptor del socket.12   fd. si se quiere. socket() bind() listen() accept()/* En la próxima sección se explicará como usar esta llamada */ Como todas las funciones descritas arriba. que alguien pueda conectarse a nuestra máquina.) sin_size=sizeof(struct sockaddr_in). (. void *addr. se debe usar accept() para conseguir la conexión. 3.

h> #include <sys/socket.const void *msg. flags. . Veamos los argumentos:  fd.h> #include <sys/socket. unsigned int flags).h> int recv(int fd. void *buf. Es la longitud máxima que podrá tener el búffer. Al igual que todas las demás llamadas que aquí se vieron. Y sobre los argumentos de esta llamada:     fd. con el cual se desea enviar datos. recv() #include <sys/types.h> int send(int fd. send() #include <sys/types.int len. o el número de bytes enviados en caso de éxito.int flags). buf. Es el fichero descriptor del socket. len. msg. es la longitud del dato que se quiere enviar (en bytes).. Si se desea enviar datos usando sockets no conectados de datagramas debe usarse la llamada sendto(). flags..13 } (. len. Es el búfer en el cual se guardará la información a recibir. se deberá establecer como 0. deberá ser establecido a 0[8] . Es un puntero apuntando al dato que se quiere enviar. int len. El propósito de esta función es enviar datos usando sockets de flujo o sockets conectados de datagramas. Por ahora.) A este punto se usará la variable fd2 para añadir las llamadas send() y recv(). Es el descriptor del socket por el cual se leerán datos. send() devuelve -1 en caso de error.

Lo mismo que para recv() from.h> close(fd). Es un puntero a un entero local que debería ser inicializado a sizeof(struct sockaddr). La función close() es usada para cerrar la conexión de nuestro descriptor de socket. Análogamente a send(). Lo mismo que para recv() len. recvfrom() devuelve el número de bytes recibidos. Lo mismo que para recv() buf.h> int recvfrom(int fd. int *fromlen).void *buf.14 Al igual de lo que se dijo para send(). o -1 si se produjo un error. fromlen. shutdown() #include <sys/socket. Lo mismo que para recv() flags. recv() devuelve el número de bytes leídos en el búfer.h> int shutdown(int fd. close() #include <unistd. unsigned int flags struct sockaddr *from. Si se deseara enviar. int len. o -1 en caso de error. . Veamos los argumentos:       fd. int how). Análogamente a lo que pasaba con recv(). recibir datos usando sockets desconectados de Datagramas. esta función es usada con datos en sockets de flujo o sockets conectados de datagramas. Si llamamos a close() no se podrá escribir o leer usando ese socket. recvfrom() #include <sys/types.h> #include <sys/socket. o en este caso. se debe usar la llamada recvfrom(). Es un puntero a la estructura sockaddr. y si alguien trata de hacerlo recibirá un mensaje de error.

shutdown() devolverá 0 si todo ocurre bien. BIBLIOGRAFÍA  Unix Networking Programming. Sólo se podrá establecer uno de estos nombres: o 0. Stevens  Comunicaciones en Unix. Es lo mismo llamar a close() que establecer how a 2. Es un puntero a un array que contiene el nombre del nodo actual.  Material en Internet . R. gethostname() #include <unistd. size. Jean Marie Riflet  Ayuda en línea Sistema Operacional Linux. Veamos de qué se tratan los argumentos:   hostname. Es el fichero descritor del socket al que queremos aplicar esta llamada. size_t size).15 Veamos los argumentos:   fd. Prohibido recibir y enviar. o 2. La función gethostname() es usada para obtener el nombre de la máquina local. o 1. La longitud del array que contiene al nombre del nodo (en bytes). Prohibido enviar. o -1 en caso de error.h> int gethostname(char *hostname. how. Prohibido recibir.