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, '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);
}
', 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 thep 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 newpayload, total length, and the length
of the buffer to send. - Finally we will send the
p buf
to the client usingtcp_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
static void tcp_server_handle (struct tcp_pcb *tpcb, struct tcp_server_struct *es)
{
struct tcp_server_struct *esTx;
Missing mem_malloc for this “*esTx” ??
well i did same as you did but when i am going to connect the TCP Client no callback receive and in hercules displaying connection time out
Hello, thank you for your excellent explanation, it was a very good tutorial. I am a bit confused. Please guide me. I get the following error after the build. How can I fix it?
02-stm32server\02-stm32server.axf: Error: L6218E: Undefined symbol SCB_EnableDCache (referred from main.o).
02-stm32server\02-stm32server.axf: Error: L6218E: Undefined symbol SCB_EnableICache (referred from main.o).
Please generate your own project instead of copying the entire code. Which MCU are you using anyway ?
it only echo back not Hello from TCP SERVER is not recived , echo back working, please give guide which change required?
./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?
In main.c change:
struct netif gnetif;
to:
extern struct netif gnetif;
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?
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).
I tried your fix on the STM32F423.
While the code worked I still get the error “Assertion “pbuf_free: p->ref > 0″ failed at line 747 in ../Middlewares/Third_Party/LwIP/src/core/pbuf.c”
ı 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 ?
What should I do for 8 different ports?
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.
What doesn’t work ?
If the ping is not successful, you should start with the first tutorial in this series.
ping is worked
but it echo for 10 times in one send
I want to find the why
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.
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.
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.
Hello
In static void tcp_server_handle app crash in line esTx->pcb = es->pcb;
I use stm32f107 and FreeRTOS and LWIP
https://controllerstech.com/how-to-use-stm32-as-tcp-server-and-client-using-netconn/
How can this work?
What a horror …
Oh, this has been written before
how to get lwip file
struct tcp_server_struct *esTx = 0;
:
:
esTx->state = es->state;
Thank you very much Sir.
Would you create the example with ADC and GPIO?
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
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 ?
What you mean why ?
this is the raw library, and it works pretty well without RTOS also
Hello, for me first packet is correctly echoed but then the program hangs and subsequent packets are ignored
That’s obvious isn’t it. If the program hangs, it’s going to ignore things
Hi, may I know which buffer I should clear? I’m facing the same reply corrupted issue in the payload message
clear that pbuf (received and sent both) after processing the data. I don’t have the lan connection right now to test it.
Hi, I cleared the pbuf but it is still the same unfortunately. Only managed to send 5 char at once without any corruptions.
while testing, I was able to sent quite lengthy packets at once. I don’t know why your is only limited to 5.
Hello Tim,
me too. <= 5 characters ok, >5 I get a corrupted response (originale string with trailing garbage). Did you solve it ?
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