BSD Sockets API

BSD Sockets API

The BSD Sockets API is the API exposed by every POSIX-compliant OS (Linux, UNIX-like, ...) with minor variations. It is implemented within the new mbed Networking libraries.

Introduction

A socket is an endpoint that can be used to send and/or receive data to/from another remote socket through a network.

TCP Sockets

TCP Sockets are used in connected mode (in a client/server fashion).

The advantage of the connected mechanism is its reliability. TCP uses an acknowledge mechanism that ensures that the data you send will be received before transmitting something else.

The server creates a socket that is bound to a specific port. This server listens on this socket and as soon as a client connects to it - using a socket itself - creates another socket that will handle the connection.

UDP Sockets

UDP Sockets are used in non-connected mode.

UDP does not implement a retransmission mechanism of lost packets, so you will get a constant data throughput be you will not be able to guarantee that the data you send actually gets somewhere.

Unicast

The initiator binds a socket to a port (that can be random or not) and sends a packet to the remote socket. The remote socket that is bound to a known port can send data back to the initiator.

Multicast

The initiator can stream data to a group of receivers. This is really useful for transmitting high bandwidth-consuming data (TV, radio).

More info on sockets

Check out Wikipedia's page on sockets.

The BSD Sockets API

The API and its companion functions were regrouped in a single header files to make it simpler. All of the following functions are regrouped in the "socket" namespace to avoid confusion with other stream-oriented libraries.

#include "api/socket.h"

Socket functions

These are described in the BSD Sockets Wikipedia page.

Companion functions

DNS Request are very useful prior to establishment of a connection.

The function gethostbyname takes a hostname as argument and returns information about this host, or NULL on failure.

struct hostent* gethostbyname(const char *name);

The hostent structure contains the following info:

struct hostent {
    char  *h_name;            /* official name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
};

The first IP address of the host is contained in h_addr_list[0].

You will then be able to populate a struct sockaddr_in object than can be passed to the socket() function.

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

This example demonstrates how to resolve an address and then connect a socket:

const char* host = "mbed.org";
uint16_t port = 80;
struct sockaddr_in serverAddr;

//Now populate structure
std::memset(&serverAddr, 0, sizeof(struct sockaddr_in));

//Resolve DNS if needed

DBG("Resolving DNS address or populate hard-coded IP address");
struct hostent *server = socket::gethostbyname(host);
if(server == NULL)
{
  return NET_NOTFOUND; //Fail
}
memcpy((char*)&serverAddr.sin_addr.s_addr, (char*)server->h_addr_list[0], server->h_length);

serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);

//Create socket
DBG("Creating socket");
sock = socket::socket(AF_INET, SOCK_STREAM, 0); //UDP socket
if (sock < 0)
{
  ERR("Could not create socket");
  return NET_OOM;
}

//Connect it
DBG("Connecting socket to %s:%d", inet_ntoa(m_serverAddr.sin_addr), ntohs(m_serverAddr.sin_port));
ret = socket::connect(m_sock, (const struct sockaddr *)&m_serverAddr, sizeof(m_serverAddr));
if (ret < 0)
{
  socket::close(m_sock);
  ERR("Could not connect");
  return NET_CONN;
}

return OK;

Timeouts

You can use two modes when calling socket-related functions: blocking (blocks indefinitely) or non-blocking (fails immediately) mode.

This gets a bit tricky if you want to specify a timeout (for instance you want to wait for a response from the server for 3 secs maximum).

In this case you can use the select() function that allows you to wait for an event (data is readable/data has been written) on socket for a specified time.

//Wait for socket to be readable
//Creating file descriptors (sockets) set
fd_set socksSet;
FD_ZERO(&socksSet);
FD_SET(sock, &socksSet); //Add "sock" socket to the list of sockets to listen on

//Setup timeout
struct timeval t_val;
t_val.tv_sec = timeout / 1000;
t_val.tv_usec = (timeout - (t_val.tv_sec * 1000)) * 1000;

//Wait for socket to be readable
int ret = socket::select(FD_SETSIZE, &socksSet, NULL, NULL, &t_val);
if(ret <= 0 || !FD_ISSET(m_sock, &socksSet))
{
  WARN("Timeout");
  return NET_TIMEOUT; //Timeout
}

Implementation

In the mbed networking library, this API relies on LwIP and is available under the socket:: namespace.


Please log in to post comments.