Sockets
Los sockets establecen un canal de comunicación entre dos procesos remotos a través de Internet.
Funciones
Socket* | bsocket_connect (...) |
Socket* | bsocket_server (...) |
Socket* | bsocket_accept (...) |
void | bsocket_close (...) |
void | bsocket_local_ip (...) |
void | bsocket_remote_ip (...) |
void | bsocket_read_timeout (...) |
void | bsocket_write_timeout (...) |
bool_t | bsocket_read (...) |
bool_t | bsocket_write (...) |
uint32_t | bsocket_url_ip (...) |
uint32_t | bsocket_str_ip (...) |
const char_t* | bsocket_host_name (...) |
const char_t* | bsocket_host_name_ip (...) |
const char_t* | bsocket_ip_str (...) |
void | bsocket_hton2 (...) |
void | bsocket_hton4 (...) |
void | bsocket_hton8 (...) |
void | bsocket_ntoh2 (...) |
void | bsocket_ntoh4 (...) |
void | bsocket_ntoh8 (...) |
Podemos definir un socket como un canal de comunicación entre dos procesos que están ejecutándose en diferentes máquinas. Utilizan como base la familia de protocolos TCP/IP que rigen la comunicación por Internet desde los primeros prototipos de la gran red allá por el año 1969. Por su parte, el protocolo IP (Internet Protocol) se encarga del envío de pequeños paquetes de datos entre dos computadores remotos a través de la red. Como hay paquetes que pueden perderse o tomar diferentes caminos al atravesar los nodos de Internet, TCP (Transmission Control Protocol) se encargará de ordenarlos secuencialmente y volver a pedir aquellos que se hayan perdido. Otro aspecto importante que añade TCP es el concepto de puerto lo que permite que una misma máquina disponga de múltiples conexiones abiertas al mismo tiempo. La conjunción de TCP/IP provee al proceso de un canal fiable de comunicación bidireccional (full-duplex) con el proceso remoto y es la base del modelo cliente/servidor (Figura 1).
- Utiliza bsocket_connect en el proceso cliente para crear un canal de comunicación con un servidor remoto.
- Utiliza bsocket_server en el proceso servidor para quedar a la escucha de peticiones de clientes.
- Utiliza bsocket_accept para aceptar la petición de un cliente y empezar la comunicación.
- Utiliza bsocket_read para leer datos de un socket.
- Utiliza bsocket_write para escribir datos en un socket.
Los sockets son la primitiva de comunicación de más bajo nivel accesible por las aplicaciones. Son extremadamente rápidos pero, por lo general, sus funciones son bloqueantes, es decir, detendrán al proceso hasta que el otro interlocutor responda.
- bsocket_connect detendrá al proceso cliente hasta que el servidor responda o se cumpla el
timeout
. - bsocket_accept detendrá al proceso servidor hasta que llegue una petición de un cliente o se cumpla el
timeout
. - bsocket_read detendrá al proceso hasta que el otro interlocutor escriba datos en el canal o se cumpla el
timeout
. - bsocket_write detendrá al proceso hasta que el otro interlocutor lea datos del canal y libere el búfer intermedio o se cumpla el
timeout
.
Al margen de estas indicaciones, trabajar con sockets es muy parecido a hacerlo con archivos en disco. La implementación de TCP/IP es complicada y forma parte del sistema operativo, por lo que el establecimiento de la conexión se ha simplificado a través de las llamadas al sistema vistas anteriormente. Ya que un socket tan solo permite enviar y recibir bytes, ambos interlocutores necesitan definir un protocolo que indique el orden, secuencia y tipo de datos a compartir de tal forma que la comunicación sea satisfactoria y libre de interbloqueos. Algunos de los protocolos más utilizados en Internet son: HTTP, SMTP, FTP, SSH, etc.
1. Ejemplo Cliente/Servidor
Como ejemplo vamos a ver como dos procesos intercambian información mediante sockets. El protocolo es extremadamente sencillo. Tras la conexión el cliente (Listado 2) enviará una serie de valores numéricos al servidor (Listado 1) y este le responderá reenviando el mismo valor. Cuando el cliente envíe el valor UINT32_MAX
la comunicación terminará.
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
uint32_t client_id = 0; Socket *server_sock = bsocket_server(3444, 32, NULL); if (server_sock == NULL) return; for(;;) { Socket *income_sock = NULL; uint32_t ip0, ip1; uint16_t p0, p1; bstd_printf("Waiting for a new client\n"); income_sock = bsocket_accept(server_sock, 0, NULL); if (income_sock == NULL) continue; bstd_printf("Client %d arrives\n", client_id); bsocket_local_ip(income_sock, &ip0, &p0); bsocket_remote_ip(income_sock, &ip1, &p1); bstd_printf("Local IP: %s:%d\n", bsocket_ip_str(ip0), p0); bstd_printf("Remote IP: %s:%d\n", bsocket_ip_str(ip1), p1); for (;;) { byte_t data[4]; uint32_t rsize; if (bsocket_read(income_sock, data, sizeof(data), &rsize, NULL) == TRUE) { uint32_t i; bsocket_ntoh4((byte_t*)&i, data); if (i != UINT32_MAX) { bstd_printf("Readed %d from client\n", i); bsocket_hton4(data, (byte_t*)&i); if (bsocket_write(income_sock, data, sizeof(data), NULL, NULL) == TRUE) { bstd_printf("Sending %d to client\n", i); } else { bstd_printf("Error writting to client\n"); break; } } else { bstd_printf("Client %d say bye!\n", client_id); break; } } else { bstd_printf("Error reading from client\n"); break; } } bstd_printf("\n\n"); bsocket_close(&income_sock); client_id += 1; } bsocket_close(&server_sock); |
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
Socket *sock = NULL; serror_t error; uint32_t i = 0; byte_t data[4]; sock = bsocket_connect(bsocket_str_ip("192.168.1.21"), 3444, 5000, &error); if (sock == NULL) { bstd_printf("Connection error\n"); return; } bsocket_read_timeout(sock, 2000); bsocket_write_timeout(sock, 5000); while (i < kPING_COUNTER) { bsocket_hton4(data, (const byte_t*)&i); if (bsocket_write(sock, data, sizeof(data), NULL, NULL) == TRUE) { bstd_printf("Sending %d to server\n", i); } else { bstd_printf("Error writting in socket\n"); break; } if (bsocket_read(sock, data, sizeof(data), NULL, NULL) == TRUE) { uint32_t j; bsocket_ntoh4((byte_t*)&j, data); bstd_printf("Readed %d from server\n", j); if (j != i) { bstd_printf("Error data corruption\n"); break; } i += 1; } else { bstd_printf("Error reading in socket\n"); break; } } if (i == kPING_COUNTER) { i = UINT32_MAX; bsocket_hton4(data, (const byte_t*)&i); if (bsocket_write(sock, data, sizeof(data), NULL, NULL) == TRUE) { bstd_printf("Sending FINISH to server\n"); } else { bstd_printf("Error writting in socket\n"); } } bsocket_close(&sock); |
bsocket_connect ()
Crea un socket cliente e intenta establecer una conexión con un servidor remoto.
Socket* bsocket_connect(const uint32_t ip, const uint16_t port, const uint32_t timeout_ms, serror_t *error);
ip | La dirección IPv4 32-bit del host remoto. bsocket_str_ip. |
port | El puerto de conexión. |
timeout_ms | Número máximo de milisegundos que esperará para establecer conexión. Si es |
error | Código de error si la función falla. Puede ser |
Retorna
Manejador del socket, o NULL
si la función falla.
Observaciones
El proceso se bloqueará hasta que se obtenga respuesta desde el servidor o se cumpla el timeout
. Ver Ejemplo Cliente/Servidor.
bsocket_server ()
Crea un socket servidor.
Socket* bsocket_server(const uint16_t port, const uint32_t max_connect, serror_t *error);
port | El puerto donde "escuchará" el servidor. |
max_connect | El número máximo de conexiones que puede mantener en cola. |
error | Código de error si la función falla. Puede ser |
Retorna
Manejador del socket, o NULL
si la función falla.
Observaciones
Las peticiones de los clientes se irán almacenando en una cola hasta que se reciba una llamada a bsocket_accept. Ver Ejemplo Cliente/Servidor.
bsocket_accept ()
Acepta una conexión al servidor creado con bsocket_server e inicia la conversación con el cliente.
Socket* bsocket_accept(Socket *socket, const uint32_t timeout_ms, serror_t *error);
socket | Manejador devuelto por bsocket_server. |
timeout_ms | Número máximo de milisegundos que esperará para recibir la petición. Si es |
error | Código de error si la función falla. Puede ser |
Retorna
Manejador del socket, o NULL
si la función falla.
Observaciones
El proceso se bloqueará hasta que se obtenga una petición por parte de un cliente o se cumpla el timeout
. Ver Ejemplo Cliente/Servidor.
bsocket_close ()
Cierra un socket previamente creado con bsocket_connect, bsocket_server o bsocket_accept.
void bsocket_close(Socket **socket);
socket | El manejador del socket. Será puesto a |
bsocket_local_ip ()
Obtiene la dirección ip y el puerto local asociado al socket.
void bsocket_local_ip(Socket *socket, uint32_t *ip, uint16_t *port);
socket | Manejador del socket. |
ip | Dirección IP local. |
port | Puerto IP local. |
bsocket_remote_ip ()
Obtiene la dirección ip y el puerto remoto asociado al otro interlocutor de la conexión.
void bsocket_remote_ip(Socket *socket, uint32_t *ip, uint16_t *port);
socket | Manejador del socket. |
ip | Dirección IP remota. |
port | Puerto IP remoto. |
bsocket_read_timeout ()
Establece el tiempo máximo de espera de la función bsocket_read.
void bsocket_read_timeout(Socket *socket, const uint32_t timeout_ms);
socket | Manejador del socket. |
timeout_ms | Número máximo de milisegundos que esperará hasta que el interlocutor escriba datos en el canal. Si es |
bsocket_write_timeout ()
Establece el tiempo máximo de espera de la función bsocket_write.
void bsocket_write_timeout(Socket *socket, const uint32_t timeout_ms);
socket | Manejador del socket. |
timeout_ms | Número máximo de milisegundos que esperará hasta que el interlocutor lea los datos y desbloqueé en el canal. Si es |
bsocket_read ()
Lee datos desde el socket.
bool_t bsocket_read(Socket *socket, byte_t *data, const uint32_t size, uint32_t *rsize, serror_t *error);
socket | Manejador del socket. |
data | Búfer donde se escribirán los datos leídos. |
size | El número de bytes máximos a leer (tamaño del búfer). |
rsize | Recibe el número de bytes leídos realmente. Puede ser |
error | Código de error si la función falla. Puede ser |
Retorna
TRUE
si se han leído datos. FALSE
si ha ocurrido algún error.
Observaciones
El proceso se bloqueará hasta que el interlocutor escriba datos en el canal o venza el timeout. Ver bsocket_read_timeout.
bsocket_write ()
Escribe datos en el socket.
bool_t bsocket_write(Socket *socket, const byte_t *data, const uint32_t size, uint32_t *wsize, serror_t *error);
socket | Manejador del socket. |
data | Búfer que contiene los datos a escribir. |
size | El número de bytes a escribir. |
wsize | Recibe el número de bytes escritos realmente. Puede ser |
error | Código de error si la función falla. Puede ser |
Retorna
TRUE
si se han escrito datos. FALSE
si ha ocurrido algún error.
Observaciones
El proceso se bloqueará si el canal está lleno hasta que el interlocutor lea los datos y desbloqueé o venza el timeout. Ver bsocket_write_timeout.
bsocket_url_ip ()
Obtiene la dirección IPv4 de un host a partir de su url.
uint32_t bsocket_url_ip(const char_t *url, serror_t *error);
1 2 3 4 5 6 |
uint32_t ip = bsocket_url_ip("www.google.com", NULL); if (ip != 0) { Socket *sock = bsocket_connect(ip, 80, NULL); ... } |
url | La url del host p.e, |
error | Código de error si la función falla. Puede ser |
Retorna
Valor de la dirección IPv4 del host o 0
si ha habido algún error.
bsocket_str_ip ()
Obtiene la dirección IPv4 a partir de una cadena tipo "192.168.1.1"
.
uint32_t bsocket_str_ip(const char_t *ip);
1 2 3 4 |
uint32_t ip = bsocket_str_ip("192.168.1.1"); Socket *sock = bsocket_connect(ip, 80, NULL); ... } |
ip | La cadena con la IP. |
Retorna
Valor de la dirección IPv4 en formato binario 32bits.
bsocket_host_name ()
Obtiene el nombre del host.
const char_t* bsocket_host_name(char_t *buffer, const uint32_t size);
buffer | Buffer para almacenar el nombre. |
size | Tamaño de |
Retorna
Puntero a la cadena buffer
.
bsocket_host_name_ip ()
Obtiene el nombre del host a través de su IP.
const char_t* bsocket_host_name_ip(uint32_t ip, char_t *buffer, const uint32_t size);
ip | Valor de la dirección IPv4 en formato binario 32bits. |
buffer | Buffer para almacenar el nombre. |
size | Tamaño de |
Retorna
Puntero a la cadena buffer
.
bsocket_ip_str ()
Obtiene la dirección IP en formato cadena de texto.
const char_t* bsocket_ip_str(uint32_t ip, const char_t *ip);
ip | Valor de la dirección IPv4 en formato binario 32bits. |
ip | La cadena con la IP. |
Retorna
Cadena tipo "192.168.1.1".
Observaciones
La cadena se devuelve en un búfer interno que será sobrescrito en la siguiente llamada. Hacer una copia de la cadena si necesitamos que sea persistente.
bsocket_hton2 ()
Cambia el "endianness" de un valor de 16bits previamente a ser enviado por el socket Host-to-Network.
void bsocket_hton2(byte_t *dest, const byte_t *src);
1 2 3 4 |
uint16_t value = 45321; byte_t dest[2]; bsocket_hton2(dest, (const byte_t*)&value); bsocket_write(sock, dest, 2, NULL, NULL); |
dest | Búfer destino (al menos 2 bytes). |
src | Búfer (variable). |
bsocket_hton4 ()
Igual que bsocket_hton2, para valores de 4 bytes.
void bsocket_hton4(byte_t *dest, const byte_t *src);
dest | Búfer destino (al menos 4 bytes). |
src | Búfer (variable). |
bsocket_hton8 ()
Igual que bsocket_hton2, para valores de 8 bytes.
void bsocket_hton8(byte_t *dest, const byte_t *src);
dest | Búfer destino (al menos 8 bytes). |
src | Búfer (variable). |
bsocket_ntoh2 ()
Cambia el "endianness" de un valor de 16bits tras ser recibido por el socket Network-to-Host.
void bsocket_ntoh2(byte_t *dest, const byte_t *src);
1 2 3 4 5 |
byte_t src[2]; uint16_t value; bsocket_read(sock, src, 2, NULL, NULL); bsocket_ntoh2((byte_t*)&value, src); // value = 45321 |
dest | Búfer (variable) destino de 16bits. |
src | Búfer recibido por el socket. |
bsocket_ntoh4 ()
Igual que bsocket_ntoh2, para valores de 4 bytes.
void bsocket_ntoh4(byte_t *dest, const byte_t *src);
dest | Búfer (variable) destino de 32bits. |
src | Búfer recibido por el socket. |
bsocket_ntoh8 ()
Igual que bsocket_ntoh2, para valores de 8 bytes.
void bsocket_ntoh8(byte_t *dest, const byte_t *src);
dest | Búfer (variable) destino de 64bits. |
src | Búfer recibido por el socket. |