samples: net: sockets: Add echo-service sample

The echo-service sample demostrates how to use the socket
service API.

Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
This commit is contained in:
Jukka Rissanen 2023-12-21 16:56:39 +02:00 committed by Carles Cufí
parent 6ea6326e79
commit f4373709e7
6 changed files with 377 additions and 0 deletions

View file

@ -0,0 +1,11 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(sockets_service_echo)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
include(${ZEPHYR_BASE}/samples/net/common/common.cmake)

View file

@ -0,0 +1,51 @@
.. zephyr:code-sample:: sockets-service-echo
:name: Echo server (service)
:relevant-api: bsd_sockets
Implements a simple IPv4/IPv6 TCP echo server using BSD sockets and socket service API.
Overview
********
The sockets/echo_service sample application for Zephyr implements a TCP echo
server supporting both IPv4 and IPv6 and using a BSD Sockets compatible API.
The purpose of this sample is to show how to use socket service API.
The socket service is a concept where many blocking sockets can be listened by
one thread, and which can then trigger a callback if there is activity in the set
of sockets. This saves memory as only one thread needs to be created in the
system.
The application supports IPv4 and IPv6, and both UDP and TCP are also supported.
The source code for this sample application can be found at:
:zephyr_file:`samples/net/sockets/echo_service`.
Requirements
************
- :ref:`networking_with_host`
- or, a board with hardware networking
Building and Running
********************
Build the Zephyr version of the sockets/echo_service application like this:
.. zephyr-app-commands::
:zephyr-app: samples/net/sockets/echo_service
:board: <board_to_use>
:goals: build
:compact:
After the sample starts, it expects connections at 192.0.2.1, or 2001:db8::1
and port 4242.
The easiest way to connect is:
.. code-block:: console
$ telnet 192.0.2.1 4242
After a connection is made, the application will echo back any line sent
to it. The application implements a single-threaded server using blocking
sockets, and currently is only implemented to serve only one client connection
at time. After the current client disconnects, the next connection can proceed.

View file

@ -0,0 +1,6 @@
# Overlay for experimental TCP as qemu_x86 with E1000
CONFIG_PCIE=y
CONFIG_NET_L2_ETHERNET=y
CONFIG_NET_QEMU_ETHERNET=y

View file

@ -0,0 +1,38 @@
# General config
# The async method used in the sample needs more stack for the workqueue
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=1500
CONFIG_POSIX_API=y
# Networking config
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=y
CONFIG_NET_TCP=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y
CONFIG_NET_IPV4_MAPPING_TO_IPV6=y
CONFIG_POSIX_MAX_FDS=10
CONFIG_NET_MAX_CONN=5
CONFIG_NET_SOCKETS_SERVICE=y
CONFIG_NET_SOCKETS_POLL_MAX=20
# Network driver config
CONFIG_TEST_RANDOM_GENERATOR=y
# Network address config
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_NEED_IPV4=y
CONFIG_NET_CONFIG_NEED_IPV6=y
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
# Network buffers
CONFIG_NET_PKT_RX_COUNT=16
CONFIG_NET_PKT_TX_COUNT=16
CONFIG_NET_BUF_RX_COUNT=64
CONFIG_NET_BUF_TX_COUNT=64
CONFIG_NET_CONTEXT_NET_PKT_POOL=y
CONFIG_NET_SHELL=y

View file

@ -0,0 +1,16 @@
sample:
description: echo server using socket service API
name: socket_service_echo
common:
harness: net
depends_on: netif
filter: CONFIG_FULL_LIBC_SUPPORTED and not CONFIG_NATIVE_LIBC
# eventfd does not work properly with native_posix so exclude it here
platform_exclude:
- native_posix
- native_posix_64
tests:
sample.net.sockets.service.echo:
tags:
- net
- socket

View file

@ -0,0 +1,255 @@
/*
* Copyright (c) 2023 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_echo_server_svc_sample, LOG_LEVEL_DBG);
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/posix/unistd.h>
#include <zephyr/posix/poll.h>
#include <zephyr/posix/arpa/inet.h>
#include <zephyr/posix/sys/socket.h>
#include <zephyr/net/socket_service.h>
#define MY_PORT 4242
static char addr_str[INET6_ADDRSTRLEN];
static struct pollfd sockfd_udp[1] = {
[0] = { .fd = -1 }, /* UDP socket */
};
static struct pollfd sockfd_tcp[1] = {
[0] = { .fd = -1 }, /* TCP socket */
};
#define MAX_SERVICES 1
static void receive_data(bool is_udp, struct net_socket_service_event *pev,
char *buf, size_t buflen);
static void tcp_service_handler(struct k_work *work)
{
struct net_socket_service_event *pev =
CONTAINER_OF(work, struct net_socket_service_event, work);
static char buf[1500];
/* Note that in this application we receive / send data from
* system work queue. In proper application the socket reading and data
* sending should be done so that the system work queue is not blocked.
* It is possible to create a socket service that uses own work queue.
*/
receive_data(false, pev, buf, sizeof(buf));
}
static void udp_service_handler(struct k_work *work)
{
struct net_socket_service_event *pev =
CONTAINER_OF(work, struct net_socket_service_event, work);
static char buf[1500];
receive_data(true, pev, buf, sizeof(buf));
}
/* In this example we create two services, one with async behavior and one with
* sync one. The async is for TCP and sync is for UDP (this is just an arbitrary
* choice).
* This is an artificial example, both UDP and TCP sockets could be served by the
* same service.
*/
NET_SOCKET_SERVICE_SYNC_DEFINE_STATIC(service_udp, NULL, udp_service_handler, MAX_SERVICES);
NET_SOCKET_SERVICE_ASYNC_DEFINE_STATIC(service_tcp, NULL, tcp_service_handler, MAX_SERVICES);
static void receive_data(bool is_udp, struct net_socket_service_event *pev,
char *buf, size_t buflen)
{
struct pollfd *pfd = &pev->event;
int client = pfd->fd;
struct sockaddr_in6 addr;
socklen_t addrlen = sizeof(addr);
int len, out_len;
char *p;
len = recvfrom(client, buf, buflen, 0,
(struct sockaddr *)&addr, &addrlen);
if (len <= 0) {
if (len < 0) {
LOG_ERR("recv: %d", -errno);
}
/* If the TCP socket is closed, mark it as non pollable */
if (!is_udp && 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);
}
return;
}
p = buf;
do {
out_len = sendto(client, p, len, 0,
(struct sockaddr *)&addr, addrlen);
if (out_len < 0) {
LOG_ERR("sendto: %d", -errno);
break;
}
p += out_len;
len -= out_len;
} while (len);
}
static int setup_tcp_socket(struct sockaddr_in6 *addr)
{
socklen_t optlen = sizeof(int);
int ret, sock, opt;
sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
LOG_ERR("socket: %d", -errno);
return -errno;
}
ret = getsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, &optlen);
if (ret == 0 && opt) {
LOG_INF("IPV6_V6ONLY option is on, turning it off.");
opt = 0;
ret = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, optlen);
if (ret < 0) {
LOG_WRN("Cannot turn off IPV6_V6ONLY option");
} else {
LOG_INF("Sharing same socket between IPv6 and IPv4");
}
}
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;
}
return sock;
}
static int setup_udp_socket(struct sockaddr_in6 *addr)
{
socklen_t optlen = sizeof(int);
int ret, sock, opt;
sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) {
LOG_ERR("socket: %d", -errno);
return -errno;
}
ret = getsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, &optlen);
if (ret == 0 && opt) {
LOG_INF("IPV6_V6ONLY option is on, turning it off.");
opt = 0;
ret = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, optlen);
if (ret < 0) {
LOG_WRN("Cannot turn off IPV6_V6ONLY option");
} else {
LOG_INF("Sharing same socket between IPv6 and IPv4");
}
}
if (bind(sock, (struct sockaddr *)addr, sizeof(*addr)) < 0) {
LOG_ERR("bind: %d", -errno);
return -errno;
}
return sock;
}
int main(void)
{
int tcp_sock, udp_sock, ret;
struct sockaddr_in6 addr = {
.sin6_family = AF_INET6,
.sin6_addr = IN6ADDR_ANY_INIT,
.sin6_port = htons(MY_PORT),
};
static int counter;
tcp_sock = setup_tcp_socket(&addr);
if (tcp_sock < 0) {
return tcp_sock;
}
udp_sock = setup_udp_socket(&addr);
if (udp_sock < 0) {
return udp_sock;
}
sockfd_udp[0].fd = udp_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);
}
LOG_INF("Single-threaded TCP/UDP echo server waits "
"for a connection on port %d", MY_PORT);
while (1) {
struct sockaddr_in6 client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client;
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 #%d from %s (%d)", counter++, 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;
}
}
(void)net_socket_service_unregister(&service_tcp);
(void)net_socket_service_unregister(&service_udp);
close(tcp_sock);
close(udp_sock);
return 0;
}