热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

文章内容为工程调试记录,内容排版散乱,阅读慎入!LwIPRaw回调编程调试记录1.tcp_connect连接问题:1.1连接不上,没有任何的回调函数调用,延时可以偶尔的解决这个问题解决思路:在软
文章内容为工程调试记录,内容排版散乱,阅读慎入!
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移植记录

未完待续

推荐阅读
author-avatar
友爱锦锦_950
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有