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.

FIXED the Errors

The code is fixed now and you can receive more than 5 characters without the data being corrupted.

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
 */

We will start with the 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 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_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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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.


Let’s take a look at the server handle function now

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

	/* 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



DOWNLOAD

You can buy me a coffee Sensor by clicking DONATE OR Just click DOWNLOAD to download the code

controllerstech

Subscribe
Notify of
guest
11 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Menu