关于LWIP协议栈连续多次tcp_write后失败的解决过程

前段时间一直在调试lwip协议栈的问题,在stm32F107上实现一个C/S 架构的通信程序。项目初期的时候设计的是B/S架构的控制,然后在使用过程中发现了些限制,因为芯片自身的RAM有限,所以跑B/S的server端略显压力,为了处理类似动态网页内容,开辟一个5K的缓冲区,然后一次tcp_write就可以将内容发送给浏览器了,当然网页内容也是比较简单,考虑到后续可能会有更多的数据处理,故决定开发一个C/S架构的控制。

         上位机client倒是没什么太多可说的,自己封装下基本的winsock操作。考虑到用TCP协议传输简单地封装了下数据封包和拆包的协议,然后MFC作为图形界面。在stm32端主要采用lwip的RAW API,然后利用callback的方式处理接收上位机命令、数据后的处理,初始化服务器的代码如下:
         void Server_init(void)
{
                  struct tcp_pcb *pcb;
                  pcb = tcp_new(); // 动态创建一个pcb
                  tcp_bind(pcb, IP_ADDR_ANY, 8082);  // 绑定端口8082
                  pcb = tcp_listen(pcb); // 开始监听
                  tcp_accept(pcb, Server_accept); // accept成功时的回调函数                                       
}
然后在Server_accept中也主要是初始化一些回调函数,
         static err_t Server_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
                  tcp_err(pcb, Server_conn_err); // 错误时的回调函数
                  tcp_recv(pcb, Server_recv); // 接收到数据后的回调函数
                  tcp_sent(pcb, Server_sent); // tcp_write数据成功发送后的回调函数
                  gRemoteIp = pcb->remote_ip; // 获取远程客户端的地址
                  return ERR_OK;
}
最重要的函数就是Server_recv()了,在这个函数中,根据客户端不同的命令,然后处理相应的数据发送给客户端,但这是问题就暴露出来了。截取一段发送数据的简化代码:
for(i = 0; i < WMFlag.WM_Record_Num; ++i) {
         SendCharBuff(pcb, WMTempData, strlen(WMTempData), PT_TEXT);
}
其中SendCharBuff主要是调用tcp_write函数,这个当WM_Record_Num这个数值很大时,客户端总是接收不全,后来经过反复地进行实验发现,然来是tcp_write这个函数在循环到12次的时候会返回ERR_MEM的内存错误,这个问题让我百思不得其解,然后通过网上的一些资料,很多人说是lwip协议栈有BUG,然后我姑且相信了这个结论,但是有BUG也得继续调呀。于是便想到了winsock里面有个WSAGetLastError()这个函数,但是lwip里面却没有,网上找了找说可以开启lwip的调试功能,于是乎就开始设置lwip的调试功能了。
关于开启LWIP的调试功能主要的设置如下:
1、  在src/include/lwip目录中找到debug.h这个文件,然后在里面添加如下代码
// Add By 风格独特 2012-06-28
// 增加串口调试功能
// 声明外部的USART2_Printf串口输出调试信息函数
extern void USART2_Printf(const char *format, ...);
 
// 定义格式化内容的宏
// PS 通过调试这个宏发现了以前不知道的一个小知识,那就是字符串可以这样表示
// “aaaa” “bbb” == “aaaabbb”
#define U8_F "c"
#define S8_F "c"
#define X8_F "x"
#define U16_F "u"
#define S16_F "d"
#define X16_F "x"
#define U32_F "u"
#define S32_F "d"
#define X32_F "x"
 
// 设置调试的宏,注释这个宏可以关闭调试功能
#define LWIP_DEBUG
 
#define LWIP_PLATFORM_DIAG(x) USART2_Printf x
// Add End
 
2、  在src/include/lwip目录中找到opt.h这个文件,然后找到如下代码
#ifndef TCP_OUTPUT_DEBUG
#define TCP_OUTPUT_DEBUG                LWIP_DBG_OFF
#endif
         其中将LWIP_DBG_OFF改为LWIP_DBG_ON,即开启了TCP_OUT_DEBUG的调试,当然如果想开启其他的调试输出,就可以将相应的地方改为LWIP_DBG_ON即可。
3、  实现USART2_Printf这个函数,代码如下
void USART2_Printf(const char *format,...)
{
         int iOutLen;
         char buf[256];
         va_list arg_ptr;
        
         va_start(arg_ptr, format);
         iOutLen = vsprintf(buf, format, arg_ptr);
         va_end(arg_ptr);
         USART_SendStr(buf, iOutLen);
}
开启调试后得到如下的调试输出信息:
         tcp_enqueue: 12 (after enqueued)
tcp_write(pcb=20009c1c, data=08003a24, len=6, apiflags=1)
tcp_enqueue(pcb=20009c1c, arg=08003a24, len=6, flags=0, apiflags=1)
tcp_enqueue: queuelen: 12
tcp_enqueue: too long queue 12 (max 12)
tcp_output_segment: 6848:6920
State: ESTABLISHED
可以看出在tcp_enqueue第12次的时候输出了too long queue 12 (max 12),超出最大的列队次数,于是在工程中搜索too long queue这句话,在tcp_out.c文件中找到了如下代码:
if ((queuelen > TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_enqueue: queue too long %"U16_F" (%"U16_F")\n", queuelen, TCP_SND_QUEUELEN));
goto memerr;
}
然后发现TCP_SND_QUEUELEN的值为12,跟到TCP_SND_QUEUELEN的定义代码:
#define TCP_SND_BUF             (2*TCP_MSS)  // 发送缓冲区,为两个MSS的大小
 
// 此参数限制了tcp_write的次数,系数默认为6, 改为3000
#define TCP_SND_QUEUELEN        (3000 * TCP_SND_BUF)/TCP_MSS
于是将那个默认的系数由6改为3000,再次测试时发现可以连续tcp_write超过12次了,于是解决了tcp_write的连续多次调用后失败的问题。
最后补充一下tcp_write这个函数的最后一个参数的说明,该函数的声明如下
err_t tcp_write(struct tcp_pcb *pcb, void *dataptr, u16_t len, u8_t copy);
其中第四个参数是一个copy参数,当为0时为不拷贝数据,也就是在dataptr所指的缓冲区里面发送数据,因为调用tcp_write成功后数据并不会立即发送,所以要确保dataptr所指的缓冲区内容保持不变,如果调用tcp_write成功后,再改变dataptr缓冲区可能就会和预期发送的数据不相符,当时我也碰到过这个问题,后来将最后的参数改为1,为1时即拷贝缓冲区内容,当执行tcp_write时,会将dataptr所指向的缓冲区内容先拷贝到发送的缓冲区中,这样的话执行tcp_write之后再改变dataptr所指的内容是不影响数据的正确发送的。
  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
LWIP_DEBUGF是一个宏定义,用于在lwIP协议栈中输出调试信息。它的定义如下: ```c #define LWIP_DEBUGF(debug, message) \ do { \ if ((debug) && (lwip_debug)) { \ LWIP_PLATFORM_DIAG((_U32)"lwIP: ", message); \ } \ } while (0) ``` 其中,debug是一个表示调试级别的参数,message则是要输出的调试信息。在使用LWIP_DEBUGF输出调试信息时,需要先定义LWIP_DEBUG宏来开启调试功能,并设置输出级别。在lwipopts.h文件中,可以找到如下代码: ```c #define LWIP_DEBUG 0 ``` 将LWIP_DEBUG的值设置为1,即可开启调试功能。同时,还需要根据需要设置输出级别,可以在lwipopts.h文件中找到类似如下代码: ```c #define ETHARP_DEBUG LWIP_DBG_OFF #define NETIF_DEBUG LWIP_DBG_ON #define PBUF_DEBUG LWIP_DBG_OFF #define API_LIB_DEBUG LWIP_DBG_OFF #define API_MSG_DEBUG LWIP_DBG_OFF #define SOCKETS_DEBUG LWIP_DBG_OFF #define ICMP_DEBUG LWIP_DBG_OFF #define IGMP_DEBUG LWIP_DBG_OFF #define INET_DEBUG LWIP_DBG_OFF #define IP_DEBUG LWIP_DBG_OFF #define IP_REASS_DEBUG LWIP_DBG_OFF #define RAW_DEBUG LWIP_DBG_OFF #define MEM_DEBUG LWIP_DBG_OFF #define MEMP_DEBUG LWIP_DBG_OFF #define SYS_DEBUG LWIP_DBG_OFF #define TCP_DEBUG LWIP_DBG_OFF #define TCP_INPUT_DEBUG LWIP_DBG_OFF #define TCP_FR_DEBUG LWIP_DBG_OFF #define TCP_RTO_DEBUG LWIP_DBG_OFF #define TCP_CWND_DEBUG LWIP_DBG_OFF #define TCP_WND_DEBUG LWIP_DBG_OFF #define TCP_OUTPUT_DEBUG LWIP_DBG_OFF #define TCP_RST_DEBUG LWIP_DBG_OFF #define TCP_QLEN_DEBUG LWIP_DBG_OFF #define UDP_DEBUG LWIP_DBG_OFF #define TCPIP_DEBUG LWIP_DBG_OFF #define SLIP_DEBUG LWIP_DBG_OFF #define DHCP_DEBUG LWIP_DBG_OFF #define AUTOIP_DEBUG LWIP_DBG_OFF #define DNS_DEBUG LWIP_DBG_OFF #define IP6_DEBUG LWIP_DBG_OFF #define DHCP6_DEBUG LWIP_DBG_OFF #define MLD6_DEBUG LWIP_DBG_OFF #define ICMP6_DEBUG LWIP_DBG_OFF #define ND6_DEBUG LWIP_DBG_OFF #define UDP6_DEBUG LWIP_DBG_OFF #define TCP6_DEBUG LWIP_DBG_OFF ``` 其中,每个宏定义了一个调试输出级别,可以根据需要将其设置为LWIP_DBG_ON、LWIP_DBG_OFF或LWIP_DBG_TRACE。例如,将IP_DEBUG的值设置为LWIP_DBG_ON,则可以开启IP协议相关的调试输出。在需要输出调试信息的地方,可以使用LWIP_DEBUGF宏来输出信息。例如: ```c LWIP_DEBUGF(IP_DEBUG, ("Received packet of length %d\n", p->tot_len)); ``` 这条语句将输出一个类似于“lwIP: Received packet of length 128”这样的调试信息,其中128是p->tot_len的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值