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:
parent
6ea6326e79
commit
f4373709e7
11
samples/net/sockets/echo_service/CMakeLists.txt
Normal file
11
samples/net/sockets/echo_service/CMakeLists.txt
Normal 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)
|
51
samples/net/sockets/echo_service/README.rst
Normal file
51
samples/net/sockets/echo_service/README.rst
Normal 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.
|
6
samples/net/sockets/echo_service/overlay-e1000.conf
Normal file
6
samples/net/sockets/echo_service/overlay-e1000.conf
Normal 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
|
38
samples/net/sockets/echo_service/prj.conf
Normal file
38
samples/net/sockets/echo_service/prj.conf
Normal 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
|
16
samples/net/sockets/echo_service/sample.yaml
Normal file
16
samples/net/sockets/echo_service/sample.yaml
Normal 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
|
255
samples/net/sockets/echo_service/src/main.c
Normal file
255
samples/net/sockets/echo_service/src/main.c
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue