第十八节 使用JPerf 工具测试网速

打印 上一主题 下一主题

主题 1869|帖子 1869|积分 5607

iPerf 与JPerf

在解说网络测速之前,我们先来了解一下测速的工具:iPerf 是一个跨平台的网络性能测试工具,它支持Win/Linux/Mac/Android/iOS 等平台,iPerf 可以测试TCP 和UDP(我们一般不对UDP 进行测速)带宽质量,iPerf 可以丈量最大TCP 带宽,可以具有多种参数进行测试,同时iPerf 还可以报告带宽,耽误抖动和数据包丢失的环境,我们可以使用iPerf 的这些特性来测试一些网络装备如路由器,防火墙,交换机等的性能。
虽然iPerf 很好用,但是它却是下令行格式的软件,对使用测试的人员并不友爱,使用者需要记下他繁琐的下令,不过它还有一个图形界面程序叫做JPerf,使用JPerf 程序能简化了复杂下令行参数的构造,而且它还保存测试效果,而且将测试效果实时图形化出来,更加一目了然,当然,JPerf 也肯定拥有iPerf 的所有功能。
测试网络速率

获取JPerf 网络测速工具

在测速之前,我们需要得到网络测速工具,在我们的论坛上有这个工具,然后,我们直接下载即可:http://www.firebbs.cn/forum.php?mod=viewthread&tid=26274&fromuid=37393。
下载后解压,双击jperf.bat 运行,稍等一会就出现JPerf 的界面,详细见图。

图JPerf 界面
我们来解说一下这个界面的一些内容:


  • 图(1):客户端设置,电脑作为客户端,毗连到服务器中(即我们的开发板作为服务器),服
    务器地址需要填写正确,端口号默认是5001,并发流默认是1 个。
  • 图(2):服务器设置,电脑作为服务器,我们的开发板作为客户端,client limit 选项表现仅允许指定客户端毗连,Num Connections 指定最大允许毗连的数量,为0 不限定。
  • 图(3):开始和停止JPerf 的运行。
  • 图(4):兼容旧版本(当server 端和client 端版本不一样时使用),默认不勾选,Transmit 设置测试模式,我们一般指定发送的时间,以秒为单位,当然也可以指定发送的数据大小,以字节为单位。
  • 图(5):如果勾选Dual 表现同时进行双向传输测试,如果勾选Trade 表现单独进行双向传输测试,默认不勾选。
  • 图(6):指定需要传输的文件以及表现最大TCP 报文段。
  • 图(7):传输层设置,我们一般用来测试TCP 毗连的速率,Buffer Length 选项用于设置缓冲区大小,TCP Window Size 用于指定TCP 窗口大小,Max Segment Size 用于设定最大MTU 值,TCP No Delay 用于设定TCP 不延时。
  • 图(8):网速表现窗口,以折线图的情势表现出来。
  • 图(9):网速相干数据输出窗口,以文本的情势。
测试开发板吸收速率(NETCONN API)

首先,我们肯定需在开发板上开发程序的,那么我们就单独创建一个iPerf 测速线程,在开发板上运行,开发板作为客户端,不断监听客户端(JPerf 上位机)的毗连。
代码实现部分:我们首先拷贝一个移植好的工程,而且在工程中添加两个文件,分别为ipref.c 和ipref.h,然后在ipref.c 文件下添加代码清单 中的代码,其实这个代码跟TCP 服务器实验的代码都是差不多的,只不过吸收到数据不进行处理惩罚而已,在ipref.h 文件下添加代码清单 中的代码。
代码清单 ipref.c 文件内容
  1. /* FreeRTOS 头文件*/
  2. #include "FreeRTOS.h"
  3. #include "task.h"
  4. #include "queue.h"
  5. #include "semphr.h"
  6. #include <stdint.h>
  7. #include <stdio.h>
  8. #include <lwip/sockets.h>
  9. #include "iperf.h"
  10. #include "lwip/opt.h"
  11. #include "lwip/sys.h"
  12. #include "lwip/api.h"
  13. #define IPERF_PORT 5001
  14. #define IPERF_BUFSZ (4 * 1024)
  15. void iperf_server(void *thread_param)
  16. {
  17.         struct netconn *conn, *newconn;
  18.         err_t err;
  19.         void* recv_data;
  20.        
  21.         recv_data = (void *)pvPortMalloc(IPERF_BUFSZ);
  22.         if (recv_data == NULL) {
  23.                 printf("No memory\n");
  24.         }
  25.        
  26.         conn = netconn_new(NETCONN_TCP);
  27.         netconn_bind(conn, IP_ADDR_ANY, 5001);
  28.        
  29.         LWIP_ERROR("tcpecho: invalid conn", (conn != NULL), return;);
  30.        
  31.         /* Tell connection to go into listening mode. */
  32.         netconn_listen(conn);
  33.         while (1)
  34.         {
  35.                 /* Grab new connection. */
  36.                 err = netconn_accept(conn, &newconn);
  37.                 /*printf("accepted new connection %p\n", newconn);*/
  38.                 /* Process the new connection. */
  39.                 if (err == ERR_OK)
  40.                 {
  41.                         struct netbuf *buf;
  42. // void *data;
  43.                         u16_t len;
  44.                        
  45.                         while ((err = netconn_recv(newconn, &buf)) == ERR_OK)
  46.                         {
  47.                                 /*printf("Recved\n");*/
  48.                                 do
  49.                                 {
  50.                                         netbuf_data(buf, &recv_data, &len);
  51. // err = netconn_write(newconn, data, len, NETCONN_COPY);
  52.                                 }
  53.                                 while (netbuf_next(buf) >= 0);
  54.                                 netbuf_delete(buf);
  55.                         }
  56.                         /*printf("Got EOF, looping\n");*/
  57.                         /* Close connection and discard connection identifier. */
  58.                         netconn_close(newconn);
  59.                         netconn_delete(newconn);
  60.                 }
  61.         }
  62. }
  63. void
  64. iperf_server_init(void)
  65. {
  66.         sys_thread_new("iperf_server", iperf_server, NULL, 2048, 4);
  67. }
复制代码
代码清单 ipref.h 文件内容
  1. #ifndef LWIP_IPERF_H
  2. #define LWIP_IPERF_H
  3. #define TCP_SERVER_THREAD_NAME "iperf_server"
  4. #define TCP_SERVER_THREAD_STACKSIZE 1024
  5. #define TCP_SERVER_THREAD_PRIO 4
  6. void iperf_server(void *thread_param);
  7. void iperf_server_init(void);
复制代码
在main.c 文件中将iperf_server_init() 调用一下即可。而且配置好开发板的IP 地址与端口号,我们打开JPerf 测速软件,配置好要毗连的服务器IP 地址与端口,测试时间设置得长一点,我们使用10000 秒,然后点击开始,就得到我们需要的网速数据,速率高达94Mbps,即11.5M 字节/秒,已经黑白常高的速率了,而且通过折线图,我们也能看到这速率是很稳定的,详细见图。

图 NETCONN API 吸收速率
测试开发板吸收速率(Socket API)

这个实验我们只需要把上一个实验中的ipref.c 文件内容替换掉就行了,详细见代码清单。
代码清单 ipref.c 文件内容
  1. /* FreeRTOS 头文件*/
  2. #include "FreeRTOS.h"
  3. #include "task.h"
  4. #include "queue.h"
  5. #include "semphr.h"
  6. #include <stdint.h>
  7. #include <stdio.h>
  8. //#include <lwip/time.h>
  9. #include <lwip/sockets.h>
  10. //#include <lwip/select.h>
  11. //#include "netdb.h"
  12. #include "iperf.h"
  13. #include "lwip/opt.h"
  14. #include "lwip/sys.h"
  15. #include "lwip/api.h"
  16. #define IPERF_PORT 5001
  17. #define IPERF_BUFSZ (4 * 1024)
  18. void iperf_server(void *thread_param)
  19. {
  20.         uint8_t *recv_data;
  21.         socklen_t sin_size;
  22.         uint32_t tick1, tick2;
  23.         int sock = -1, connected, bytes_received;
  24.         uint64_t recvlen;
  25.         struct sockaddr_in server_addr, client_addr;
  26.         char speed[32] = { 0 };
  27.         fd_set readset;
  28.         struct timeval timeout;
  29.        
  30.         recv_data = (uint8_t *)pvPortMalloc(IPERF_BUFSZ);
  31.         if (recv_data == NULL)
  32.         {
  33.                 printf("No memory\n");
  34.                 goto __exit;
  35.         }
  36.        
  37.         sock = socket(AF_INET, SOCK_STREAM, 0);
  38.         if (sock < 0)
  39.         {
  40.                 printf("Socket error\n");
  41.                 goto __exit;
  42.         }
  43.        
  44.         server_addr.sin_family = AF_INET;
  45.         server_addr.sin_addr.s_addr = INADDR_ANY;
  46.         server_addr.sin_port = htons(IPERF_PORT);
  47.         memset(&(server_addr.sin_zero), 0x0, sizeof(server_addr.sin_zero));
  48.        
  49. if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
  50.         {
  51.                 printf("Unable to bind\n");
  52.                 goto __exit;
  53.         }
  54.        
  55.         if (listen(sock, 5) == -1)
  56.         {
  57.                 printf("Listen error\n");
  58.                 goto __exit;
  59.         }
  60.        
  61.         timeout.tv_sec = 3;
  62.         timeout.tv_usec = 0;
  63.        
  64.         printf("iperf_server\n");
  65.         while (1)
  66.         {
  67.                 FD_ZERO(&readset);
  68.                 FD_SET(sock, &readset);
  69.                
  70.                 if (select(sock + 1, &readset, NULL, NULL, &timeout) == 0)
  71.                         continue;
  72.                
  73.                 printf("iperf_server\n");
  74.                 sin_size = sizeof(struct sockaddr_in);
  75.        
  76.                 connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size);
  77.        
  78.                 printf("new client connected from (%s, %d)\n",
  79.                         inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
  80.                
  81.                 {
  82.                         int flag = 1;
  83.                
  84.                         setsockopt(connected,
  85.                                         IPPROTO_TCP, /* set option at TCP level */
  86.                                         TCP_NODELAY, /* name of option */
  87.                                         (void *) &flag, /* the cast is historical cruft */
  88.                                         sizeof(int)); /* length of option value */
  89.                 }
  90.        
  91.                 recvlen = 0;
  92.                 tick1 = xTaskGetTickCount();
  93.                 while (1)
  94.                 {
  95.                         bytes_received = recv(connected, recv_data, IPERF_BUFSZ, 0);
  96.                         if (bytes_received <= 0) break;
  97.                
  98.                         recvlen += bytes_received;
  99.                
  100.                         tick2 = xTaskGetTickCount();
  101.                         if (tick2 - tick1 >= configTICK_RATE_HZ * 5)
  102.                         {
  103.                                 float f;
  104.                                 f=(float)(recvlen * configTICK_RATE_HZ/125/(tick2-tick1));
  105.                                 f /= 1000.0f;
  106. // snprintf(speed, sizeof(speed), "%.4f Mbps!\n", f);
  107. // printf("%s", speed);
  108.                                 tick1 = tick2;
  109.                                 recvlen = 0;
  110.                         }
  111.                 }
  112.        
  113.                 if (connected >= 0) closesocket(connected);
  114.                 connected = -1;
  115.         }
  116.        
  117. __exit:
  118.         if (sock >= 0) closesocket(sock);
  119.         if (recv_data) free(recv_data);
  120. }
  121. void
  122. iperf_server_init(void)
  123. {
  124.         sys_thread_new("iperf_server", iperf_server, NULL, 2048, 4);
  125. }
复制代码
然后得到数据,对比数据,我们发现,NETCONN API 的服从是比Socket API 的服从更高,这是由于Socket API 需要对数据进行拷贝,才能传递到上层应用中,不过71Mbps(8.7M 字节/秒)的速率已经是算很高了,详细见图。

图 Socket API 吸收速率
测试开发板发送速率(NETCONN API)

测完两种API 的吸收速率,那么就来测试一下开发板的发送速率,发送速率其实是更加重要的,好比开发板收罗一些图像,想要发送出去,如果发送速率跟不上的话,传输出去的图像就会卡帧,而发送速率充足快,就会很流通,我们测试发送速率将开发板作为客户端,JPerf 软件则作为服务器,我们开发板向服务器发送数据。首先我们也是把移植好的工程拿过来,而且添加两个文件,分别为iperf_client.c 和iperf_client.h,然后在对应的文件中添加所示的代码
代码清单 iperf_client.c 文件内容
  1. #include "iperf_client.h"
  2. #include "lwip/opt.h"
  3. #include "lwip/sys.h"
  4. #include "lwip/api.h"
  5. #define IPERF_PORT 5001
  6. #define IPERF_BUFSZ (4 * 1024)
  7. static void iperf_client(void *thread_param)
  8. {
  9.         struct netconn *conn;
  10.        
  11.         int i;
  12.        
  13.         int ret;
  14.        
  15.         uint8_t *send_buf;
  16.        
  17.         uint64_t sentlen;
  18.        
  19.         u32_t tick1, tick2;
  20.         ip4_addr_t ipaddr;
  21.        
  22.         send_buf = (uint8_t *) pvPortMalloc(IPERF_BUFSZ);
  23.         if (!send_buf) return ;
  24.        
  25.         for (i = 0; i < IPERF_BUFSZ; i ++)
  26.                 send_buf[i] = i & 0xff;
  27.                
  28.         while (1)
  29.         {
  30.                 conn = netconn_new(NETCONN_TCP);
  31.                 if (conn == NULL)
  32.                 {
  33.                         printf("create conn failed!\n");
  34.                         vTaskDelay(10);
  35.                         continue;
  36.                 }
  37.                
  38.                 IP4_ADDR(&ipaddr,192,168,0,181);
  39.                
  40.                 ret = netconn_connect(conn,&ipaddr,5001);
  41.                 if (ret == -1)
  42.                 {
  43.                         printf("Connect failed!\n");
  44.                         netconn_close(conn);
  45.                         vTaskDelay(10);
  46.                         continue;
  47.                 }
  48.                
  49.                 printf("Connect to iperf server successful!\n");
  50.                 tick1 = sys_now();
  51.                 while (1)
  52.                 {
  53.                         tick2 = sys_now();
  54.                        
  55.                         if (tick2 - tick1 >= configTICK_RATE_HZ * 5)
  56.                         {
  57.                                 float f;
  58.                                 f = (float)(sentlen*configTICK_RATE_HZ/125/(tick2 - →tick1));
  59.                                 f /= 1000.0f;
  60.                                 printf("send speed = %.4f Mbps!\n", f);
  61.                        
  62.                                 tick1 = tick2;
  63.                                 sentlen = 0;
  64.                         }       
  65.                         ret = netconn_write(conn,send_buf,IPERF_BUFSZ,0);
  66.                         if (ret == ERR_OK)
  67.                         {
  68.                                 sentlen += IPERF_BUFSZ;
  69.                         }
  70.                 }
  71. // netconn_close(conn);
  72. // netconn_delete(conn);
  73.         }
  74. }
  75. void
  76. iperf_client_init(void)
  77. {
  78.         sys_thread_new("iperf_client", iperf_client, NULL, 2048, 4);
  79.        
  80. }
复制代码
代码清单 iperf_client.h 文件内容
  1. #ifndef IPERF_CLIENT_H
  2. #define IPERF_CLIENT_H
  3. void iperf_client_init(void);
  4. #endif /* IPERF_CLIENT_H */
复制代码
在main.c 文件中调用iperf_client_init() 函数即可,然后配置JPerf 软件成为客户端,让开发板进行毗连,开发板毗连的客户端IP 地址与端口号根据实际环境去配置即可,详细见图,从实验现象可以看出,发送的速率还是很快的(94Mbps,即11.5M 字节/秒)而且还很稳定。

图 NETCONN API 发送速率
测试开发板发送速率(Socket API)

本实验基于是一个实验,我们将测试开发板发送速率(NETCONN API)的工程拿过来,将iperf_client.c 的内容替换代码清单 所示的代码即可。
代码清单 iperf_client.c 文件内容
  1. #include "iperf_client.h"
  2. #include "lwip/opt.h"
  3. #include "lwip/sys.h"
  4. #include "lwip/api.h"
  5. #include <lwip/sockets.h>
  6. #define PORT 5001
  7. #define IP_ADDR "192.168.0.181"
  8. #define IPERF_BUFSZ (4 * 1024)
  9. static void iperf_client(void *thread_param)
  10. {
  11.         int sock = -1,i;
  12.         struct sockaddr_in client_addr;
  13.         uint8_t* send_buf;
  14.         u32_t tick1, tick2;
  15.         uint64_t sentlen;
  16.        
  17.         send_buf = (uint8_t *) pvPortMalloc(IPERF_BUFSZ);
  18.         if (!send_buf)
  19.                 return ;
  20.                
  21.         for (i = 0; i < IPERF_BUFSZ; i ++)
  22.                 send_buf[i] = i & 0xff;
  23.         while (1)
  24.         {
  25.                 sock = socket(AF_INET, SOCK_STREAM, 0);
  26.                 if (sock < 0)
  27.                 {
  28.                         printf("Socket error\n");
  29.                         vTaskDelay(10);
  30.                         continue;
  31.                 }
  32.                
  33.                 client_addr.sin_family = AF_INET;
  34.                 client_addr.sin_port = htons(PORT);
  35.                 client_addr.sin_addr.s_addr = inet_addr(IP_ADDR);
  36.                 memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));
  37.                 if (connect(sock,
  38.                                         (struct sockaddr *)&client_addr,
  39.                                         sizeof(struct sockaddr)) == -1)
  40.                 {
  41.                         printf("Connect failed!\n");
  42.                         closesocket(sock);
  43.                         vTaskDelay(10);
  44.                         continue;
  45.                 }
  46.                
  47.                 printf("Connect to iperf server successful!\n");
  48.                 tick1 = sys_now();
  49.                 while (1)
  50.                 {
  51.                         tick2 = sys_now();
  52.                         if (tick2 - tick1 >= configTICK_RATE_HZ * 5)
  53.                         {
  54.                                 float f;
  55.                                 f = (float)(sentlen*configTICK_RATE_HZ/125/(tick2 - tick1));
  56.                                 f /= 1000.0f;
  57.                                 printf("send speed = %.4f Mbps!\n", f);
  58.                                
  59.                                 tick1 = tick2;
  60.                                 sentlen = 0;
  61.                         }
  62.                        
  63.                         if (write(sock,send_buf,IPERF_BUFSZ) < 0)
  64.                                 break;
  65.                         else
  66.                         {
  67.                                 sentlen += IPERF_BUFSZ;
  68.                         }
  69.                 }
  70.                 closesocket(sock);
  71.         }
  72. }
  73. void
  74. iperf_client_init(void)
  75. {
  76.         sys_thread_new("iperf_client", iperf_client, NULL, 2048, 8);
  77. }
复制代码
测试效果详细见图,从两个实验的对比可以看出,基于Socket API 的发送速率根本相差无几。

图 Socket API 发送速率
提高LwIP 网络传输的速率

如果按照LwIP 默认的配置,是远不可能达到我们实验所表现的速率的,由于还没优化,那肯定也是不稳定的,下面我们来看看优化的参数,首先,网速一定受限于硬件,只有硬件是很好的,那么软件才能优化的更好,网卡肯定要选择好一点的网卡,然后在工程中的stm32f4xx_hal_config.h文件中配置以太网发送和吸收的缓冲区大小,默认是4,我们可以稍微改大一点,详细见代码清单。
代码清单stm32f4xx_hal_config.h 文件配置参数
  1. #define ETH_RXBUFNB ((uint32_t)8U) /* 接收缓冲区*/
  2. #define ETH_TXBUFNB ((uint32_t)8U) /* 发送缓冲区*/
复制代码
此外,还需在lwipopts.h 文件中配置LwIP 的参数,详细见代码清单,首先,我们对LwIP管理的内存肯定要分配的大一些,而对于发送数据是存储在ROM 或者静态存储区的时间,还要将MEMP_NUM_PBUF 宏定义改的大一点,当然发送缓冲区大小和发送缓冲区队列长度决定了发送速率的大小,根据差异需求进行配置,而且需要不断调试,而对于吸收数据的配置,应该配置TCP 缓冲队列中的报文段数量与TCP 吸收窗口大小,特别是吸收窗口的大小,这直接可以影响数据的吸收速率。
代码清单 lwipopts.h 文件配置参数
  1. //内存堆heap 大小
  2. #define MEM_SIZE (25*1024)
  3. /* memp 结构的pbuf 数量, 如果应用从ROM 或者静态存储区发送大量数据时
  4. 这个值应该设置大一点*/
  5. #define MEMP_NUM_PBUF 25
  6. /* 最多同时在TCP 缓冲队列中的报文段数量*/
  7. #define MEMP_NUM_TCP_SEG 150
  8. /* 内存池大小*/
  9. #define PBUF_POOL_SIZE 65
  10. /* 每个pbuf 内存池大小*/
  11. #define PBUF_POOL_BUFSIZE \
  12. LWIP_MEM_ALIGN_SIZE(TCP_MSS+40+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)
  13. /* 最大TCP 报文段,TCP_MSS = (MTU - IP 报头大小- TCP 报头大小*/
  14. #define TCP_MSS (1500 - 40)
  15. /* TCP 发送缓冲区大小(字节) */
  16. #define TCP_SND_BUF (11*TCP_MSS)
  17. /* TCP 发送缓冲区队列的最大长度*/
  18. #define TCP_SND_QUEUELEN (8* TCP_SND_BUF/TCP_MSS)
  19. /* TCP 接收窗口大小*/
  20. #define TCP_WND (11*TCP_MSS)
复制代码
当然,除此之外,想要整个LwIP 能高速平稳运行,只配置这些是不够的,好比我们应该使用停止的方式吸收数据,这就省去了CPU 查询数据,而且,我们应该将内核邮箱的容量增大,这样子在吸收到数据之后,投递给内核就不会由于无法投递而阻塞,同时内核线程的优先级应该设置得更高一点,这样子就能及时去处理惩罚这些数据,当然,我们也可以独立使用一个新的发送线程,这样子内核就无需调用底层网卡函数,它可以用心处理惩罚数据,发送数据的事情就交由发送线程去处理惩罚,同时,在处理惩罚数据的时间,不使用串口打印信息,由于串口是一个很慢的外设,当然啦,关于提高LwIP 网络传输的速率,还有很多东西要优化的,这也跟使用环境有关系,不能一概而论,只是给出一些方向,详细怎么实现,还需要各人亲身实践去调试。

参考资料wIP 应用开发实战指南—基于野火STM32

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

怀念夏天

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表