標籤:lwip raw tcp_connect mqtt paho
文章內容為工程調試記錄,內容排版散亂,閱讀慎入!
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移植記錄
未完待續
使用LwIP回調編程實現paho開源MQTT庫的移植