问题的发现
大部分新手小白使用ZYNQ的PS端实现TCP客户端时,会参考Xilinx的lwIP TCP Perf Client
例程,把它的整体代码搬过来,我再进行相关的项目开发的时候,也是这样,一开始只是想先测测板子实际能跑的带宽,而例程中的transfer_data()
函数也确实是放在while(1)中循环运行,就没太关注,而在后面需要在指定的时刻发送数据时,发现哪怕仅仅是执行一次transfer_data()
函数,网络调试助手也会不断地收到数据。下面对这个问题进行分析并提出解决方案。
问题分析
先来看一看整个例程涉及到发送的地方。
例程把它放在了while(1)中,只要不是快速定时器与慢速定时器设置的时间到了,都是一直循环运行它以及另外一个处理输入的函数xemacif_input(netif)
,考虑到这整个例程是一个测速的程序,本身就应该是一直不断地发送数据,这里看上去没有什么问题,那就进入到transfer_data()
函数内部看看。
void transfer_data(void)
{
if (client.client_id)
tcp_send_perf_traffic();
}
函数比较简单,即这个client_id
不为0时,就运行tcp_send_perf_traffic()
。这个client_id
通过查找可以发现,它在while(1)上的start_application()
函数中进行初始化赋值为0,在连接成功回调函数tcp_client_connected
中+1,即连接成功后,发送函数就会不断地运行tcp_send_perf_traffic()
。那我们再进入其看看。
static err_t tcp_send_perf_traffic(void)
{
err_t err;
u8_t apiflags = TCP_WRITE_FLAG_COPY | TCP_WRITE_FLAG_MORE;
if (c_pcb == NULL) {
return ERR_CONN;
}
#ifdef __MICROBLAZE__
/* Zero-copy pbufs is used to get maximum performance for Microblaze.
* For Zynq A9, ZynqMP A53 and R5 zero-copy pbufs does not give
* significant improvement hense not used. */
apiflags = 0;
#endif
while (tcp_sndbuf(c_pcb) > TCP_SEND_BUFSIZE) {
err = tcp_write(c_pcb, send_buf, TCP_SEND_BUFSIZE, apiflags);
if (err != ERR_OK) {
xil_printf("TCP client: Error on tcp_write: %d\r\n",
err);
return err;
}
err = tcp_output(c_pcb);
if (err != ERR_OK) {
xil_printf("TCP client: Error on tcp_output: %d\r\n",
err);
return err;
}
client.total_bytes += TCP_SEND_BUFSIZE;
client.i_report.total_bytes += TCP_SEND_BUFSIZE;
}
if (client.end_time || client.i_report.report_interval_time) {
u64_t now = get_time_ms();
if (client.i_report.report_interval_time) {
if (client.i_report.start_time) {
u64_t diff_ms = now - client.i_report.start_time;
u64_t rtime_ms = client.i_report.report_interval_time;
if (diff_ms >= rtime_ms) {
tcp_conn_report(diff_ms, INTER_REPORT);
client.i_report.start_time = 0;
client.i_report.total_bytes = 0;
}
} else {
client.i_report.start_time = now;
}
}
if (client.end_time) {
/* this session is time-limited */
u64_t diff_ms = now - client.start_time;
if (diff_ms >= client.end_time) {
/* time specified is over,
* close the connection */
tcp_conn_report(diff_ms, TCP_DONE_CLIENT);
xil_printf("TCP test passed Successfully\n\r");
tcp_client_close(c_pcb);
c_pcb = NULL;
return ERR_OK;
}
}
}
return ERR_OK;
}
虽然比较长,但实际也就包含了两个部分,一个部分是当发送buffer足够时,循环地把发送数组大小的数据发送出去,另一部分则是测速部分,我们这里不过多关注。但问题来了,这看上去并没有什么问题,虽然发送buffer足够时会一直进行发送,但其会在buffer不足后跳出while循环,再返回,而例程设置的发送buffer本身也不大,只有65535Bytes,发送数组长度为7300,应该发送8组后就停了下来,正当百思不得其解时,发现在这个函数下面还有一个函数tcp_client_sent
/** TCP sent callback, try to send more data */
static err_t tcp_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
return tcp_send_perf_traffic();
}
其英文注释写的很明白,这是TCP发送成功的回调函数,而该例程在此直接返回了上面讨论的tcp_send_perf_traffic()
,尝试发送更多的数据,即每成功发送出去了7300Bytes后,也会再执行一次发送函数,这也就导致我们把其移植到自己的工程,想根据我们的需要进行发送时,哪怕只执行一次发送函数transfer_data()
,也会一直发送数据。
问题的解决
知道了问题的产生原因,那解决起来就很容易了,我们只要修改发送成功的回调函数tcp_client_sent
,将其直接改为一下形式即可:
/** TCP sent callback, try to send more data */
static err_t tcp_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
return;
}