STM32 ETHERNET #4. TCP SERVER

This it the fourth tutorial in the STM32 Ethernet series, and today we will see how to create TCP Server using STM32.I have already covered the UDP server and Client, and you can check them here.

CubeMX Setup

The cubeMX Setup will remain same as the previous Tutorials. I will leave some pictures below for you to refer. For the detailed setup, check out the Connection tutorial


Some Insight into the CODE

Below are the steps Given to implement the TCP Server with STM32

/* Impementation for the TCP Server
   1. Create TCP Block.
   2. Bind the Block to server address, and port.
   3. Listen for the  incoming requests by the client
   4. Accept the Request, and now the server is ready for the data transfer
 */

initialization of the TCP server

void tcp_server_init(void)
{
	/* 1. create new tcp pcb */
	struct tcp_pcb *tpcb;

	tpcb = tcp_new();

	err_t err;

	/* 2. bind _pcb to port 7 ( protocol) */
	ip_addr_t myIPADDR;
	IP_ADDR4(&myIPADDR, 192, 168, 0, 111);
	err = tcp_bind(tpcb, &myIPADDR, 7);

	if (err == ERR_OK)
	{
		/* 3. start tcp listening for _pcb */
		tpcb = tcp_listen(tpcb);

		/* 4. initialize LwIP tcp_accept callback function */
		tcp_accept(tpcb, tcp_server_accept);
	}
	else
	{
		/* deallocate the pcb */
		memp_free(MEMP_TCP_PCB, tpcb);
	}
}
  • Here First of all, we will create a new TCP Control block. tpcb = tcp_new();
  • Then we will bind the Block to the local IP Address and Port. This will be the server IP and Port. tcp_bind(tpcb, &myIPADDR, 7)
  • The next step is to listen for the incoming traffic. tpcb = tcp_listen(tpcb);
  • And finally we will accept the request from the client. tcp_accept(tpcb, tcp_server_accept);

tcp_server_accept

It initializes few more things. The code for it is shown below

static err_t tcp_server_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
  err_t ret_err;
  struct tcp_server_struct *es;

  LWIP_UNUSED_ARG(arg);
  LWIP_UNUSED_ARG(err);

  /* set priority for the newly accepted tcp connection newpcb */
  tcp_setprio(newpcb, TCP_PRIO_MIN);

  /* allocate structure es to maintain tcp connection information */
  es = (struct tcp_server_struct *)mem_malloc(sizeof(struct tcp_server_struct));
  if (es != NULL)
  {
    es->state = ES_ACCEPTED;
    es->pcb = newpcb;
    es->retries = 0;
    es->p = NULL;

    /* pass newly allocated es structure as argument to newpcb */
    tcp_arg(newpcb, es);

    /* initialize lwip tcp_recv callback function for newpcb  */
    tcp_recv(newpcb, tcp_server_recv);

    /* initialize lwip tcp_err callback function for newpcb  */
    tcp_err(newpcb, tcp_server_error);

    /* initialize lwip tcp_poll callback function for newpcb */
    tcp_poll(newpcb, tcp_server_poll, 0);

    ret_err = ERR_OK;
  }
  else
  {
    /*  close tcp connection */
    tcp_server_connection_close(newpcb, es);
    /* return memory error */
    ret_err = ERR_MEM;
  }
  return ret_err;
}

You can see above that it initializes the callbacks like tcp_arg, tcp_recv and tcp_poll. But here are interested in tcp_recv only.






tcp_server_recv

tcp_recv initializes the callback tcp_server_recv, which will be called whenever the server receives some data from the client. The function is shown below

static err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
  struct tcp_server_struct *es;
  err_t ret_err;

  LWIP_ASSERT("arg != NULL",arg != NULL);

  es = (struct tcp_server_struct *)arg;

  /* if we receive an empty tcp frame from client => close connection */
  if (p == NULL)
  {
    /* remote host closed connection */
    es->state = ES_CLOSING;
    if(es->p == NULL)
    {
       /* we're done sending, close connection */
       tcp_server_connection_close(tpcb, es);
    }
    else
    {
      /* we're not done yet */
      /* acknowledge received packet */
      tcp_sent(tpcb, tcp_server_sent);

      /* send remaining data*/
      tcp_server_send(tpcb, es);
    }
    ret_err = ERR_OK;
  }
  /* else : a non empty frame was received from client but for some reason err != ERR_OK */
  else if(err != ERR_OK)
  {
    /* free received pbuf*/
    if (p != NULL)
    {
      es->p = NULL;
      pbuf_free(p);
    }
    ret_err = err;
  }
  else if(es->state == ES_ACCEPTED)
  {
    /* first data chunk in p->payload */
    es->state = ES_RECEIVED;

    /* store reference to incoming pbuf (chain) */
    es->p = p;

    /* initialize LwIP tcp_sent callback function */
    tcp_sent(tpcb, tcp_server_sent);

    /* handle the received data */
    tcp_server_handle(tpcb, es);

    ret_err = ERR_OK;
  }
  else if (es->state == ES_RECEIVED)
  {
    /* more data received from client and previous data has been already sent*/
    if(es->p == NULL)
    {
      es->p = p;

      /* handle the received data */
      tcp_server_handle(tpcb, es);
    }
    else
    {
      struct pbuf *ptr;

      /* chain pbufs to the end of what we recv'ed previously  */
      ptr = es->p;
      pbuf_chain(ptr,p);
    }
    ret_err = ERR_OK;
  }
  else if(es->state == ES_CLOSING)
  {
    /* odd case, remote side closing twice, trash data */
    tcp_recved(tpcb, p->tot_len);
    es->p = NULL;
    pbuf_free(p);
    ret_err = ERR_OK;
  }
  else
  {
    /* unknown es->state, trash data  */
    tcp_recved(tpcb, p->tot_len);
    es->p = NULL;
    pbuf_free(p);
    ret_err = ERR_OK;
  }
  return ret_err;
}
  • Here few things are predefines, and we will leave them as it is. We will focus on line 42 onwards.
  • When the data is received for the first time, the state will be set to accepted.
  • This is to make sure that the tcp_sent callback is initialized, and it should only initialize once.
  • After this, the state will be changed to RECEIVED, and the code at line number 58 will execute.
  • here we will store the incoming data into our buffer (line 63), and call the server handle function (line 66) to handle the incoming data.
  • I have created this server handle function, so that there you can process the incoming data better. And you should leave the received callback as it is.


Server Handle

static void tcp_server_handle (struct tcp_pcb *tpcb, struct tcp_server_struct *es)
{
	struct tcp_server_struct *esTx;

	/* get the Remote IP */
	ip4_addr_t inIP = tpcb->remote_ip;
	uint16_t inPort = tpcb->remote_port;

	/* Extract the IP */
	char *remIP = ipaddr_ntoa(&inIP);

	esTx->state = es->state;
	esTx->pcb = es->pcb;
	esTx->p = es->p;

	char buf[100];
	memset (buf, '\0', 100);

	strncpy(buf, (char *)es->p->payload, es->p->tot_len);
	strcat (buf, "+ Hello from TCP SERVER\n");


	esTx->p->payload = (void *)buf;
	esTx->p->tot_len = (es->p->tot_len - es->p->len) + strlen (buf);
	esTx->p->len = strlen (buf);

	tcp_server_send(tpcb, esTx);

	pbuf_free(es->p);

}
  • Here we will get the IP address and the port for the client
  • The IP address is in the integer format, and we can use the function ipaddr_ntoa to convert the address in the proper format
  • The incoming data from the client is stored in the payload of the p buf.
  • I am mixing the incoming data with the new one, and storing it in the buffer.
  • Then we will update the p buf with the new payload, total length, and the length of the buffer to send.
  • Finally we will send the p buf to the client using tcp_server_send function.


RESULT

  • Here I have used the Hercules as the TCP Client
  • The Pink color is the data sent by the client, and in response, the Black color data is sent by the Server

Check out the Video Below










Info

You can help with the development by DONATING
To download the code, click DOWNLOAD button and view the Ad. The project will download after the Ad is finished.

31 Comments. Leave new

  • ./LWIP/App/lwip.o:C:/Users/syd/STM32CubeIDE/workspace_1.12.0/stm32F207/Debug/../LWIP/App/lwip.c:42: multiple definition of `gnetif’;

    I am getting above error when i built the code. Any comments?

    Reply
  • I have a problem on Nucleo-F207ZG. UDP server works perfectly, so I think network config is OK. I tried TCP server, ping is OK, it is possible to connect and disconnect but if you send any data (e.g. “TEST”), you get reply (“TEST+ Hello from TCP SERVER”) but on debug UART there are two same messages: “Assertion “pbuf_free: p->ref > 0″ failed at line 747 in ../Middlewares/Third_Party/LwIP/src/core/pbuf.c”. This means that “LWIP_ASSERT(“pbuf_free: p->ref > 0″, p->ref > 0);” failed in loop which should de-allocate all consecutive pbufs. LWIP 2.0.3. Perhaps someone can help?

    Reply
    • I solved this problem in this way: I removed tcp_server_handle (and changed tcp_server_handle calls to tcp_server_send in tcp_server_recv) and modified tcp_server_send:
      – changed line:
      wr_err = tcp_write(tpcb, ptr->payload, ptr->len, 1);
      – with following lines:
      char buf[100];
      memset (buf, ‘\0’, 100);
      strncpy(buf, ptr->payload, ptr->len);
      strcat (buf, “+ Hello from TCP SERVER\n”);
      wr_err = tcp_write(tpcb, buf, strlen (buf), 1);
      Now it works properly (and is simplier).

      Reply
  • ı am struggling multiple client situations.
    If ı use stm32 tcp server and one tcp client connections — there is no problem.

    But ı use stm32 tcp server and two or more tcp client connect to stm32. All connections succesfully happen and all client can send data to tcp server. but tcp server try to send data. just last client receive data. How can tcp server send data all connected client ?

    Reply
  • What should I do for 8 different ports?

    Reply
  • Hello everyone
    thanks for education
    I write same code
    But it dosn’t work
    Is this code depends on CubeMX version or CubeIDE version??
    witch version do you use??
    I use f407IG Cortex Board.

    Reply
  • Thanks a lot!
    I am using STM32F767ZI and STM32CubeIDE. In this case I found HardFault error after calling “tcp_server_handle()”. that was about memmory allocation for “struct tcp_server_struct *esTx;”. I’ve added solved it “esTx = (struct tcp_server_struct *)mem_malloc(sizeof(struct tcp_server_struct));” before using “esTx” and solved it.

    Reply
    • Hey man, make sure to use the following after SendData(tpcb, esTx);

      /* delete es structure */ if (esTx != NULL) { mem_free(esTx); }

      You need to free the memory otherwise your board will crash after X received TCP messages.

      Reply
  • Has anyone used lwip and tcpserver in cube?
    I have a very hard problem with this on my stm32f107vct6 micro and no one can help.
    I used Cube’s LWIP TCP ECHO SERVER example in stm32f107, but the same problem exists.

    Reply
  • Hello
    In static void tcp_server_handle app crash in line esTx->pcb = es->pcb;
    I use stm32f107 and FreeRTOS and LWIP

    Reply
  • How can this work?

    	struct tcp_server_struct *esTx = 0;
        ...  
      	esTx->state = es->state;
    	esTx->pcb = es->pcb;
    	esTx->p = es->p;
    

    What a horror …

    Reply
  • how to get lwip file

    Reply
  • esTx is a NULL pointer in tcp_server_handle ... surely not?
    
    

    struct tcp_server_struct *esTx = 0;
    :
    :
    esTx->state = es->state;

    Reply
  • Thamanoon Kedwiriyakarn
    March 2, 2022 8:11 AM

    Thank you very much Sir.
    Would you create the example with ADC and GPIO?

    Reply
  • I want to send message from the server to the client without a request from the client.
    is this possible ? if so can you point for an example

    Reply
  • Hi there,

    my question is: Why does this work ?
    I started an STM32 Project and wanted to use the Socket Library. But for that i needed an RTOS on my NucleoBoard.
    Why does the TCP and UDP Server work without that ?

    Reply
  • Hello, for me first packet is correctly echoed but then the program hangs and subsequent packets are ignored

    Reply
  • Hi, may I know which buffer I should clear? I’m facing the same reply corrupted issue in the payload message

    Reply
    • clear that pbuf (received and sent both) after processing the data. I don’t have the lan connection right now to test it.

      Reply
      • Hi, I cleared the pbuf but it is still the same unfortunately. Only managed to send 5 char at once without any corruptions.

        Reply
        • while testing, I was able to sent quite lengthy packets at once. I don’t know why your is only limited to 5.

          Reply
        • Hello Tim,
          me too. <= 5 characters ok, >5 I get a corrupted response (originale string with trailing garbage). Did you solve it ?

          Reply
          • Hi, yes managed to solve it by using memcpy for the incoming data, and manipulating it further(anything you want). then subtract the original data length from tot len before adding ur new data length to send back

          • HI Tim,

            I followed your hint and ended with the following function:

            static void my_server_handle(struct tcp_pcb *tpcb, struct tcp_echoserver_struct *es)
            {
               char buf[100] = “Ricevuto: “;
               memcpy(buf + 10, es->p->payload, es->p->len);
               int len = 100;

               es->p->payload = buf;
               es->p->tot_len = (es->p->tot_len – es->p->len) + len;
               es->p->len = len;
               tcp_echoserver_send(tpcb, es);
            }

            This works perfectly as an echo repeater (where I cannot make work the one in the article since sometimes I get corrupted data). Anyway, the next task for me is to send an infinite stream of data once the client is connected: do you have any hint ?

            Thanx

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

keyboard_arrow_up

Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We don't have any banner, Flash, animation, obnoxious sound, or popup ad. We do not implement these annoying types of ads!

We need money to operate the site, and almost all of it comes from our online advertising.

Please add controllerstech.com to your ad blocking whitelist or disable your adblocking software.

×