SDK Multiplataforma en C logo

SDK Multiplataforma en C

Sockets

❮ Anterior
Siguiente ❯

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 (...)
voidbsocket_close (...)
voidbsocket_local_ip (...)
voidbsocket_remote_ip (...)
voidbsocket_read_timeout (...)
voidbsocket_write_timeout (...)
bool_tbsocket_read (...)
bool_tbsocket_write (...)
voidbsocket_url_ip (...)
uint32_tbsocket_str_ip (...)
const char_t*bsocket_ip_str (...)
voidbsocket_hton2 (...)
voidbsocket_hton4 (...)
voidbsocket_hton8 (...)
voidbsocket_ntoh2 (...)
voidbsocket_ntoh4 (...)
voidbsocket_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.
  • Esquema que muestra dos procesos remotos conectados mediante un socket a través de Internet.
    Figura 1: Los sockets TCP/IP permiten conectar dos procesos a través de Internet.

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á.

Listado 1: Sencillo servidor basado en sockets.
 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);
Listado 2: Proceso cliente.
 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 0 se esperará indefinidamente.

error

Código de error si la función falla. Puede ser NULL.

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 NULL.

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 0 se esperará indefinidamente.

error

Código de error si la función falla. Puede ser NULL.

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 NULL tras el cierre.


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 0 se esperará indefinidamente.


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 0 se esperará indefinidamente.


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 NULL.

error

Código de error si la función falla. Puede ser NULL.

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 NULL.

error

Código de error si la función falla. Puede ser NULL.

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.

void
bsocket_url_ip(uint32_t,
               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);
    ...
}

Valor de la dirección IPv4 del host o 0 si ha habido algún error.

url

La url del host p.e, www.google.com.

error

Código de error si la función falla. Puede ser NULL.


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_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.

❮ Anterior
Siguiente ❯