cancel
Showing results for 
Search instead for 
Did you mean: 

How to create a server capable of handle multiple TCP clients using FreeRTOS, LwIP nectonn API and HAL libraries

genisuvi
Associate III

As title says I'm trying to create an only one TCP server in a project based on FreeRTOS, HAL libraries, LwIP middleware and netocnn API. I want to develop a server capable to attend about 5/10 clients simultanously using the same port.

I started testing the ST demo and then customizing the server thread.

But this demo works fine for one connection. As the netconn services called by this thread are thread blocking, I wonder how can multiple connection be done.

If demo is this:

1.-server thread implementation:

static void tcpecho_thread(void *arg)
{
  struct netconn *conn, *newconn;
  err_t err, accept_err;
  struct netbuf *buf;
  void *data;
  u16_t len;
 
LWIP_UNUSED_ARG(arg);
 
/* Create a new connection identifier. */
conn = netconn_new(NETCONN_TCP);
 
if (conn!=NULL)
{  
/* Bind connection to well known port number 7. */
 
err = netconn_bind(conn, NULL, 7);
 
if (err == ERR_OK)
{
  /* Tell connection to go into listening mode. */
  netconn_listen(conn);
 
  while (1) 
  {
    /* Grab new connection. */
     accept_err = netconn_accept(conn, &newconn);
 
    /* Process the new connection. */
    if (accept_err == ERR_OK) 
    {
 
      while (netconn_recv(newconn, &buf) == ERR_OK) 
      {
        do 
        {
          netbuf_data(buf, &data, &len);
          netconn_write(newconn, data, len, NETCONN_COPY);
 
        } 
        while (netbuf_next(buf) >= 0);
 
        netbuf_delete(buf);
      }
 
      /* Close connection and discard connection identifier. */
      netconn_close(newconn);
      netconn_delete(newconn);
    }
  }
}
else
{
  netconn_delete(newconn);
}
}
}
 
 
/*-----------------------------------------------------------------------------------*/
 
void tcpecho_init(void)
{
  sys_thread_new("tcpecho_thread", tcpecho_thread, NULL, DEFAULT_THREAD_STACKSIZE, TCPECHO_THREAD_PRIO );
}
/*-----------------------------------------------------------------------------------*/
 
 

2.-This is the main code where startThread is created:

/**
  * @brief  Main program
  * @param  None
  * @retval None
  */
int main(void)
{
  /* STM32F2xx HAL library initialization:
       - Configure the Flash ART accelerator on ITCM interface
       - Configure the Systick to generate an interrupt each 1 msec
       - Set NVIC Group Priority to 4
       - Global MSP (MCU Support Package) initialization
     */
  HAL_Init();  
  
  /* Configure the system clock to 120 MHz */
  SystemClock_Config(); 
 
  Tasks_InitCommon();//mine
  Tasks_InitVars();//mine
  Tasks_HwInit();//mine
 
 
  /* Init thread */
 
  osThreadDef(Start, StartThread, osPriorityNormal, 0, configMINIMAL_STACK_SIZE * 5);
  osThreadCreate (osThread(Start), NULL); 
  	  	  	  	  	  	  	  	  	  	
 
  /* Start scheduler */
  osKernelStart();
  
  /* We should never get here as control is now taken by the scheduler */
  for( ;; );
}
 
 

3.-This is startthread demo code where server thread is created:

static void StartThread(void const * argument)
{  
 
		  /* Create tcp_ip stack thread */
	  tcpip_init(NULL, NULL);
 
	  /* Initialize the LwIP stack */
 
	  Netif_Config();
 
	  /* Initialize tcp echo server */
 
	  tcpecho_init();
 
	    /* Notify user about the network interface config */
	  User_notification(&gnetif);
 
  for( ;; )
  {
    /* Delete the Init Thread */ 
    osThreadTerminate(NULL);
  }
}

How could I improve the demo in order to accept more than one connection?

I have seen one suggetion but I think it doesn't fit with my needs: someone suggests to create the same number of server threads (in this case tcpecho_init threads) than number of connection that you expected to have. But I don't need multiple servers with different application ports, I want to do an only one server handling as many uknown number of connections (up to 10) as it is having in run time. I'm not interested on having prefedefined connections (neither x num of servers running).

My hardware platform is a stm32f207ZG microcontroller.

I will appreciate any idea/suggestions/exemples, etc. Thanks for your attention.

4 REPLIES 4
Pavel A.
Evangelist III

Spawn a new thread for every accepted connection (newconn) so the main thread will keep listening.

Thansk Pavel.

It seems a better idea than the first one. That is just what I have done:

/*Here is the client thread with their actions once 
packet is received up to the answer and closing connection*/
 
static void ActiveConn_thread(void *arg)
{
    struct netconn *newconn = arg;
    struct netbuf *buf;
    void *data;
 
    u16_t len;
    uint8_t pp[20];
    uint8_t *buffer = NULL ;
    uint8_t nPin = 0;
    /* get new client connection reiceved buffer */
    while (netconn_recv(newconn, &buf) == ERR_OK) //buf = netbuf = packet buffer from netconn
    {
        do
        {
            netbuf_data(buf, &data, &len);//Get the data pointer and length of the data inside a netbuf = buf = paquet rebut.
            buffer = (uint8_t*)data;
            Length = len-1;
 
            //*get bytes
            for (uint8_t i = 0; i<=len;i++)
            {
                pp[i]=buffer[i];
 
            }
            /* actions*/
            if(pp[0] == 0xC1)
            {
 
              len = 16;
              for (uint8_t i = 0; i<len;i++)
              {
            
                  BytesRD[i] = ML_GPIO_GetState(gpio_inputs[i].tPort,gpio_inputs[i].tPin);
                  if (BytesRD[i] == 0) BytesRD[i] = 1;
                  else BytesRD[i] = 0;
              }
 
              /* answer*/
              data = (void*)BytesRD;
              netconn_write(newconn, data, len, NETCONN_COPY);
            }
 
        }
 
        while (netbuf_next(buf) >= 0);
        netbuf_delete(buf);
 
    }
 
    /* Close connection and discard connection identifier. */
    netconn_close(newconn);
    netconn_delete(newconn);
    
    /***deleting thread***/
    vTaskDelete( NULL );
}

But I'm noticing that every connection thread is getting memory from freeRtos stack and they don't free this memory after client thread is deleted. So server connection has limitations with the number of total connections capable to be created. Even if the same application client is disconnected, new connections are not possible. As a result the tcp server also stops working.

Note: when freertos RAM size is increased, more client threads can be created. But this is not an optimal solution. Everytime a connection is closed or created, a connection is exhausted, and hte number of available connections decreases.

This implies a high risk to achieve 0 new connections and make the server hangs.

Yes, this is a well known scalability problem, even in real web servers. Be creative :)

bruce_wayne_1997
Associate II

Can you please comment for a scenario where the connection to client needs to be open always and cannot be closed/deleted until user requests for it?

What I'm trying to do is:
1. Initialize Network interface -> Netif_config();

2. Initialize a netconn for STM32H7.

3. Bind the STM's netconn to port 5

4. Wait for first client to connect (netconn_accept()), then make a new thread to transfer data (netconn_write() and netconn_recv())

5. Wait for second client to connect (netconn_accept()), then make a new thread to transfer data (netconn_write() and netconn_recv())

What problem I'm facing:
The code is blocked in the second netconn_accept() function. I can see on the second client's TCP utility app that connection is either actively refused by STM or generic error.
What do I have to do?

Basically I have to netconn_write() and netconn_recv() for two different clients simultaneously.

What is the right LWIP design to do this?

Is this possible to do on the same port or do I make two different ports?

Can I use netconn_write() and netconn_recv() functions from two different threads simultaneously since I'm addressing two different clients?

 

Ethernet thread I'm using:

void StartEthernetComm(void *argument)
{
  /* USER CODE BEGIN StartEthernetComm */

	// Packet to be transmitted loaded with arbitary information
	for(uint8_t i=0;i<ETH_BUFFERSIZE;i++)
	{
		Eth_Packet[i] = 0x41;
	}

	/* Create tcp_ip stack thread */
	tcpip_init(NULL, NULL);

	/* Initialize the LwIP stack */ // Network interface config init
	Netif_Config();

	//NetConn implementation
	struct netconn *conn_stm, *tcp_conn_1, *tcp_conn_2;
	err_t err, accept_err;
	static struct netbuf *buf;

	conn_stm = netconn_new(NETCONN_TCP);

	// Continue only if STM's netconn struct is initialized
	if (conn_stm!= NULL)
	{
		/* Bind to port 5 with default IP address */
		if(netconn_bind(conn_stm, NULL, 5) == ERR_OK)
		{
			blink_yellow_led(2, 200);
		}
		else
			HAL_GPIO_WritePin(GPIOE, LED_YELLOW_Pin, 1);

		// Set server in listening mode
		if(netconn_listen(conn_stm) == ERR_OK)
			blink_yellow_led(2, 200);

		// Process a new connection 1
		if(netconn_accept(conn_stm, &tcp_conn_1) == ERR_OK)
		{
			blink_yellow_led(2, 200);

			// TCP_CLIENT_1
			tcp_conn_1_attr.name = "ethernet_client_1";
			tcp_conn_1_attr.stack_size = 4 * configMINIMAL_STACK_SIZE;
			tcp_conn_1_attr.priority = osPriorityNormal;
			tcp_conn_1_handle = osThreadNew(tcp_client_1_handling_thread, tcp_conn_1, &tcp_conn_1_attr);
		}
		// Process a new connection 2
		if(netconn_accept(conn_stm, &tcp_conn_2) == ERR_OK)
		{
			blink_yellow_led(2, 200);

			// TCP_CLIENT_1
			tcp_conn_2_attr.name = "ethernet_client_2";
			tcp_conn_2_attr.stack_size = 4 * configMINIMAL_STACK_SIZE;
			tcp_conn_2_attr.priority = osPriorityNormal;
			tcp_conn_2_handle = osThreadNew(tcp_client_2_handling_thread, tcp_conn_2, &tcp_conn_2_attr);
		}

		for(;;)
		{
                         osDelay(5);
		}
	}
	// If STM's netconn struct doesn't get initialized
	else
		HAL_GPIO_WritePin(GPIOE, LED_YELLOW_Pin, 1);
  /* USER CODE END StartEthernetComm */
}

 

Thread 1 code:

void tcp_client_1_handling_thread(void *argument)
{
	/* USER CODE BEGIN tcp_client_1_handling_thread */
	printf("Started client1 specific thread\r\n");

	struct netconn *conn_new = (struct netconn *) argument;
	static struct netbuf *buf;

	/* Infinite loop */
	for(;;)
	{
		blink_yellow_led(1, 100);
		if(netconn_write(conn_new, Eth_Packet, ETH_BUFFERSIZE, NETCONN_COPY) != ERR_OK)
		{
			printf("Ethernet packet tx failed\r\n");
		}
		osDelay(1000);
	}
	/* USER CODE END tcp_client_1_handling_thread */
}

 

Thread 2 code:

void tcp_client_2_handling_thread(void *argument)
{
	/* USER CODE BEGIN tcp_client_1_handling_thread */
	printf("Started client2 specific thread\r\n");

	struct netconn *conn_new = (struct netconn *) argument;
	static struct netbuf *buf;

	/* Infinite loop */
	for(;;)
	{

	if(netconn_write(conn_new, Eth_Packet, ETH_BUFFERSIZE, NETCONN_COPY) != ERR_OK)
	{
		printf("Ethernet packet tx failed\r\n");
	}

		osDelay(1000);
	}
	/* USER CODE END tcp_client_1_handling_thread */
}