Recently, a netizen is asking about the speed of LWIP. This article will do a simple test on the speed of LWIP. For comparison, this article will use both non-system and system environments.
Introduction of 5.1 Network Speed Measuring Tool
However, before measuring speed, you need to introduce the following speed measuring tools, there are two software: iPerf and jperf.
The iPerf is a cross-platform network performance testing tool that supports platforms such as Win/Linux/Mac/Android/iOS. The iPerf can test TCP and UDP (we don't normally speed UDP)Bandwidth quality, iPerf can measure maximum TCP bandwidth, can be tested with a variety of parameters, and iPerf can also report bandwidth, delay jitter and packet loss. We can use these characteristics of iPerf to test the performance of some network devices such as routers, firewalls, switches, etc.
Although the iPerf is useful, it is a command-line format software that is not friendly to testers and users need to write down their tedious commands. However, there is a graphical interface program called JPerf, which simplifies the construction of complex command-line parameters, andIt also saves the test results and visualizes them in real time to make it more clear. Of course, JPerf certainly has all the functions of the iPerf, which is essentially the functions of the iperf. Therefore, this paper uses JPerf speed measurement.
For JPerf software, please get it by yourself under the instructions below. After downloading JPerf, decompress it.
Then click jperf.bat to open the software.
It is worth noting that a Java environment is also required to run the software, so configure the Java environment yourself.
5.2 No System Speed Measurement (RAW API)
To use JPerf for speed measurement, a TCP server or client must be implemented first. The TCP theory is not covered here. There is a lot of information on the Internet. This section only explains how to use RAW API to implement a TCP server.
5.2.1 TCP-related RAW API
Before we begin to implement TCP servers, let's first take a look at what RAW API functions are associated with TCP in LwIP and briefly understand their functions.
- API functions for establishing TCP connections
function | describe |
---|---|
tcp_new() | Create PCB Control Block for TCP |
tcp_bind() | IP and port number of the binding server |
tcp_listen() | PCB Control Block Listening for TCP |
tcp_accepted() | Notifies the LWIP stack that a TCP connection has been accepted |
tcp_conect() | Connect to remote hosts, used by clients |
- API functions for sending TCP data
function | describe |
---|---|
tcp_write() | Construct a message and place it in the send buffer queue of the control block |
tcp_sent() | A callback function registered with the control block sent field, which is called back after the data has been successfully sent |
tcp_output() | Send data from send buffer queue |
- Receive TCP data
function | describe |
---|---|
tcp_recv() | A callback function registered with the control block recv field that is called when new data is received |
tcp_recved() | This function must be called when the program has finished processing the data, notifying the kernel to update the receive window |
- poll function
function | describe |
---|---|
tcp_poll() | A callback function registered with the control block poll field that is called periodically |
- Close and abort connections
function | describe |
---|---|
tcp_close() | Close a TCP connection |
tcp_err() | Callback function registered with control block err field, called when an error is encountered |
tcp_abort() | Disconnect TCP Connection |
Before the implementation of TCP server, cooperate with LWIP. The author's previous articles on how to transplant LWIP can be consulted.
Transplantation of LWIP (no system)
The author uses static IP here and opens the TCP module.
5.2.2 TCP Server Implementation Process
Previously, you learned about the API functions involved in TCP and opened the related configuration through STM32CubeMX, so how to implement a TCP server using these functions? Let's first briefly explain its basic process.
1. New Control Block
Use the tcp_new() function to create a TCP control block.
2. Bind Control Blocks
For the server, after creating a new fast control, you need to bind the local IP and port on the control block to facilitate client connections.
3. Control Block Listening
With the tcp_listen function, for the server, you need to explicitly call the tcp_listen function to put the control block in a listening state and wait for the connection request from the client.
4. Connect
Once the tcp_listen function is in the server listening state, you need to register a receive handler with the tcp_accept function immediately, because once a client connection request is successfully established, the server will call the handler.
5. Accept and process data
Once the connection is successful, the accept callback function calls the tcp_recv function to register a receive-complete handler. For the server, when the client's data or operational requirements are received, the callback function is called to process the call. This is a complex process: after receiving the data, first notify the update acceptance window (using the tcp_recved function), process and send data (using the tcp_write function), clear the sent data (using the tcp_sent function) when the data is sent successfully, and close the connection (using the function tcp_close).
The whole flowchart is shown below:
5.2.3 TCP Server Code Implementation
The implementation process of the TCP server is analyzed before, and the next step is through the API described earlier.
The first is the initialization of the TCP server. The modern code is as follows:
/** * @brief TCP Server Initialization * @param None * @retval res */ uint8_t tcp_server_init(void) { uint8_t res = 0; err_t err; struct tcp_pcb *tcppcbnew; //Define a TCP Server Control Block struct tcp_pcb *tcppcbconn; //Define a TCP Server Control Block /* Assign a tcp_pcb structure to the TCP server */ tcppcbnew = tcp_new(); if(tcppcbnew) //Created successfully { //Bind local IP with specified port number, IP_ADDR_ANY binds all local IP addresses err = tcp_bind(tcppcbnew,IP_ADDR_ANY,TCP_SERVER_PORT); if(err==ERR_OK) //Binding complete { tcppcbconn=tcp_listen(tcppcbnew); //Set tcppcb to listen //Callback function to initialize tcp_accept of LWIP tcp_accept(tcppcbconn,tcp_server_accept); } else { res=1; } } else { res=1; } return res; }
You can see that the tcp_accept() function registers a callback function with the following implementation code:
/** * @brief lwIP tcp_accept()Callback function * @param arg,newpcb, err * @retval ret_err */ 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); tcp_setprio(newpcb,TCP_PRIO_MIN);//Set the newly created pcb priority es=(struct tcp_server_struct*)mem_malloc(sizeof(struct tcp_server_struct)); //Allocate memory if(es!=NULL) //Successful memory allocation { es->state = ES_TCPSERVER_ACCEPTED; //Receive Connection es->pcb = newpcb; es->p = NULL; tcp_arg(newpcb, es); tcp_recv(newpcb, tcp_server_recv); //Initialize the callback function for tcp_recv() tcp_err(newpcb, tcp_server_error); //Initialize tcp_err() callback function tcp_poll(newpcb, tcp_server_poll,1); //Initialize tcp_poll callback function tcp_sent(newpcb, tcp_server_sent); //Initialize Send Callback Function tcp_server_flag |= 1<<5; //Marked Client Connection ret_err=ERR_OK; } else { ret_err=ERR_MEM; } return ret_err; }
This function is used for data interaction with the client. Receive Send and other functions are registered in the function. The most important thing in this paper is to need Receive Function, the code is as follows:
/** * @brief lwIP tcp_recv()Callback function of function * @param arg,tpcb, p, err * @retval ret_err */ err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { err_t ret_err; uint32_t data_len = 0; struct pbuf *q; struct tcp_server_struct *es; LWIP_ASSERT("arg != NULL",arg != NULL); es=(struct tcp_server_struct *)arg; if(p == NULL) //Receive empty data from client { es->state = ES_TCPSERVER_CLOSING;//TCP connection needs to be closed es->p = p; ret_err = ERR_OK; } else if(err != ERR_OK) //Received a non-empty data from the client, but for some reason err!=ERR_OK { if(p) { pbuf_free(p); //Release Received pbuf } ret_err = err; } else if(es->state == ES_TCPSERVER_ACCEPTED) //Connected { if(p != NULL) //Print out when connected and received data is not empty { memset(tcp_server_recvbuf, 0, TCP_SERVER_RX_BUFSIZE); //Data Receive Buffer Zeroing for(q = p; q != NULL; q = q->next) //Traverse the entire pbuf list { //Determines whether the data to be copied to TCP_SERVER_RX_BUFSIZE is larger than the remaining space of TCP_SERVER_RX_BUFSIZE, if greater than //Otherwise all data will be copied if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len)) { memcpy(tcp_server_recvbuf+data_len,q->payload,(TCP_SERVER_RX_BUFSIZE-data_len));//Copy Data } else { memcpy(tcp_server_recvbuf+data_len,q->payload,q->len); } data_len += q->len; if(data_len > TCP_SERVER_RX_BUFSIZE) { break; //Out of TCP Client Receive Array, Out } } tcp_server_flag |= 1<<6; //Tag Received Data tcp_recved(tpcb,p->tot_len);//Used to get received data and notify LWIP that more data is available pbuf_free(p); //Release memory ret_err=ERR_OK; } } else//Server shutdown { tcp_recved(tpcb,p->tot_len);//Used to get received data and notify LWIP that more data is available es->p = NULL; pbuf_free(p); //Release memory ret_err = ERR_OK; } return ret_err; }
As you can see, the above functions are callback functions that are used layer by layer. Please refer to the source code for other related functions. We will not go into details here.
Finally, the main() function initializes the TCP server.
/** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART3_UART_Init(); MX_LWIP_Init(); /* USER CODE BEGIN 2 */ tcp_server_init(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ MX_LWIP_Process(); //LWIP Polling Task /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
Then compile the project and download it to the board. Open the jperf software with the appropriate parameters, and the results are as follows:
From the figure above, we can see that the transmission speed is about 1-2M or a little slow.
In order to improve the transmission speed of LwIP network, the configuration of LwIP needs to be adjusted appropriately, mainly by increasing the Heap Size of memory, the size of memory pool, the number of TCP segments, the maximum TCP segments, the maximum length of TCP send buffer queue, etc.
Modifications to the above parameters can be configured through STM32CubeMX. This is the following option:
The corresponding files are lwipopts.h and opt.h, with the main configuration parameters in opt.h.
The author directly modified the parameters in the file as follows:
//Heap size of memory heap #define MEM_SIZE (24*1024) /* memp Number of pbuf s in the structure, which should be set a little higher if the application sends a large amount of data from ROM or static storage */ #define MEMP_NUM_PBUF 24 /* Maximum number of message segments concurrently in the TCP buffer queue */ #define MEMP_NUM_TCP_SEG 150 /* Memory pool size */ #define PBUF_POOL_SIZE 64 /* Maximum TCP message segment, TCP_MSS = (MTU - IP header size - TCP header size) */ #define TCP_MSS (1500 - 40) /* TCP Send Buffer Size (Bytes) */ #define TCP_SND_BUF (11*TCP_MSS) /* TCP Receive window size */ #define TCP_WND (11*TCP_MSS)
Modify and compile, and the test results are as follows:
Comparing with the default parameters used in the previous section, we can see that the speed increases dramatically. It is about 4-5 times faster.
Performance optimization for LWIP will be explained in later chapters, with the focus of this article on speed measurement.
5.3 RT-Thread System Speed Measurement (Socket API)
The previous section uses the RAW API to implement the TCP server. This section uses the Socket API to implement the TCP server. Refer to my blog about the implementation of the TCP server.
The server code to implement TCP is as follows:
#include <rtthread.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdev.h> #include <stdio.h> #include <string.h> #define SERVER_PORT 8888 #define BUFF_SIZE 4096 static char recvbuff[BUFF_SIZE]; static void net_server_thread_entry(void *parameter) { int sfd, cfd, maxfd, i, nready, n; struct sockaddr_in server_addr, client_addr; struct netdev *netdev = RT_NULL; char sendbuff[] = "Hello client!"; socklen_t client_addr_len; fd_set all_set, read_set; //FD_SETSIZE contains the fd of the server int clientfds[FD_SETSIZE - 1]; // Get netdev network card object by name netdev = netdev_get_by_name((char*)parameter); if (netdev == RT_NULL) { rt_kprintf("get network interface device(%s) failed.\n", (char*)parameter); } //Create socket if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { rt_kprintf("Socket create failed.\n"); } server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); //server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Get IP Address Information in Network Card Object server_addr.sin_addr.s_addr = netdev->ip_addr.addr; //Bind socket if (bind(sfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0) { rt_kprintf("socket bind failed.\n"); closesocket(sfd); } rt_kprintf("socket bind network interface device(%s) success!\n", netdev->name); //Listening socket if(listen(sfd, 5) == -1) { rt_kprintf("listen error"); } else { rt_kprintf("listening...\n"); } client_addr_len = sizeof(client_addr); //Initialize maxfd equal to sfd maxfd = sfd; //Empty fdset FD_ZERO(&all_set); //Add sfd file descriptors to the collection FD_SET(sfd, &all_set); //Initialize the collection of client FDS for(i = 0; i < FD_SETSIZE -1 ; i++) { //Initialized to -1 clientfds[i] = -1; } while(1) { //Each time a select returns, the fd_set collection changes and cannot be used with a select. //So we want to save the settings fd_set and the read fd_set read_set = all_set; nready = select(maxfd + 1, &read_set, NULL, NULL, NULL); //No timeout mechanism, no return 0 if(nready < 0) { rt_kprintf("select error \r\n"); } //Determine whether the listened socket has data if(FD_ISSET(sfd, &read_set)) { //A client is connected cfd = accept(sfd, (struct sockaddr *)&client_addr, &client_addr_len); if(cfd < 0) { rt_kprintf("accept socket error\r\n"); //Continue Selecting continue; } rt_kprintf("new client connect fd = %d\r\n", cfd); //Add a new cfd to the fd_set collection FD_SET(cfd, &all_set); //Update maxfd to select maxfd = (cfd > maxfd)?cfd:maxfd; //Save the new cfd to the cfds collection for(i = 0; i < FD_SETSIZE -1 ; i++) { if(clientfds[i] == -1) { clientfds[i] = cfd; //Exit without adding break; } } //There are no other sockets to handle: prevent duplicate work here and don't perform other tasks if(--nready == 0) { //Continue Selecting continue; } } //Traverse all client file descriptors for(i = 0; i < FD_SETSIZE -1 ; i++) { if(clientfds[i] == -1) { //Continue traversal continue; } //Determine if it is in the fd_set set set set if(FD_ISSET(clientfds[i], &read_set)) { n = recv(clientfds[i], recvbuff, sizeof(recvbuff), 0); //rt_kprintf("clientfd %d: %s \r\n",clientfds[i], recvbuff); if(n <= 0) { //Clear from collection FD_CLR(clientfds[i], &all_set); //The current client fd assignment is -1 clientfds[i] = -1; } else { //Write back to client n = send(clientfds[i], sendbuff, strlen(sendbuff), 0); if(n < 0) { //Clear from collection FD_CLR(clientfds[i], &all_set); //The current client fd assignment is -1 clientfds[i] = -1; } } } } } } static int server(int argc, char **argv) { rt_err_t ret = RT_EOK; if (argc != 2) { rt_kprintf("bind_test [netdev_name] --bind network interface device by name.\n"); return -RT_ERROR; } /* Create serial thread */ rt_thread_t thread = rt_thread_create("server", net_server_thread_entry, argv[1], 2048, 5, 10); /* Start threads if created successfully */ if (thread != RT_NULL) { rt_thread_startup(thread); } else { ret = RT_ERROR; } return ret; } #ifdef FINSH_USING_MSH #include <finsh.h> MSH_CMD_EXPORT(server, network interface device test); #endif /* FINSH_USING_MSH */
Add the appropriate code and compile it before downloading the firmware. The test results are as follows:
It can be seen that its transmission speed is also around 1M, which is slower than in unsystematic environment.
To increase the speed, configure the LWIP parameters as follows:
Compile, download, and test results as follows:
You can see that the speed has been improved, but the increase is obvious when there is no system. The later chapters on the reasons will be analyzed in detail.
Resource Acquisition Method
1. Long press the QR code below to pay attention to the public number [Embedded Experimental Building]
2. Get information on public number response keywords [LWIP]
Welcome to my website
BruceOu's Beep Mile
BruceOu's Home Page
BruceOu's Blog
BruceOu's CSDN Blog
BruceOu's Brief
BruceOu's knowledge