Socket Services
Overview
The socket service API can be used to install a handler that is called if there is data received on a socket. The API helps to avoid creating a dedicated thread for each TCP or UDP service that the application provides. Instead one thread is created that serves data to multiple listening sockets which saves memory as only one thread needs to be created in the system in this case.
See Echo server (service) sample application to learn how to create a simple BSD socket based server application using the sockets service API. The source code for this sample application can be found at: samples/net/sockets/echo_service.
API Description
Socket service API is enabled using CONFIG_NET_SOCKETS_SERVICE
config option and implements the following operations:
NET_SOCKET_SERVICE_SYNC_DEFINE
Define a network socket service. This socket service is created with extern scope so it can be used from multiple C source files.
NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC
Define a network socket service with static scope. This socket service can only be used within one C source file.
-
Register pollable sockets for this service. User must create the sockets before this call.
net_socket_service_unregister()
Remove pollable sockets for this service. User can close the sockets after this call.
-
User specified callback which is called when data is received on the listening socket.
Application Overview
If the socket service API is enabled, an application must create the service like this:
#define MAX_BUF_LEN 1500
#define MAX_SERVICES 1
static void udp_service_handler(struct net_socket_service_event *pev)
{
struct pollfd *pfd = &pev->event;
int client = pfd->fd;
struct sockaddr_in6 addr;
socklen_t addrlen = sizeof(addr);
/* In this example we use one static buffer in order to avoid
* having a large stack.
*/
static char buf[MAX_BUF_LEN];
len = recvfrom(client, buf, sizeof(buf), 0,
(struct sockaddr *)&addr, &addrlen);
if (len <= 0) {
/* Error */
...
return;
}
/* Do something with the received data. The pev variable contains
* user data that was stored in the socket service when it was
* registered.
*/
}
NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC(service_udp, udp_service_handler, MAX_SERVICES);
The application needs to create the sockets, then register them to the socket service after which the socket service thread will start to call the callback for any incoming data.
/* Create one or multiple sockets */
struct pollfd sockfd_udp[1] = { 0 };
int sock, ret;
sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) {
LOG_ERR("socket: %d", -errno);
return -errno;
}
/* Set possible socket options after creation */
...
/* Then bind the socket to local address */
if (bind(sock, (struct sockaddr *)addr, sizeof(*addr)) < 0) {
LOG_ERR("bind: %d", -errno);
return -errno;
}
/* Set the polled sockets */
sockfd_udp[0].fd = sock;
sockfd_udp[0].events = POLLIN;
/* Register UDP socket to service handler */
ret = net_socket_service_register(&service_udp, sockfd_udp,
ARRAY_SIZE(sockfd_udp), NULL);
if (ret < 0) {
LOG_ERR("Cannot register socket service handler (%d)", ret);
return ret;
}
/* Application logic happens here. When application is ready to
* quit, one should unregister the socket service and close the
* socket.
*/
(void)net_socket_service_unregister(&service_udp);
close(sock);
The TCP socket needs slightly different logic as we need to add any
accepted socket to the listening socket by calling the
net_socket_service_register()
for the accepted socket.
struct sockaddr_in6 client_addr;
socklen_t client_addr_len = sizeof(client_addr);
struct pollfd sockfd_tcp[1] = { 0 };
int client;
/* TCP socket service is created similar way as the UDP one */
sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
LOG_ERR("socket: %d", -errno);
return -errno;
}
if (bind(sock, (struct sockaddr *)addr, sizeof(*addr)) < 0) {
LOG_ERR("bind: %d", -errno);
return -errno;
}
if (listen(sock, 5) < 0) {
LOG_ERR("listen: %d", -errno);
return -errno;
}
while (1) {
client = accept(tcp_sock, (struct sockaddr *)&client_addr,
&client_addr_len);
if (client < 0) {
LOG_ERR("accept: %d", -errno);
continue;
}
inet_ntop(client_addr.sin6_family, &client_addr.sin6_addr,
addr_str, sizeof(addr_str));
LOG_INF("Connection from %s (%d)", addr_str, client);
sockfd_tcp[0].fd = client;
sockfd_tcp[0].events = POLLIN;
/* Register all the sockets to service handler */
ret = net_socket_service_register(&service_tcp, sockfd_tcp,
ARRAY_SIZE(sockfd_tcp), NULL);
if (ret < 0) {
LOG_ERR("Cannot register socket service handler (%d)", ret);
break;
}
}
For any closed TCP client connection we need to remove the closed socket from the polled socket list.
/* If the TCP socket is closed while reading the data in the handler,
* mark it as non pollable.
*/
if (sockfd_tcp[0].fd == client) {
sockfd_tcp[0].fd = -1;
/* Update the handler so that client connection is
* not monitored any more.
*/
(void)net_socket_service_register(&service_tcp, sockfd_tcp,
ARRAY_SIZE(sockfd_tcp), NULL);
close(client);
LOG_INF("Connection from %s closed", addr_str);
}
Please see a more complete example in the echo_service
sample source
code at samples/net/sockets/echo_service/src/main.c.