Exercise 1

Using the socket API

Many of the libraries used in this course, like the MQTT library (Lesson 4) and the CoAP library (Lesson 5) don’t use the socket API directly, meaning the application needs to handle that. In this exercise, we will use the socket API to create a socket and connect to an echo server over UDP.

Exercise Steps

1. In the GitHub repository for this course, go to the base code for this exercise, found in lesson3/cellfund_less3_exer1.

2. Set the necessary networking configurations

2.1 Configure the Zephyr networking API

Enable Zephyr native networking API and disable the Zephyr native IP stack.

2.2 Enable and configure the BSD sockets compatible API

Enable the BSD socket like API on top of Zephyr’s native networking API, socket offloading (so all socket API calls are sent to the modem), and POSIX names for the socket API (to disable the _zsock prefix).

3. Include the header file for the socket API

#include <zephyr/net/socket.h>

4. Define the hostname and port for the echo server.

This is a UDP based echo server that we are running specifically for this course.

#define SERVER_HOSTNAME "nordicecho.westeurope.cloudapp.azure.com"
#define SERVER_PORT "2444"

Definition

Echo server: An echo server is a server running an application that sends back (or “echos”) all received messages. So when a client connects to an echo server and sends a message, that message will be received back.

5. Initialize variables for resolving the server address, creating the socket and receiving messages from the server.

5.1 Declare the structure for the socket used when creating the socket and the server address structure, used when connecting the socket.

static int sock;
static struct sockaddr_storage server;

As we mentioned in Socket API, using struct sockaddr_storage instead of struct sockaddr is good practice as it promotes protocol-family independence (see here for a good explanation).

5.2 Declare the receive buffer for receiving messages from the UDP echo server.

static uint8_t recv_buf[MESSAGE_SIZE];

6. In the function server_resolve(), we will resolve the IP address of the server, retrieve the relevant information and print the address to console.

6.1 Call getaddrinfo() to get the IP address of the echo server.

Create the empty structure addrinfo result and the structure addrinfo hints and specify the family – IPv4 (AF_INET) and the socket type – UDP (SOCK_DGRAM) and the Then call getaddrinfo() with the hostname, and the other parameters to get the address.

int err;
struct addrinfo *result;
struct addrinfo hints = {
	.ai_family = AF_INET,
	.ai_socktype = SOCK_DGRAM
};
	
err = getaddrinfo(SERVER_HOSTNAME, SERVER_PORT, &hints, &result);
if (err != 0) {
	LOG_INF("ERROR: getaddrinfo failed %d\n", err);
	return -EIO;
}

if (result == NULL) {
	LOG_INF("ERROR: Address not found\n");
	return -ENOENT;
} 

6.2. After getaddrinfo() is called, retrieve the relevant information from addrinfo result.

Create a pointer server4 of type struct sockaddr_in to point to server. Then set the address in server4 to point to the address from result. The family and port we already know, as AF_INET and SERVER_PORT.

struct sockaddr_in *server4 = ((struct sockaddr_in *)&server);
server4->sin_addr.s_addr =
 ((struct sockaddr_in *)result->ai_addr)->sin_addr.s_addr;
server4->sin_family = AF_INET;
server4->sin_port = ((struct sockaddr_in *)result->ai_addr)->sin_port;

6.3 Convert the address into a string and print it.

Convert the network address structure in server4->sin_addr.s_addr into a character string in ipv4_addr to print on the console.

char ipv4_addr[NET_IPV4_ADDR_LEN];
inet_ntop(AF_INET, &server4->sin_addr.s_addr, ipv4_addr,
 sizeof(ipv4_addr));
LOG_INF("IPv4 Address found %s", ipv4_addr);

6.4 Free the memory allocated for the addrinfo structure result, using freeadrinfo().

freeaddrinfo(result);

7. In the function server_connect(), create a IPv4 UDP socket.

sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) {
	LOG_INF("Failed to create socket: %d.\n", errno);
	return -errno;
}

8. Then connect the socket to the echo server, using the structure server.

err = connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr_in));
if (err < 0) {
	LOG_INF("Connect failed : %d\n", errno);
	return -errno;
}

9. Send a message every time button 1 is pressed.

In button_handler(), whenever button 1 is pressed call send() with the socket and message.

if (button_state & DK_BTN1_MSK){	
	int err = send(sock, MESSAGE_TO_SEND, SSTRLEN(MESSAGE_TO_SEND), 0);
	if (err < 0) {
		LOG_INF("Failed to send message, %d", errno);
		return;
	} LOG_INF("Successfully sent message: %s", MESSAGE_TO_SEND);
}

10. In the while-loop in main, call recv() to listen to received messages.

If recv() returns with a positive integer, and the string is null-terminated, print the received message.

received = recv(sock, recv_buf, sizeof(recv_buf) - 1, 0);

if (received < 0) {
	LOG_ERR("Socket error: %d, exit", errno);
	break;
}

if (received == 0) {
	LOG_ERR("Empty datagram");
	break;
}

recv_buf[received] = 0;
LOG_INF("Data received from the server: (%s)", recv_buf);

11. Build the exercise and flash it to the board as we have done in the previous lessons.

12. Press button 1 a few times and observe the following output.

The solution for this exercise can be found in lesson3/cellfund_less3_exer1_solution.

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

Forgot your password?
Enter your email address, and we will send a link to reset your password.