1. 程式人生 > >【轉載】筆記:無os的LwIP在TCP server歷程中網路資料傳送,串口出現tcp_write及tcp_receive錯誤。

【轉載】筆記:無os的LwIP在TCP server歷程中網路資料傳送,串口出現tcp_write及tcp_receive錯誤。

 

2016年09月07日 22:13:58 新野-新野 閱讀數:6180 標籤: LWIP 更多

個人分類: 轉載筆記

做一個,串列埠收到資料然後通過tcp主動傳送出去的東西,但是目前遇到以下問題,當tcp初始化後連線也建立好了,此時如果每即使毫秒就向串列埠扔一組資料,串列埠收到資料後主動傳送tcp出去,但是如果傳送頻率很快(感覺100ms內的話)發個一段時間,程式就會死,debug了一下,死在了tcp_write_check函式裡的斷言這裡

if (pcb->snd_queuelen != 0) {
    LWIP_ASSERT("tcp_write: pbufs on queue => at least one queue non-empty",
      pcb->unacked != NULL || pcb->unsent != NULL);
  } else {
    LWIP_ASSERT("tcp_write: no pbufs on queue => both queues empty",
      pcb->unacked == NULL && pcb->unsent == NULL);
  }

求助 主動傳送tcp 頻率很快的情況下怎麼樣才能不死。。。

ps:如果是客戶端主動傳送tcp給我,我在tcp收的回撥函式裡再呼叫tcp_write,把資料丟回去,無論多快,都沒問題。。。

 

 

方法:1

1,增加TCP傳送緩衝區大小。
2,向串列埠扔資料的速度超過了TCP主動發的速度,如果有可能應該握手控制一下。這個還可能與客戶端接收處理速度不夠有關,應該排查一下。

 

 

 

其他的內容:

 

//*****************************************************************************

// ---------- Memory options ----------

#define MEM_ALIGNMENT                   4           // default is 1

#define MEM_SIZE                        (22 * 1024)  // default is 1600, was 16K

 

//*****************************************************************************

// ---------- Internal Memory Pool Sizes ----------

#define MEMP_NUM_PBUF                     24    // Default 16, was 16

#define MEMP_NUM_TCP_PCB                  1    // Default 5, was 12

 

 

//*****************************************************************************

// ---------- TCP options ----------

LWIP_TCP                        1

#define TCP_WND                         4096   // default is 2048

#define TCP_MSS                        1024        // default is 128

#define TCP_SND_BUF                     (16 * TCP_MSS// default is 256, was 6 *

//#define TCP_SND_QUEUELEN                (4 * (TCP_SND_BUF/TCP_MSS))

 

 

 

[轉載]關於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所指的內容是不影響資料的正確傳送的。

 

 

 

其他:tcp_receive出錯的問題。

   LWIP+FREERTOS在STM32F107上執行,在TCP通訊傳送資料,一段時間之後出現斷言死迴圈。
   報錯:Assertion tcp_recive:valid queue length failed at line 957 in F:\.........\tcp_in.c
   請問下,上面情況是不是opt.h裡面配置的問題呢?
謝謝!尋求幫助。。急!

在公司裡面,通過遠端上網的,無法上傳東西。
1.上面的問題應該是接受函式裡面處理的問題,在中斷觸發的接收函式裡面判斷是否有資料,用的if(ETH_GetRxPKtSize()!=0),改為while後正常了。
2.現在碰到一個新的問題:在連線一段時間之後(這個時間不定,有時1個小時以內,有時候幾天時間才出現),無法進入乙太網的中斷。還在除錯中。

 

 

 

其他:STM32F407+lwip的速度提升問

詳情連結地址:http://www.openedv.com/forum.php?mod=viewthread&tid=43116&page=1#pid247658 

1、儘量不要使用delay延時函式!應用程式中的延時函式對於速度的影響很大的。
2、儘量減少資料拷貝,資料拷貝也會浪費大量的時間。
3、就像別人說的,將快取開大一點,你如果使用了ST的官方乙太網庫的話,有下面幾個地方需要改打一點。

 

 

1

2

3

//STM32內部MAC傳送和接收BUF個數

#define ETH_RXBUFNB             10

#define ETH_TXBUFNB     10

 

 

#define MEM_SIZE                20000   //記憶體堆heap大小

#define MEMP_NUM_PBUF           30  //MEMP_NUM_PBUF:memp結構的pbuf數量,如果應用從ROM或者靜態儲存區傳送大量資料時                                        //這個值應該設定大一點

#define MEMP_NUM_TCP_SEG        300 //MEMP_NUM_TCP_SEG:最多同時在佇列中的TCP段數量

 

#define PBUF_POOL_SIZE          30  //PBUF_POOL_SIZE:pbuf記憶體池個數

#define PBUF_POOL_BUFSIZE       512 //PBUF_POOL_BUFSIZE:每個pbuf記憶體池大小

 

#define TCP_MSS                 (1500 - 40)   //最大TCP分段,TCP_MSS = (MTU - IP報頭大小 - TCP報頭大小

#define TCP_SND_BUF          (25*TCP_MSS)   //TCP傳送緩衝區大小(bytes).

#define TCP_SND_QUEUELEN        (10* TCP_SND_BUF/TCP_MSS)   //TCP_SND_QUEUELEN: TCP傳送緩衝區大小(pbuf).這個值最                                                                //小為(2 * TCP_SND_BUF/TCP_MSS)

#define TCP_WND                 (8*TCP_MSS)             //TCP傳送視窗

 

 

注意:TCP_WND是傳送視窗,這個值對於LWIP的速度影響非常大。