cancel
Showing results for 
Search instead for 
Did you mean: 

Handling multiple TCP clients on STM32H563 with NetX Duo

STea
ST Employee

Summary

In this article, we guide the users of the Azure NetX Duo middleware to handle multiple TCP clients on the STM32H563 microcontroller as an example. 

This guide is divided into two parts:

Prerequisites

This project utilizes the following software and hardware environment: 

Handling multiple clients on the same port

We base our manipulation on the already working example found in the CubeH5Firmware example
STM32Cube\Repository\STM32Cube_FW_H5_V1.2.0\Projects\NUCLEO-H563ZI\Applications\NetXDuo\Nx_UDP_Echo_Server.
You can find attached the file app_netxduo++clients_same_port.c that you can replace in this project to test this use case.

We start from there as the basic configuration of NetX Duo can be obtained from the following article.

1. Initialization

1.1. System initialization

  • NetX Duo system: Initialize the NetX Duo system using nx_system_initialize().
  • Packet pool: Allocate memory for the packet pool and create it using nx_packet_pool_create().

1.2. IP instance creation

  • Memory allocation: Allocate memory for the IP instance.
  • IP instance: Create the IP instance using nx_ip_create().
  • Protocol enabling: Enable ARP, ICMP, TCP, and UDP protocols using nx_arp_enable(), nx_icmp_enable(), nx_tcp_enable(), and nx_udp_enable() respectively.

1.3. Thread creation

  • Main thread: Allocate memory and create the main thread using tx_thread_create().
  • TCP server thread: Allocate memory and create the TCP server thread using tx_thread_create().
  • Link thread: Allocate memory and create the Link thread using tx_thread_create().

1.4. DHCP client

  • DHCP client: Create the DHCP client using nx_dhcp_create().
  • Notification callback: Set the DHCP notification callback and start the DHCP client using nx_dhcp_start().

2. Main thread entry

2.1. IP address change notification

  • Callback registration: Register the IP address change callback using nx_ip_address_change_notify().
  • DHCP client: Start the DHCP client and wait for an IP address using a semaphore.

2.2. Start TCP server thread

  • Start thread: Once the IP address is obtained, start the TCP server thread using tx_thread_resume().

3. TCP server thread entry

3.1. Socket creation

  • Create sockets: Create multiple TCP sockets using nx_tcp_socket_create().

3.2. Start listening

  • Listen on socket: Start listening on the first socket using nx_tcp_server_socket_listen().

3.3. Handle incoming connections

  • Loop: Use a loop to handle incoming connections and data reception.
  • Callbacks: Use callbacks for connection, data reception, and disconnection.

4. TCP callbacks

4.1. Listen callback

  • Accept connection: Accept incoming connections using nx_tcp_server_socket_accept().
  • Unlisten port: Unlisten the port and set the receive notify callback using nx_tcp_socket_receive_notify().

4.2. Receive callback

  • Retrieve packet: Retrieve the received packet using nx_tcp_socket_receive().
  • Echo packet: Optionally, send the packet back (echo) or process it as needed.

4.3. Disconnect callback

  • Unaccept socket: Unaccept the socket and start listening again if all sockets are busy.

5. Link thread entry

  • Check status: Continuously check the physical link status using nx_ip_interface_status_check().
  • Handle events: Handle link down and link up events appropriately.

5.2. Example code

/* TCP server thread entry */
static VOID tcp_thread_entry(ULONG thread_input)
{
    UINT status;

    /* Create all sockets */
    for (INT i = 0; i < APP_TCP_SOCKET_NUM; i++)
    {
        status = nx_tcp_socket_create(&NetXDuoEthIpInstance, g_tcp_sck + i, "TCP Socket",
                                      NX_IP_NORMAL, NX_FRAGMENT_OKAY,
                                      NX_IP_TIME_TO_LIVE, 512, NX_NULL,
                                      g_tcp_sck_disconn_cb);
        if (NX_SUCCESS != status)
        {
            __BKPT(0);
        }
    }

    /* Start listening on the first socket */
    status = nx_tcp_server_socket_listen(&NetXDuoEthIpInstance, APP_TCP_PORT, g_tcp_sck, 0,
                                         g_tcp_sck_listen_cb);
    if (NX_SUCCESS != status)
    {
        __BKPT(0);
    }

    while (1)
    {
        /* Receive pointers to TCP socket and network packet from TCP callback */
        ULONG msg[2];
        status = tx_queue_receive(&g_tcp_q, msg, TX_WAIT_FOREVER);

        if (TX_SUCCESS == status)
        {
            NX_TCP_SOCKET * p_sck    = (NX_TCP_SOCKET *) msg[0];
            NX_PACKET     * p_packet = (NX_PACKET *)     msg[1];

            status = nx_tcp_socket_send(p_sck, p_packet, NX_NO_WAIT);
            if (NX_SUCCESS != status)
            {
                nx_packet_release(p_packet);
            }
        }
        else
        {
            /* Unrecoverable error */
            tx_thread_suspend(tx_thread_identify());
        }
    }
}

/* TCP listen callback */
static void g_tcp_sck_listen_cb(NX_TCP_SOCKET * p_sck, UINT port)
{
    /* Incoming connection, accept and queueing new requests */
    nx_tcp_server_socket_accept(p_sck, NX_NO_WAIT);
    nx_tcp_server_socket_unlisten(&NetXDuoEthIpInstance, port);
    nx_tcp_socket_receive_notify(p_sck, g_tcp_sck_receive_cb);

    /* Attempt to find another idle socket to start listening on */
    ULONG state = 0;

    for (INT i = 0; i < APP_TCP_SOCKET_NUM; i++)
    {
        /* Get socket state value */
        nx_tcp_socket_info_get(g_tcp_sck + i, 0, 0, 0, 0, 0, 0, 0, &state, 0, 0, 0);

        /* Start listening if socket is idle */
        if (NX_TCP_CLOSED == state)
        {
            nx_tcp_server_socket_listen(&NetXDuoEthIpInstance, port, g_tcp_sck + i, 0,
                                        g_tcp_sck_listen_cb);
            break;
        }
    }

    /* Ran out of sockets, set appropriate flag to let next socket to
     * disconnect know that it should start listening right away. */
    if (NX_TCP_CLOSED != state)
    {
        g_not_listening = TX_TRUE;
    }
}

/* TCP receive callback */
static void g_tcp_sck_receive_cb(NX_TCP_SOCKET * p_sck)
{
    NX_PACKET * p_packet;

    /* This callback is invoked when data is already received. Retrieving
     * packet with no suspension. */
    nx_tcp_socket_receive(p_sck, &p_packet, NX_NO_WAIT);

    /* Send packet back on the same TCP socket */
    nx_tcp_socket_send(p_sck, p_packet, NX_NO_WAIT);
}

/* TCP disconnect callback */
static void g_tcp_sck_disconn_cb(NX_TCP_SOCKET * p_sck)
{
    nx_tcp_server_socket_unaccept(p_sck);

    /* If all sockets are busy, start listening again */
    if (TX_TRUE == g_not_listening)
    {
        nx_tcp_server_socket_listen(&NetXDuoEthIpInstance, APP_TCP_PORT, p_sck, 0,
                                    g_tcp_sck_listen_cb);
        g_not_listening = TX_FALSE;
    }
}

6. Testing the application

We can now test our application by generating multiple packets on the same port (5000).
STea_6-1720537723656.png

We can see that we are receiving incoming connections on the same port. 

STea_0-1720535182402.png

 


Handling multiple clients on separate ports

You can find attached the file app_netxduo++clients_separate_port.c that you can replace in this project to test this use case.

2. MX_NetXDuo_Init

2.1. Create TCP server threads

  • Create threads: Create two separate threads with the same handler to handle each connection separately.
  /* Allocate the memory for TCP server thread   */
  if (tx_byte_allocate(byte_pool, (VOID **) &pointer,2 *  DEFAULT_MEMORY_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

  /* create the TCP server thread */
  ret = tx_thread_create(&AppTCPThread, "App TCP Thread", App_TCP_Thread_Entry, 0, pointer, 2 * DEFAULT_MEMORY_SIZE,
                         DEFAULT_PRIORITY, DEFAULT_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START);
  
    if (tx_byte_allocate(byte_pool, (VOID **) &pointer,2 *  DEFAULT_MEMORY_SIZE, TX_NO_WAIT) != TX_SUCCESS)
  {
    return TX_POOL_ERROR;
  }

    ret = tx_thread_create(&AppTCPThread2, "App TCP2 Thread", App_TCP_Thread_Entry, 1, pointer, 2 * DEFAULT_MEMORY_SIZE,
                         DEFAULT_PRIORITY+1, DEFAULT_PRIORITY+1, TX_NO_TIME_SLICE, TX_DONT_START);

3. TCP server thread entry

3.1. Socket creation

  • Create sockets: Create TCP sockets for each port using nx_tcp_socket_create().

3.2. Start listening

  • Listen on sockets: Start listening on each port using nx_tcp_server_socket_listen().

3.3. Handle Incoming Connections

  • Loop: Use a loop to handle incoming connections and data reception.
  • Callbacks: Use callbacks for connection, data reception, and disconnection.

4. TCP callbacks

4.1. Listen callback

  • Accept connection: Accept incoming connections using nx_tcp_server_socket_accept().
  • Unlisten port: Unlisten the port and set the callback using nx_tcp_socket_receive_notify().

4.2. Receive callback

  • Retrieve packet: Retrieve the received packet using nx_tcp_socket_receive().
  • Echo packet: Optionally, send the packet back (echo) or process it as needed.

4.3. Disconnect callback

  • Unaccept socket: Unaccept the socket and start listening again if all sockets are busy.

5. Link thread entry

  • Check Status: Continuously check the physical link status using nx_ip_interface_status_check().
  • Handle Events: Handle link down and link up events appropriately.

6. Example code

/* TCP server thread entry */
static VOID App_TCP_Thread_Entry(ULONG thread_input)
{
  UINT ret;
  UCHAR data_buffer[512];
  UINT ti = (UINT)thread_input; // thread index

  ULONG source_ip_address;
  NX_PACKET *data_packet;

  UINT source_port;
  ULONG bytes_read;

  /* create the TCP socket */
  ret = nx_tcp_socket_create(&NetXDuoEthIpInstance, &TCPSocket[ti], "TCP Server Socket", NX_IP_NORMAL, NX_FRAGMENT_OKAY,
                             NX_IP_TIME_TO_LIVE, WINDOW_SIZE, NX_NULL, NX_NULL);
  if (ret)
  {
    Error_Handler();
  }

  /*
  * listen to new client connections.
  * the TCP_listen_callback will release the 'Semaphore' when a new connection is available
  */
  ret = nx_tcp_server_socket_listen(&NetXDuoEthIpInstance, server_ports[ti], &TCPSocket[ti], MAX_TCP_CLIENTS, tcp_listen_callback);

  if (ret)
  {
    Error_Handler();
  }
  else
  {
    printf("TCP Server listening on PORT %d ..\r\n", server_ports[ti]);
  }

  if(tx_semaphore_get(&DHCPSemaphore, TX_WAIT_FOREVER) != TX_SUCCESS)
  {
    Error_Handler();
  }
  else
  {
    /* accept the new client connection before starting data exchange */
    ret = nx_tcp_server_socket_accept(&TCPSocket[ti], TX_WAIT_FOREVER);

    if (ret)
    {
      Error_Handler();
    }
  }

  while(1)
  {
    ULONG socket_state;

    TX_MEMSET(data_buffer, '\0', sizeof(data_buffer));

    /* get the socket state */
    nx_tcp_socket_info_get(&TCPSocket[ti], NULL, NULL, NULL, NULL, NULL, NULL, NULL, &socket_state, NULL, NULL, NULL);

    /* if the connections is not established then accept new ones, otherwise start receiving data */
    if(socket_state != NX_TCP_ESTABLISHED)
    {
      ret = nx_tcp_server_socket_accept(&TCPSocket[ti], NX_IP_PERIODIC_RATE);
    }

    if(ret == NX_SUCCESS)
    {
      /* receive the TCP packet send by the client */
      ret = nx_tcp_socket_receive(&TCPSocket[ti], &data_packet, NX_WAIT_FOREVER);

      if (ret == NX_SUCCESS)
      {
        HAL_GPIO_TogglePin(GPIOI, GPIO_PIN_9);

        /* get the client IP address and  port */
        nx_udp_source_extract(data_packet, &source_ip_address, &source_port);

        /* retrieve the data sent by the client */
        nx_packet_data_retrieve(data_packet, data_buffer, &bytes_read);

        /* print the received data */
        PRINT_DATA(source_ip_address, source_port, data_buffer);

        /* immediately resend the same packet */
        ret =  nx_tcp_socket_send(&TCPSocket[ti], data_packet, NX_IP_PERIODIC_RATE);

        if (ret == NX_SUCCESS)
        {
          HAL_GPIO_TogglePin(GPIOI, GPIO_PIN_9);
        }
      }
      else
      {
        nx_tcp_socket_disconnect(&TCPSocket[ti], NX_WAIT_FOREVER);
        nx_tcp_server_socket_unaccept(&TCPSocket[ti]);
        nx_tcp_server_socket_relisten(&NetXDuoEthIpInstance, server_ports[ti], &TCPSocket[ti]);
      }
    }
    else
    {
      /*toggle the green led to indicate the idle state */
      HAL_GPIO_TogglePin(GPIOI, GPIO_PIN_9);
    }
  }
}

7. Testing the application

To test the application, we use a packet sender to generate two packets on different ports (5000,5001).

STea_5-1720537622676.png
STea_4-1720537595677.png

We can see in this trace that the server running on the STM32H5 is receiving both messages from different ports.

STea_1-1720535322971.png

Conclusion

By following this guide, you should be able to handle multiple clients on the same port or on separate ports using the STM32H5 microcontroller and the NetX Duo library. The key steps involve initializing the system, creating and managing threads, and handling TCP connections using callbacks.

Version history
Last update:
‎2024-08-06 02:20 AM
Updated by: