基于lwip的TCP服务器性能测试实验
上一章的lwip Echo Server实验让我们对lwip有一个基本的了解,而Echo Server是基于TCP协议的。TCP协议是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。本章我们将了解lwip tcp协议的使用并测试lwip tcp服务的性能。本章分为以下几个部分:
- 简介
- 实验任务
- 硬件设计
- 软件设计
- 下载验证
简介
- TCP 协议简介TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在开放系统互连OSI参考模型中,它完成第四层传输层所指定的功能,UDP(User Datagram Protocol,用户数据报协议)是同一层内另一个重要的传输协议。在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。
应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元(MTU)的限制)。之后TCP把报文段传给IP 层,由它来通过网络将报文段传送给接收端的TCP层。TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端的数据包能被按序接收。然后接收端对已成功收到的字节发回一个相应的确认(ACK);如果发送端在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。
在数据正确性与合法性上,TCP用一个校验和函数来检验数据是否有错误,在发送和接收时都要计算校验和。在保证可靠性上,采用超时重传和捎带确认机制。在流量控制上,采用滑动窗口协议,协议中规定,对于窗口内未经确认的分组需要重传。在拥塞控制上,采用TCP拥塞控制算法。
作为与TCP协议处于同一层的UDP协议,我们简单的介绍下两者的区别:
1、TCP协议面向连接,是流传输协议,通过连接发送数据,而UDP协议传输不需要连接,是数据报协议;
2.TCP为可靠传输协议,而UDP为不可靠传输协议。即TCP协议可以保证数据的完整和有序,而UDP不能保证;
3.UDP由于不需要连接,故传输速度比TCP快,且占用资源比TCP少;
4.应用场合:TCP协议常用在对数据文件完整性较高的一些场景中,如文件传输等。UDP常用于对通讯速度有较高要求或者传输数据较少时,比如对速度要求较高的视频直播和传输数据较少的QQ等。 - TCP首部 图33.1.1体现了以太网协议体系分层设计的思想。发送端在应用层将需要传输的数据传给传输层,传输层对应用层的数据进行处理、封装,如果使用TCP协议,则对数据处理后加上TCP标识头进行封装,如果使用UDP协议,则添加UDP标识头进行封装,封装后的数据传递给网络层。网络层对传输层下发的数据处理后添加IP标识头进而传递给数据链路层。数据链路层将网络层下发的数据打包成以太网数据包递交给物理层传输。接收端接收到以太网数据包后进行反向操作从而得到发送端应用层发送的数据。
图33.1.1 以太网包数据格式
下图是TCP报文段标识头,在没有选项的情况下,它通常是20个字节。
图33.1.2 TCP报文段标识头
源端口号和目的端口号用于寻找发送端和接收端的应用进程,这个和UDP报文相同,这两个值加上IP首部中的源IP地址和目的IP地址唯一确定一个TCP连接。
序列号字段用来标识从TCP发送端向TCP接收端发送的数据字节流,它表示在这个报文段中的第一个数据字节。当建立一个新的连接时,握手报文中的SYN标志置1,这个握手报文中的序号字段为随机选择的初始序号ISN(Initial Sequence Number),当连接建立好以后发送方要发送的第一个字节序号为ISN+1。
确认号字段只有在ACK为1的时候才有用,确认号中包含发送确认的一方所期望收到的下一个序号,确认号是在上一次成功接收到的数据字节序列号上加一,例如上次接收成功接收到对方发过来的数据序号为X,那么返回的确认号就应该为X+1。
首部长度中给出了首部的长度,以4字节为单位,这个字段有4bit,因此TCP最多有60字节的首部((2^4-1)*4 = 60字节)。如果没有任何的选项字段,正常的首部长度是20字节。
TCP首部中还有6个标志比特,这6个标志位的说明如下表所示。
表33.1.1 TCP 首部标志位说明
标志位 |
说明 |
URG |
该位置1时表示紧急指针有效 |
ACK |
该位置1表示确认序号字段有效 |
PSH |
改为置1表示接收方应该尽快将这个报文段交给应用层 |
RST |
该位置1表示重建连接 |
SYN |
用来发起连接 |
FIN |
发送端完成发送任务,终止连接 |
窗口尺寸也就窗口大小,其中填写相应的值以通知对方自己期望接收的字节数,窗口大小字段是TCP流量控制的关键字段,窗口大小是一个16bit的字段,因此窗口大小最大为65535字节。
16位的校验和覆盖了整个TCP报文段:TCP首部和TCP数据。校验和首先在数据发送方通过特殊的算法计算得出,在传递到接收方之后,还需要再重新计算。如果某个数据报在传输过程中被第三方篡改或者由于线路噪音等原因受到损坏,发送和接收方的校验计算和将不会相符,由此UDP协议可以检测是否出错。
紧急指针只有在URG置1时有效,紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。
- LWIP中RAW API编程接口中与TCP相关的函数
LWIP提供了很多关于TCP的RAW API编程函数,我们可以使用这些函数来完成有关TCP的实验,我们在表列出了一部分函数。
表33.1.2 TCP RAW API函数
函数分组 |
API 函数 |
函数功能描述 |
TCP连接建立 |
tcp_new() |
创建一个TCP的PCB控制块 |
tcp_bind() |
为TCP的PCB控制块绑定一个本地IP地址和端口号 |
|
tcp_listen() |
开始TCP的PCB监听 |
|
tcp_accept() |
控制块accept字段注册的回调函数,侦听到连接时被调用 |
|
tcp_conect() |
连接远端主机 |
|
发送TCP数据 |
tcp_write() |
构造一个报文并放到控制块的发送缓冲队列中 |
tcp_sent() |
控制块sent字段注册的回调函数,数据发送成功后被回调 |
|
tcp_output() |
将发送缓冲队列中的数据发送出去 |
|
接收TCP数据 |
tcp_recv() |
控制块recv字段注册的回调函数,当接收到新数据时被调用 |
tcp_recved() |
当程序处理完数据后一定要调用这个函数,通知内核更新接收窗口 |
|
轮询函数 |
tcp_poll() |
控制块poll字段注册的回调函数,该函数周期性调用 |
关闭和中止连接 |
tcp_close() |
关闭一个TCP连接 |
tcp_err() |
控制块err字段注册的回调函数,遇到错误时被调用 |
|
tcp_abort() |
中断TCP连接 |
TCP连接由协议控制块(PCB)识别。有两种方法可以建立连接
- 被动连接(服务端)整个过程可分为以下几个步骤:
- 调用tcp_new函数来创建一个pcb
- (可选)调用tcp_arg函数以将特定于应用程序的值与pcb相关联
- 调用tcp_bind函数指定本地IP地址和端口
- 调用tcp_listen函数或tcp_listen_with_backlog函数
- 调用tcp_accept函数以指定新连接到达时要调用的函数
- 主动连接(客户端)
整个过程可分为以下几个步骤:
- 调用tcp_new函数来创建一个pcb
- (可选)调用tcp_arg函数以将特定于应用程序的值与pcb相关联
- (可选)调用tcp_bind函数以指定本地IP地址和端口
- 调用tcp_connect函数
实验任务
本章的实验任务是使用VITIS软件自带的Lwip TCP perf Server模版了解TCP服务器的性能。
硬件设计
本章的硬件设计与前一章《基于lwip的echo server实验》相同,此处不再赘述。将《基于lwip的echo server实验》的Vivado工程另存为“lwip_tcp_server_perf”,然后导出硬件平台文件到vitis文件夹,打开Vitis,进入软件设计。
软件设计
在菜单栏中选择“File->New->Application Project”, 新建一个Vitis应用工程。
在弹出的下图所示界面中,输入工程名“lwip_tcp_server_perf”,其它选项保持默认,点击“Next”。
图33.4.1 配置工程
然后添加应用平台文件,添加完成后,接下来依次点击“Next>”,直到弹出选择模板界面,选择工程模版“lwIP TCP Perf Server”,然后点击“Finish”按钮,如图33.4.2所示。
LwIP TCP Perf Server应用程序用于创建TCP服务器并使用轻量级IP堆栈(lwIP)测试下行链路性能。该应用程序将MPSOC开发板MAC地址设置为00:0a:35:00:01:02,默认使用DHCP获取动态IP地址,如果DHCP失败,则使用默认设置的静态IPv4地址192.168.1.10。应用程序在MPSOC开发板上创建TCP服务器并侦听TCP客户端的连接,一旦远程客户端与此服务器连接,TCP服务器将开始从客户端接收数据。客户端连接的详细信息和数据传输统计信息将由服务器在串行控制台上显示。
图33.4.2 选择“lwIP TCP Perf Server”模版
展开lwip_tcp_server_perf应用工程目录下的src目录,可以看到很多平台相关的文件(主要是platform开头的文件)。为了方便分析,我们将src文件夹中与本实验不相关的平台文件删除,删除后的src文件夹内容如下图所示:
图33.4.3 删除后的src文件夹内容
该实验的main.c文件与《lwip echo server》实验的main.c基本相同,无实质性变化,由于在《lwip echo server》实验已讲解了main.c的内容,此处我们就不赘述了。本章我们重点关注的是lwip raw接口下tcp的使用,创建tcp服务器和测试性能主要在tcp_perf_server.c文件中实现。在讲解tcp_perf_server.c文件之前我们先来看一下当使用lwip作为TCP服务器时,会话的建立过程。
由于raw TCP实现主要通过回调执行,因此其操作往往与各个消息的接收和处理密切相关。因此,熟悉底层TCP协议是有帮助的。对于没有lwIP使用经验的人来说,有时并不清楚什么时候需要调用什么。下表显示了远程客户端和本地lwIP tcp服务器之间交互的顺序图。
表33.4.1 lwIP会话建立(远程客户端/本地lwIP服务器)
远程 客户端 |
<TCP消息> |
lwIP堆栈动作 |
lwIP服务器操作 |
描述 |
<= tcp_new_ip_type () |
创建TCP PCB |
|||
<= tcp_bind() |
绑定端口号 |
|||
<= tcp_listen_with_backlog() |
创建侦听端点(分配新的PCB) |
|||
<= tcp_arg() |
设置回调参数 |
|||
<= tcp_accept() |
设置接受回调 |
|||
连接=> |
客户端连接到服务器 |
|||
SYN => |
远程堆栈发送SYN |
|||
分配新PCB |
lwIP创建“pending”会话 |
|||
<= SYN/ACK |
lwIP用SYN/ACK响应 |
|||
<=连接返回 |
远程堆栈通知客户端连接成功 |
|||
ACK => |
远程堆栈发送ACK以完成3次握手 |
|||
调用接受回调=> |
lwIP通知应用程序有新会话(使用新PCB) |
|||
<= tcp_arg() |
服务器分配新的会话PCB,设置新的回调参数 |
|||
<= tcp_recv() |
服务器设置recv回调 |
|||
<= tcp_err() |
服务器设置错误/中止回调 |
|||
<=服务器从具有OK状态的接受回调返回 |
||||
标记PCB激活 |
||||
连接已建立(数据现在可以由任何一方发送) |
||||
发送=> |
TCP数据=> |
客户端发送请求数据 |
||
调用recv回调=> |
||||
<= tcp_write() |
服务器将响应数据写入客户端 |
|||
将TCP段排入队列 |
||||
<= tcp_write() |
服务器写入更多数据 |
|||
将TCP段排入队列 |
TCP段可以与前一段组合 |
|||
<= tcp_recved() |
服务器通知lwIP更新接收窗口 |
|||
<=服务器从具有OK状态的recv回调返回 |
||||
<= TCP数据 |
发现要发送的排队段 |
lwIP将数据段发送到客户端,包括先前接收的客户端数据的ACK |
现在对照上表我们来分析tcp_perf_server.c文件的代码。
首先我们来看start_application函数,该函数在main函数中调用,后面的函数基本上都是通过该函数回调使用。start_application函数代码如下:
可以看到函数的调用与表中的“lwIP服务器操作”列的函数顺序一致。其中tcp_new_ip_type函数与我们在表中列的tcp_new函数有点区别,tcp_new_ip_type函数不仅具有tcp_new函数的功能,而且可以指定侦听的IP地址的类型,是用IPv4(IPADDR_TYPE_V4)、IPv6(IPADDR_TYPE_V6)还是两者都可以(IPADDR_TYPE_ANY),IPADDR_TYPE_ANY只有在lwip开启了IPv6功能才可以使用,本实验因为我们没有使能IPv6功能,所以tcp_new_ip_type函数的IPADDR_TYPE_ANY不起作用,此处调用该函数主要是考虑兼容性,如果只用IPv4,可以调用tcp_new函数。
tcp_bind函数用于绑定本地端口号和IP地址,IP_ADDR_ANY表示任意本地地址,TCP_CONN_PORT在tcp_perf_server.h头文件中宏定义为5001,该端口是我们后面使用的iperf工具的默认连接端口。
tcp_listen_with_backlog函数的第二个参数为8位二进制的最大值0xff。由于本实验实现的功能是测试TCP的性能,所以此处将连接队列限制设置为1,以便一次为一个客户端提供服务。
tcp_accept函数用于设置接受(accept)回调函数tcp_server_accept。
从表可以看到,执行start_application函数后,客户端就可以发起连接,然后底层堆栈与客户端进行三次握手,握手成功后,调用接受回调,也就是调用tcp_server_accept函数,该函数代码如下:
该函数的主要作用是设置recv接收回调和错误中止回调。代码第180-191行用于初始化测试TCP性能的相关数据,INTERIM_REPORT_INTERVAL参数在tcp_perf_server.h中宏定义如下:
该值代表每隔多长时间打印一次报告,也就是临时报告,此处的5代表5秒,当然也可以修改成其它值。
从表可以看到,设置好tcp_server_accept的最后三个函数后,连接就正式建立了,这时候就可以互通有无了。此时如果客户端发起数据传输,lwip堆栈调用recv回调,也就是tcp_recv(c_pcb, tcp_recv_perf_traffic)函数中的tcp_recv_perf_traffic函数,该函数代码如下:
由于本实验的功能是使用lwip测试TCP服务器的链路性能,测试的方法是接收客户端发来的数据流,对该数据流以每隔INTERIM_REPORT_INTERVAL时间统计一次客户端在该时间段内发送的数据总字节数,从而统计该段时间内的平均带宽,并打印该段时间内的临时报告。除此之外,还统计整个测试时间内的总字节数,从而统计整个测试时间内的平均带宽,并打印最终的测试报告。所以该函数的主要作用就是对接收到的数据进行处理。代码第153行调用的tcp_conn_report函数内容实现如下:
该函数的主要作用是打印测试报告信息。代码第87行调用的stats_buffer函数的主要作用是进行数据换算,包括单位换算,如将bit数换算成合适的单位,如Mbit,其实现如下:
注意代码第46-49行的unit数值的差异,当测试速度时,单位是1000,而不是统一的用1024,这在下载验证时会体现出来。
从表可以看到,调用接收回调后,如果客户端需要请求数据的话就需要调用tcp_write()函数响应数据请求。本实验因客户端无数据请求,因此无需调用tcp_write()函数。tcp_recved()函数在接收回调函数tcp_recv_perf_traffic中实现,见代码第164行。
如果客户端结束连接,就根据代码第199行的tcp_err(c_pcb, tcp_server_err)函数,调用错误/中止回调函数tcp_server_err,结束连接。tcp_server_err函数及其相关函数实现如下:
可以看到tcp_server_err函数对接收到的客户端的数据做最后的处理,并调用tcp_server_close函数关闭会话。
至此,tcp_perf_server.c文件的主要代码内容就讲完了,接下来进行下载验证,看一下tcp服务器的链路性能。
下载验证
首先我们将下载器与MPSOC开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB UART接口与电脑连接,用于串口通信。使用网线一端连接MPSOC开发板的以太网接口,另一端与电脑或路由器连接。最后连接开发板的电源,给开发板上电。
在Vitis软件的下方的Vitis Serial Terminal窗口中点击右上角的加号连接串口。然后下载本次实验的elf文件。下载完成后,可以看到串口打印的结果。
图33.5.1 显示打印结果
从串口打印的倒数第二行可以看到,TCP服务器侦听的端口号为5001。最后一行是本实验的关键命令,用于测试TCP链路性能。此时我们需要一个测试的工具iperf。iperf是一种用于主动测量IP网络上可达到的最大带宽的工具。它支持调整与时序、协议和缓冲区相关的各种参数。对于每个测试,它报告测量的吞吐量/比特率、损耗和其它参数。利用 Iperf这一特性,可以用来测试一些网络设备如路由器、防火墙、交换机等的性能。
由于iperf是第三方工具,需要我们手动安装,可以在开发板随附的资料“6_软件资料/1_软件”中找到,名为iperf.exe即是,直接将iperf.exe复制到C:\Windows目录即可。
iperf常用参数列在下表:
参数 |
描述 |
-s |
以服务器模式启动 |
-c |
以客户端模式启动 |
-u |
使用udp协议 |
-b |
指定传输带宽(UDP) |
-t |
指定测试时长(单位:秒) |
-i |
设置报告间隔时间(单位:秒) |
-p |
设置端口,默认是5001端口 |
-w |
对于TCP,设置TCP窗口大小 |
我们打开电脑的CMD(按win+r键后输入cmd),输入“iperf -c 192.168.1.10 -i 5 -t 30 -w 2M”,如下图所示:
图33.5.2 进行iperf测试
该命令指示iperf以客户端模式启动,连接到服务器192.168.1.10,指定TCP窗口大小为2Mbyte(参数-w 2M), 测试30秒(-t 30),因为300秒太长,不方便截图,所以改为30秒,每隔5秒打印一次输出(-i 5)。
回车后,打印连接信息,并启动测试,如下图所示:
图33.5.3 iperf测试结果
可以看到,打印出来的测试信息分为四列,分别是ID、Interval、Transfer和Bandwidth。ID用于标识测试的连接,Interval是测试时间段,由于每隔5秒打印一次输出(-i 5),所以Transfer表示该时间段内传输的数据总量,Bandwidth为该时间段内的平均带宽。
由于只测试了30秒,可以看到该段时间内带宽稳定在949Mbits/sec,对于千兆网来说,是一个合理值。另外Vitis的串口终端也打印出了信息,如下图所示:
图33.5.4 Vitis打印iperf测试结果