1. 程式人生 > >LwIP用TCP連線方式在資料量比較大協議棧卡死

LwIP用TCP連線方式在資料量比較大協議棧卡死

這段時間用STM32移植LwIP做語音傳輸。但是遇到一個問題困擾許久,在使用TCP方式做一個client去連線server,由於資料量比較大經常在連線一個多小時候就出現斷線而

也ping不通。接下來我們看一下這個問題是怎麼出現的和他的決絕方法(小白一枚,說錯的地方還望指正哈 。。。。共同學習 。嘻嘻 ^_^  )。

額,還沒有學作業系統,還生活在裸奔的年代。。。 client和server採用LwIP的Raw函式編寫。連線過程採用短連線,即傳送一次資料就請求斷開。

我們先看一個client端的程式。

/*
**建立一個連線
*/
void client_init(void)
{
	#define server_point 1080 //server埠號
	struct tcp_pcb *Clipcb;   //建立一個pcb控制塊
	struct ip_addr ipaddr; //IP
	
	IP4_ADDR(&ipaddr,192,168,1,18);  //server地址
	
	Clipcb = tcp_new(); ;//分配一個控制塊
	
	if(Clipcb != NULL)

	tcp_connect(Clipcb,&ipaddr,server_point,TcpCli_Connected); //建立連線 並註冊連線成功的回撥函式 Tcp_Cli_Connected
}
如果建立成功那麼就進入回撥函式
err_t TcpCli_Connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{

    tcp_write(tpcb,(void *)sound_buf[NT_USE_BUF_NUM],sizeof(sound_buf[ NT_USE_BUF_NUM]),0); //傳送資料

	tcp_close(tpcb);  //關閉連線

	return ERR_OK;

}

在主函式中呼叫傳送資料

int main(void)
{
<span>	</span>do somthing
if(傳送條件成立)
{
      client_init();
}
   do something

好,那麼問題來了。上面這種方法在資料量比較小的時候基本上不會出現什麼問題,但是在資料量比較大的時候就會出現協議棧卡死。而出現上述問題的關鍵是TCP連線的時候如果要斷開一個連線要經過4次握手,而我們在斷開連線的時候只是簡單的呼叫了 tcp_close( )函式,然而它具體的呼叫結果是什麼了,我們沒有進行檢測。額這裡就來看一下tcp_close函式。

err_t
tcp_close(struct tcp_pcb *pcb)
{
  err_t err;

#if TCP_DEBUG
  LWIP_DEBUGF(TCP_DEBUG, ("tcp_close: closing in "));
  tcp_debug_print_state(pcb->state);
#endif /* TCP_DEBUG */

  switch (pcb->state) {  //判斷pcb控制塊的連線狀態
  case CLOSED:
    err = ERR_OK;
    TCP_RMV(&tcp_bound_pcbs, pcb);  //將pcb控制塊移除繫結連結串列
    memp_free(MEMP_TCP_PCB, pcb);  //釋放pcb控制塊
    pcb = NULL;
    break;
  case LISTEN:
    err = ERR_OK;
    tcp_pcb_remove((struct tcp_pcb **)&tcp_listen_pcbs.pcbs, pcb);//將pcb控制塊移除監聽佇列
    memp_free(MEMP_TCP_PCB_LISTEN, pcb);
    pcb = NULL;
    break;
  case SYN_SENT:
    err = ERR_OK;
    tcp_pcb_remove(&tcp_active_pcbs, pcb); //移除活動佇列
    memp_free(MEMP_TCP_PCB, pcb);
    pcb = NULL;
    snmp_inc_tcpattemptfails();
    break;
  case SYN_RCVD:
    err = tcp_send_ctrl(pcb, TCP_FIN);  //傳送FIN關閉請求
    if (err == ERR_OK) {
      snmp_inc_tcpattemptfails();  //一個巨集定義沒有看到函式體 
      pcb->state = FIN_WAIT_1;
    }
    break;
  case ESTABLISHED:
    err = tcp_send_ctrl(pcb, TCP_FIN); //傳送FIN關閉請求
    if (err == ERR_OK) {
      snmp_inc_tcpestabresets();  //巨集
      pcb->state = FIN_WAIT_1;
    }
    break;
  case CLOSE_WAIT:
    err = tcp_send_ctrl(pcb, TCP_FIN); //傳送FIN
    if (err == ERR_OK) {
      snmp_inc_tcpestabresets();
      pcb->state = LAST_ACK;
    }
    break;
  default:      //其他狀態認為連線已經關閉
    /* Has already been closed, do nothing. */
    err = ERR_OK;
    pcb = NULL;
    break;
  }

  if (pcb != NULL && err == ERR_OK) {
    tcp_output(pcb); //將沒有傳送的資料傳送出去
  }
  return err;
}
在分析這段程式碼之前我們先看一下pcb->state 是什麼。它是pcb結構體的一員,他主要使用來記錄pcb控制塊的狀態他總共有11中狀態,在這裡我把他分為了兩類(他的所有狀態都可以在tcp.c 這個原始檔中找到)


第一類、處於連線狀態 或者說是pcb控制塊沒有釋放的狀態吧:

CLOSED:沒有任何連線狀態
LISTEN:偵聽來自遠方的TCP埠的連線請求
SYN-SENT:再發送連線請求後等待匹配的連線請求
SYN-RECEIVED:再收到和傳送一個連線請求後等待對方對連線請求的確認
ESTABLISHED:代表一個開啟的連線
CLOSE-WAIT:等待從本地使用者發來的連線中斷請求



第二類、連線關閉狀態(tcp -> state)

FIN-WAIT-1:等待遠端TCP連線中斷請求,或先前的連線中斷請求的確認
FIN-WAIT-2:從遠端TCP等待連線中斷請求
CLOSING:等待遠端TCP對連線中斷的確認
LAST-ACK:等待原來的發向遠端TCP的連線中斷請求的確認
TIME-WAIT:等待足夠的時間以確保遠端TCP接收到連線中斷請求的確認
還有一個雖然不是pcb->state 但是當pcb == NULL的時候也是處在沒有連線的狀態

瞭解了上面的以後我麼就能夠判斷一個pcb控制塊處於什麼狀態了。那麼我們開始的那種建立連線的問題出在哪裡了,在我們建立了一個連線的時候要經過3次握手,那麼可能在某一連線中client傳送出SYN以後對方還沒有回覆ACK正式建立連線我們就有建立了一個新的pcb那麼在資料量比較大的時候(這次做音訊應該是32K*4bps吧)就有一次的建立了一個pcb控制塊,或者是在我們的回掉函式中沒有正確關閉pcb連線。那麼這個連線韓式存在而我們後續卻沒有對他進行操作。如果在應用層一直沒有再次呼叫tcp_close()函式那麼這個連線將一直存在,可見是一個災難性的結果,最終協議棧資源耗光、掛掉了。

那麼我們的目標很明確就是在每次建立一個新的連線的時候確保上一個連線已經關閉,於是我們在建立連線以後就註冊一個poll函式,在我們上面的TcpCli_Connected()函式呼叫tcp_poll()函式註冊一個使用者輪詢函式client_poll()在他裡面關閉連線,在這裡註冊就保證了是已經正常接通的連線,同時回撥函式會隔一段時間去呼叫。這樣如果我們沒有正常關閉連線就會呼叫用這個poll來關閉,對應的如果正常關閉了那麼就不會再呼叫這個回撥函式。然後第二個注意的就是在每次建立連線的時候都要判斷當前連線的狀態。第三就是在server函式中也註冊一個回撥函式用來關閉連線。

         Clipcb是一個全域性變數

/*
**建立一個連線
*/
void client_init(void)
{
<span style="white-space:pre">	</span>#define server_point 1080
<span style="white-space:pre">	</span>struct ip_addr ipaddr; 
    
     if(Code_Void_BA <  CodeBufAmount-1 )
    {
        IP4_ADDR(&ipaddr,192,168,1,180);  //server IP
        
        /* add according to https://lists.gnu.org/archive/html/lwip-users/2012-06/msg00031.html*/
        /*確保上一次連線正確關閉*/
       if( (Clipcb == NULL ) || (Clipcb->state ==FIN_WAIT_1) || (Clipcb->state == TIME_WAIT)||(Clipcb->state == CLOSED)
                       <span style="white-space:pre">					</span> ||(Clipcb->state == CLOSING)||(Clipcb->state == FIN_WAIT_2)||(Clipcb->state ==LAST_ACK)) 

        {
           if(Clipcb->state == CLOSED)
           {
               tcp_close(Clipcb); 
                
           }
            
            Clipcb = tcp_new();
            
            if(Clipcb == NULL)
            {
                printf("malloc pcb err!\n");
            }
            else
	  {
	        tcp_connect(Clipcb,&ipaddr,server_point,TcpCli_Connected);//建立連線
            }
    }
        NT_USE_BUF_NUM = (NT_USE_BUF_NUM+1)%CodeBufAmount; //切換緩衝區

        Code_Void_BA++;
 
    }        
}

成功連線後的回撥函式,client_poll函式就是直接關閉連線就OK了

err_t TcpCli_Connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
    static uint32_t ti = 0;
    
    Clipcb = tpcb; 
    
    tcp_poll(Clipcb, client_poll, 0); //註冊回撥函式
    
    tcp_write(tpcb,(void *)cod_buf[NT_USE_BUF_NUM],sizeof(cod_buf[ NT_USE_BUF_NUM]),0); //將資料填寫到傳送佇列
    
    if(ERR_OK != tcp_close(tpcb))
    {
        sta = 1; printf("close err!\n");
    }
    else  sta = 0;
    
    return ERR_OK;

}

  KO

 原始碼http://download.csdn.net/detail/u014070258/8373623