使用LwIP回调编程实现paho开源MQTT库的移植

来源:互联网 发布:onvif java libraries 编辑:程序博客网 时间:2024/05/22 05:52
文章内容为工程调试记录,内容排版散乱,阅读慎入!

LwIP Raw回调编程调试记录

1. tcp_connect连接问题:

1.1 连接不上,没有任何的回调函数调用,延时可以偶尔的解决这个问题
解决思路:在软件定时器中定时检测TCP_PCB块的状态

1.2 服务器主动断开的话,在tcp_recv_cb中会收到无数据响应
此处判断其状态应该是已经断开

1.3 tcpip_callback做何用,屏蔽之后带来的影响
该函数往tcpip线程中发送了一条邮箱,tcpip线程收到之后,调用传入的回调函数将当前申请的tpcb加入到tcp_tw_pcbs链表中,并在tcpip进程中注册一个定时器事件作为当前pcb连接的内核定时器。通过后续的分析,发现当前tpcb建立连接后,连接成功回调函数立马将当前tpcb从tcp_tw_pcbs中移除。可见,其本意是通过tcpip回调的方式为当前tpcb开启一个内核定时器。因为在普通进程或函数中调用的tcp_connect虽然也有TCP_REG的调用,但却是把当前tpcb的软件定时器事件挂在到当前线程的软件定时器链表上,如果当前调用tcp_connect的进程不是通过sys_thread_new建立的,或者就是个普通函数,这时候连软件定时器链表也找不到,自然也没有定时事件的触发,这也是困扰我多日为什么tpcb的内核定时器没有启动的原因。

1.4 2个软件定时器分别负责断开检测及重发
工程自带有ttcp.c例程用于测试raw tcp,而自己也主要参考该例程编写,此处困扰我多日的一点是:采用了软件定时器来处理数据的续发。因为数据可能无法一次性传完,ttcp.c中采用了软件定时器来发送当前要发送数据的剩余数据,在实际测试中大多数时候是没问题的,但是参照该种方式,却时不时的出现数据无法发送。调试发现是发送缓存区满,抓包发现是服务器不停的回复一个ACK值,而lwip客户端没有重发,经过老衲五木的提醒,意识到lwip中tcp内核的数据处理是在一个进程中完成的,频繁的触发定时器来执行tpc_write操作,可能打乱tcp内核的执行顺序,出现上述问题。改良后的版本使用tcp_sent来续发剩余数据,只用一个软件定时器来检测tpcb的状态。

1.5. 建立连接及未连接的状态
建立连接: Current PCB:2000b3f8, state = ESTABLISHED
一直未连接成功: Current PCB:2000b3f8, state = SYN_SENT
服务器断开:
其它状态
连接不上,偶尔会回调tcp_err中的回调函数,需要标识该状态,等待定时器的处理

2. 接收到的数据异常

犯了个低级的错误,将之前的代码
    mqtt_comm_p->recvlen = p->tot_len;    
   for ( data_p = mqtt_comm_p->recvbuf, q = p;  q->next != NULL; q = q->next )
    {
      memcpy(data_p, q->payload, q->len);
      data_p += q->len;
    }
改成如下代码
    mqtt_comm_p->recvlen = p->tot_len;    
    data_p = mqtt_comm_p->recvbuf;
    q = p;
   
    while ( q != NULL )  
    {
      memcpy(data_p, q->payload, q->len);
      data_p += q->len;
      q = q->next;
    }

3. 数据无法立即发送,接收数据后才会发送出去

在调用tcp_write之后,数据并不会立即发送出去,紧随其后调用tcp_ouput会将挂在的数据发送出去。在当前写的发送函数中,没有选择使用tcp_output,因为tpcb的内核定时器会定时调用它。


4. 数据发送的处理

最初的想法:使用队列来完成数据的填充与发送。这将使得数据的发送效率降低,因为涉及到数据入队列的复制,数据出队列的复制,以及数据拷贝到发送队列的复制。

新的改进:无队列,无发送缓冲区,使用信号灯及指针来处理发送。这也是不违背为了节省内存的初衷。


5. 花费一天时间寻找两个HardFault:

5.1 buf指向了常驻内存区,之后又指向了静态内存区域
    //vPortFree(RtCtrlPkgAck);
    //vPortFree(PicInfoPkg);
    //vPortFree(buf);
释放出错,

5.2 订阅主题初始化不完全
  mqtt_comm_p->MQTTPkgSubs->subcount = 1;  //  只有一个订阅主题

 // 之前初始化循环值为mqtt_comm_p->MQTTPkgSubs->subcount,也就是1而不是4,导致后面要用的主题名称topicString内部变量未初始化,拷贝数据内存硬件错误 。                  
  for (i = 0; i < 4 ; i++) 
  {
     mqtt_comm_p->MQTTPkgSubs->topicString[i].cstring = NULL;
     mqtt_comm_p->MQTTPkgSubs->topicString[i].lenstring.data = NULL;
     mqtt_comm_p->MQTTPkgSubs->topicString[i].lenstring.len = 0; // MQTTString_initializer
     mqtt_comm_p->MQTTPkgSubs->QoSs[i] = 0; // 服务质量

教训:内存的分配和释放处理,一定要小心小心再小心;要养成变量初始化的好习惯。

6. 软件定时器中创建的tpcb控制块,使用tcp_write无法发送数据

6.1 tpcb块的tcp_connect操作中有TCP_REG调用,其本意是注册当前tpcb到tcp active链表中,再在tcpip超时链表中创建一个超时定时器。但是tcp_connect并不在tcpip中调用,也就无法在tcpip超时链表中创建定时器,tpcb内核定时器自然无法启动,数据无法发送,这也是为什么需要tcpip_callback(tcp_connect_timer_cb, mqtt_comm_p->tpcb); 的原因;
改进:可以不在外部程序中调用tcpip_callback,而是直接tcp_connect中的TCP_REG(); 替换成tcpip_callback,不过这改动了内核的代码,慎重。
tpcb断开后,谨慎起见,也需要TCP_RMV删除,防止下次无法创建新的TCP内核定时器sys_timeout。
6.2 不使用软件定时器来触发重发,以免打乱tcp内核的数据处理,改用tcp_sent完成剩余数据发送。
6.3 以上2点原因得益于老衲五木的提点,在此非常感谢~。

7. LwIP raw tcp发送程序参考:

/ MQTT 数据发送函数static OSStatus tcp_mqtt_send(struct mqtt_comm_t *mqtt_comm_p, uint8_t data[], uint32_t len, int msBlock, uint8_t apiflag){    mqtt_comm_p->sendbuf = data;    mqtt_comm_p->sendlen = len;    mqtt_comm_p->apiflags = apiflag;         sys_log("tcp_mqtt_send,sendlen: %d.\r\n", mqtt_comm_p->sendlen);    tcp_send_data(mqtt_comm_p);        if( xSemaphoreTake(mqtt_comm_p->sendsema, msBlock) != pdTRUE )    {           sys_log("tcp send timeout.\r\n");        return TCP_MQTT_SEND_ERR;    }       if (mqtt_comm_p->sendlen != 0)        return TCP_MQTT_SEND_ERR;   else      return NoErr;}// TCP 数据发送函数void tcp_send_data(struct mqtt_comm_t *mqtt_comm_p){    err_t err;    uint32_t len;    len = mqtt_comm_p->sendlen;    len = (len > tcp_sndbuf(mqtt_comm_p->tpcb) ) ?  tcp_sndbuf(mqtt_comm_p->tpcb) : len;        // 连接断开    if ( (mqtt_comm_p->tpcb == NULL ) || (mqtt_comm_p->tpcb->state != ESTABLISHED) )    {        xSemaphoreGive(mqtt_comm_p->sendsema);        sys_log("tcp_send_data: tpcb error.\r\n");        return;    }    // 发送缓冲区满    if(tcp_sndbuf(mqtt_comm_p->tpcb) == 0)    {        vTaskDelay(1);        return;    }     do    {        err = tcp_write(mqtt_comm_p->tpcb, mqtt_comm_p->sendbuf, len, mqtt_comm_p->apiflags);        if (err == ERR_MEM)            len /= 2;    }    while (err == ERR_MEM && len > 1);    if (err == ERR_OK)    {        mqtt_comm_p->sendlen -= len;        mqtt_comm_p->sendbuf += len;    }    else        sys_log("[tcp_send_data]: tcp_write failed.\r\n");    }err_t tcp_send_cb(void *arg, struct tcp_pcb *tpcb, u16_t len){  struct mqtt_comm_t *mqtt_comm_p = arg;    if ( mqtt_comm_p->sendlen > 0 )  {    tcp_send_data(mqtt_comm_p);  }  else  {    xSemaphoreGive(mqtt_comm_p->sendsema);  }    return ERR_OK;}



MQTT移植记录

未完待续
1 0
原创粉丝点击