1.1.27 Client - Berkeley Sockets APIs

This interface provides a Berkeley style sockets API. While closely matching the Berkeley standard API this interface does differ due to the architecture of the WINCS02. Only asynchronous sockets are supported; no function will block.

This API supports:

  • IPaddr and IPv6 sockets.
  • Datagram (UDP) and streaming (TCP) sockets.
  • TLS for TCP sockets.
  • Server and client operations.

Socket Event Callback

A socket event callback of type WINC_SOCKET_EVENT_CALLBACK may be registered with the socket API by calling WDRV_WINC_SocketRegisterEventCallback. The purpose of this callback is to provide low level socket events to the application. It is also possible to poll the Berkeley API to ascertain status without using the callback.

Creating Sockets

Sockets are created by calling the function socket.

/* Creating IPaddr TCP socket. */
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
/* Creating IPv6 TLS socket. */
int sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TLS);
/* Creating IPaddr UDP socket. */
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

Once created the WINCS02 device will need to allocate resources.

Callback: Completion of the creation process will be signalled via the callback using event WINC_SOCKET_EVENT_OPEN.

Polled: Completion of TCP/UDP client sockets will be signalled via connect, this will return -1 for error and the errno value EINPROGRESS. TCP server listening sockets can successfully bind during completion, then listen will return -1 with errno value EAGAIN. UDP server listening sockets will return -1 from bind with a value of EAGAIN until complete.

Attempting to call another socket function before the socket creation process has completed will normally result in that call failing with errno value ENOTSOCK.

Server Listening Sockets

Once created a socket can be bound to a local endpoint to create a server listening socket. Multicast sockets are also bound to a local endpoint.

struct sockaddr_in addr;

addr.sin_family      = AF_INET;
addr.sin_port        = htons(80);
addr.sin_addr.s_addr = htonl(INADDR_ANY);

/* Bind IPv4 TCP socket to local port 80. */
bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in))
struct sockaddr_in6 addr;

addr.sin_family      = AF_INET;
addr.sin_port        = htons(80);
memcpy(&addr.sin6_addr, &in6addr_any, sizeof(in6addr_any));

/* Bind IPv6 TCP socket to local port 80. */
bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in))

If the socket supports TLS, the socket must be associated with a TLS context. The TLS API manages TLS contexts and creates context handles; the handle will need to be passed to the socket via the setsockopt function after being converted to an index value by calling WDRV_WINC_TLSCtxHandleToCfgIdx.

int tlsIndex = WDRV_WINC_TLSCtxHandleToCfgIdx(tlsHandle);

setsockopt(sockfd, IPPROTO_TLS, TLS_CONF_IDX, &tlsIndex, sizeof(tlsIndex));

TCP server listening sockets will then need to be set to a listening state by calling listen. A backlog can be specified which sets the number of simultaneous sockets supported, the maximum supported is specified by SOMAXCONN.

NOTE: The backlog value 0 means default which, to converse resources, will be a single socket.

listen(sockfd, 0);

Callback: Once a TCP socket has been configured for listening the event callback will receive the event WINC_SOCKET_EVENT_LISTEN. A UDP socket will generate this event in response to the earlier bind request.

Polled: A TCP socket will return -1 and errno value EAGAIN until the listen process has completed. A UDP socket is the same in response to the bind call.

Client Sockets

If the socket supports TLS, the socket must be associated with a TLS context. The TLS API manages TLS contexts and creates context handles; the handle will need to be passed to the socket via the setsockopt function after being converted to an index value by calling WDRV_WINC_TLSCtxHandleToCfgIdx.

int tlsIndex = WDRV_WINC_TLSCtxHandleToCfgIdx(tlsHandle);

setsockopt(sockfd, IPPROTO_TLS, TLS_CONF_IDX, &tlsIndex, sizeof(tlsIndex));

TCP sockets will then need to be connected to a remote endpoint by calling connect.

struct sockaddr_in addr;

addr.sin_family      = AF_INET;
addr.sin_port        = htons(80);
addr.sin_addr.s_addr = htonl(0xC0A80064);

/* Connect an IPv4 socket to 192.168.0.100:80. */
connect(sockfd, (struct sockaddr*)&addr, sizeof(sockaddr_in));

Multicast Sockets

Multicast sockets start like server listening sockets by being bound to a local endpoint. The socket then needs to be joined into the appropriate multicast group.

struct in_addr addr = htonl(0xE0000001U);
struct ip_mreqn group;

memcpy(&group.imr_multiaddr, &addr, sizeof(struct in_addr));
memcpy(&group.imr_address, &addr, sizeof(struct in_addr));

group.imr_ifindex = 0;

/* Join the IPv4 multicast group 224.0.0.1. */
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
struct in6_addr addr = {/* IPv6 multicast address*/}
struct ipv6_mreq group;

memcpy(&group.ipv6mr_multiaddr, &addr, sizeof(struct in6_addr));

group.ipv6mr_interface = 0;

/* Joining a IPv6 multicast. */
setsockopt(sockfd, IPPROTO_IP, IPV6_ADD_MEMBERSHIP, &group, sizeof(group));

Incoming Server Socket Connections

Callback: For TCP sockets a new incoming connection request is signalled via the callback event WINC_SOCKET_EVENT_CONNECT_REQ.

Polled: New connection requests will be signalled through the poll function. Selecting the event POLLIN on a server listening socket will result in the revent value POLLIN being signalled.

struct pollfd fd;

fd.fd     = sockh;
fd.events = POLLIN;

if (-1 == poll(&fd, 1, 0))
{
    printf("error\n");
}
else if (0 != (fd.revents & POLLIN))
{
    union sockaddr_union sockAddr;
    socklen_t sockAddrLen = sizeof(union sockaddr_union);
    int newSockh;

    newSockh = accept(sockh, (struct sockaddr*)&sockAddr, &sockAddrLen)
}

In response to a connection attempt the application may accept the connection by calling accept.

struct sockaddr_in addr;
socklen_t addrLen = sizeof(struct sockaddr_in);

/* Accept an incoming IPv4 connection request. */
new_sockfd = accept(sockfd, (struct sockaddr*)&addr, &addrLen);
struct sockaddr_in6 addr;
socklen_t addrLen = sizeof(struct sockaddr_in6);

/* Accept an incoming IPv6 connection request. */
new_sockfd = accept(sockfd, (struct sockaddr*)&addr, &addrLen);

Client Socket Connections

Callback: Completion of TCP client socket connections are indicated be reception of the WINC_SOCKET_EVENT_CONNECT event. If TLS has been configured on the socket a second event, WINC_SOCKET_EVENT_TLS_CONNECT will indicate that TLS has also connected and that data is ready to flow.

Polled: While connections are in progress connect will return -1 and errno value EINPROGRESS. Calling poll with event POLLOUT will signal the revent for POLLOUT when connected.

struct pollfd fd;

fd.fd     = sockh;
fd.events = POLLOUT;

if (-1 == poll(&fd, 1, 0))
{
    printf("error\n");
}
else if (0 != (fd.revents & POLLOUT))
{
    printf("socket connected\n");
}

Sending Data

To send data the application can call either send for streaming sockets (TCP/TLS) or sendto for datagram sockets (UDP).

/* Send data via TCP socket. */
send(sockfd, dataPtr, dataLength, 0);
struct sockaddr_in      addr

addr.sin_family      = AF_INET;
addr.sin_port        = htons(1000);
addr.sin_addr.s_addr = htonl(0xC0A80064);

/* Send data via UDP socket to 192.168.0.100:1000. */
sendto(sockfd, dataPtr, dataLength, 0, (struct sockaddr*)&addr, sizeof(struct sockaddr_in));

If the socket is available for sending the call to send or sendto will return the number of byte written. If the socket is not available the call will return -1 with errno value EWOULDBLOCK / EAGAIN.

Callback: There is an WINC_SOCKET_EVENT_SEND event delivered to the event callback, however this does not correspond directly to the calling of send or sendto. The event corresponds to the low level sending of socket data to the WINCS02. Calling send or sendto places the application data into an internal buffer within the WINCS02 driver, the driver then chooses when and how to deliver this data to the WINCS02 device.

Polled: The ability to send data through a socket will be signalled through poll event POLLOUT. When the revent for the socket signals POLLOUT the application may send data.

struct pollfd fd;

fd.fd     = sockh;
fd.events = POLLOUT;

if (-1 == poll(&fd, 1, 0))
{
    printf("error\n");
}
else if (0 != (fd.revents & POLLOUT))
{
    send(sockh, buffer, numBytes, 0);
}

Receiving Data

To receive data the application can call either recv for streaming sockets (TCP/TLS) or recvfrom for datagram sockets (UDP).

uint8_t buffer[128];

/* Receive data from a TCP socket. */
recv(sockfd, buffer, sizeof(buffer), 0);
uint8_t buffer[128];
struct sockaddr_in addr;
socklen_t fromLen = sizeof(struct sockaddr_in);

/* Receive data from a UDP socket. */
recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&addr, &fromLen);

Callback: There is an WINC_SOCKET_EVENT_RECV event delivered to the event callback, however this does not correspond directly to data being available through recv or recvfrom. The event corresponds to the low level reception of data from the WINCS02. When data is received from the WINCS02 device it is placed in an internal buffer within the WINCS02 driver, the driver then chooses when and how to deliver this data to the application.

Polled: The ability to receive data from a socket will be signalled through poll event POLLIN. When the revent for the socket signals POLLIN the application may receive data.

struct pollfd fd;

fd.fd     = sockh;
fd.events = POLLIN;

if (-1 == poll(&fd, 1, 0))
{
    printf("error\n");
}
else if (0 != (fd.revents & POLLIN))
{
    recv(sockh, buffer, numBytes, 0);
}

TCP Socket Termination

Remote socket termination can be detected when attempting to send or receive data, or when polling using poll.

recv: When a stream socket peer has performed an orderly shutdown, the return value of recv will be 0, the traditional 'end-of-file' value.

send: Attempting to send on a terminated socket will result in a return value of -1 and the errno value of ENOTCONN.

poll: The value POLLHUP will be signalled in revents when a connection is terminated. POLLIN can be used to determine if the socket has remaining data to receive.

struct pollfd fd;

fd.fd     = sockh;
fd.events = POLLIN|POLLOUT;

if (-1 == poll(&fd, 1, 0))
{
    printf("error\n");
}
else if (0 != (fd.revents & POLLHUP))
{
    printf("socket disconnected\n");

    if (0 == (fd.revents & POLLIN))
    {
        /* No pending data to receive, socket can be closed. */
    }
}

Closing Sockets

To close a socket the application should use the function shutdown.

DNS Resolution.

This API includes the function getaddrinfo which can be used to resolve hostnames. Resolved names result in a list of struct addrinfo structures which are dynamically created, therefore the application must call freeaddrinfo when finished with the results.

For IPv6 AAAA record resolution a hints structure can be included.

struct addrinfo *pAddrInfo;
int result;

result = getaddrinfo("www.example.com", NULL, NULL, &pAddrInfo);

if (0 == result)
{
    /* Result is available. */
}
else if (EAI_AGAIN == result)
{
    /* Result is not available yet, try again. */
}
else
{
    /* An error occurred. */
}

if (NULL != pAddrInfo)
{
    /* Clean up allocation results structures. */
    freeaddrinfo(pAddrInfo);
}
struct addrinfo hints;
struct addrinfo *pAddrInfo;
int result;

/* Indicate IPv6 AAAA records are required. */
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6;

result = getaddrinfo("www.example.com", NULL, &hints, &pAddrInfo);