uIP1.0 主动发送的问题理解
最近在LPC1768上調試uIP1.0的網絡協議代碼,設置配置的是不使用分包發送的模式,原本想著發送回傳的字節數應該也不會太大,我都是本地自定義的協議.后面調試的時候,發現TCP協議居然有粘包和拆包的問題
其中粘包的問題最為惱火,本來這個輕協議棧已經出來很多年了,現在已經又很多人發現不玩這種嵌入式的東西,想找一個uIP協議棧的主動發送都沒找到,最后只有在阿莫論壇上找到唯一一片稍微有價值的文章
因為是在設備端使用uIP的TCP服務器配置,上位機連接到設備后發送指定協議,但是居然發現在設備端的TCP服務器不能實現主動發送!!!!這就很煩惱了.本來一個雙工通訊的,活生生的搞成單工
問題是這樣的
1. UI端發送14字節包請求到設備,設備回復56字節,設備的uIP緩存是56*10字節,也就是說單次uIP最多就只能回復10個幀.那么如果TCP發送粘包了,,比如突然來了20個14字節請求,那么一次設備就響應不了!!!!!問題就來,需要回復20個包的時候uIP沒這么緩存了,最大只能回復10個,那么剩下的10個怎么辦???
2.解決上面的辦法就是在設備端進行ack響應,先回復10個包給UI,UI這個時候會回復一個ACK給設備,設備收到ACK后再處理剩下的包.
3.好,按照上面的思路做好了,現在又有一個問題,就是UI端發送14字節請求是一個不斷的過程,那么這就有可能UI收到第一次的10個包的時候,第二次正好要發14字節,那么TCP協議就會把這個ACK Flag放到14字節的TCP協議上進行一起發送給設備(這樣設備就無法確定這次是要處理ACK還是newdata),正常是應該需要處理先處理ack的包,處理返回完了,再處理newdata,這樣就是要求設備端把接收到的數據作為一個緩存存起來,.這樣坐一個循環緩存還是有問題,就是當UI發送快了之后,設備端還是會存在緩存滿的情況!
4.忽然想到另外一個思路
因為uIP現在默認的接收緩存和發送緩存都是同一段內存,那么我在這里做一個區分,接收緩存設置14*4 * 2字節,返回的緩存設置成56*8字節,這種情況下我是不是就可以保證在網絡上是可以對稱通訊的呢?因為設備的接收緩存,始終都不會超過14*4 * 2字節接收,這樣處理返回的值就是56*4*2字節!!
晚上再調試代碼測試一下這個思路如何?可行的話后續再更新
2022年2月23日更新一下
上面的思路還是有問題,如果uIP的發送緩存和接收緩存設置成不同的內存段,我發現需要改動的內容還不少,最后的解決辦法是調整上位機UI端TCP發送速度,原來是13ms一次,后面改成20ms我發現粘包的概率就大大降低了,同時配合ACK多次返回的規則寫代碼這樣粘包基本能處理了
這個問題至此,我已經在48SP處理器項目上使用了,后續準備測試驗收
最后上一段這部分調試的代碼
非常核心!!!!全是多年工作的精華
//TCP回調函數:處理網絡來的數據 void TCP_Server_AppCall(void) {int i = 0;//串口數據處理,g_u8Uart0RxDataMem是運行到這函數里面時接收到數據的總緩存unsigned char *pUart0RxDataMem = (unsigned char * )g_u8Uart0RxDataMem;unsigned char u8UartParaBuf[UART0_FRAME_MAX_LEN] = {0}; //臨時變量,每次處理一幀數據unsigned char u8DataLen = 0;unsigned short u16TempRxStartIndex = 0;if(pUart0RxDataMem == NULL){return ;}int nLen = 0;unsigned char *pUIPData = (unsigned char * )uip_appdata;unsigned short u16UIPDataSendLen = 0;unsigned short u16UIPOneFrameOutLen = 0;if(uip_connected()){guIPRxByteNum = 0; //首次連接}if(uip_newdata()) //收到服務器發來數據 數據存在uip_appdata[]數組{nLen = gEmacTcpPocketLen; //gEmacTcpPocketLen隨著每次接收會變化pUIPData = (unsigned char * )uip_appdata;if(nLen <= 0) //如果接受到數據小于0,不處理{nLen = nLen + 1; //調試用return ;}// uip_newdata 新數據存入g_u8uIPDataBuff緩存中,采用逐個拷貝的方式for(i = 0; i < nLen; i++){pUart0RxDataMem[gUart0RxIndex % UART0_RX_BUFF_LEN] = pUIPData[i];gUart0RxIndex = (gUart0RxIndex + 1) % UART0_RX_BUFF_LEN;guIPRxByteNum++; //接收到的個數加1if(guIPRxByteNum > UART0_RX_BUFF_LEN) //數據太多沒處理完{guIPRxByteNum = guIPRxByteNum;}}//開始解析數據while(guIPRxByteNum >= 3){while(guIPRxByteNum >= 3) //如果接收到的數據長度大于3個字節,就可以進行幀頭0x55AA的判斷{//用楨頭完整字節判斷,一個字節容易出錯if((pUart0RxDataMem[gUart0RxStartIndex] == 0x55) &&(pUart0RxDataMem[(gUart0RxStartIndex + 1) % UART0_RX_BUFF_LEN] == 0xAA)){u8DataLen = pUart0RxDataMem[((gUart0RxStartIndex + 2) % UART0_RX_BUFF_LEN)];//第3個字節時本幀的長度,取出來判斷一下是不是14或者56if((UART_RX_PROTOCOL_SIZE == u8DataLen) ||(UART_RX_ExPROTOCOL_SIZE == u8DataLen) ) //檢測到指定的協議長度就結束循環{break;}}//如果幀長不等于14或者56,那么把幀頭的第一個字節置0,去掉一個字節,再循環判斷,直到檢測到0x55AA就跳出循環pUart0RxDataMem[gUart0RxStartIndex % UART0_RX_BUFF_LEN] = 0;gUart0RxStartIndex = (gUart0RxStartIndex + 1) % UART0_RX_BUFF_LEN; //gUart0RxStartIndex的大小始終都在0~UART0_RX_BUFF_LEN之間,防止溢出guIPRxByteNum--; //接收到的總字節數減掉1guIPRxByteNum = ( guIPRxByteNum <= 0) ? 0 : guIPRxByteNum; //防接收負數處理}//執行到這里,表示檢測到了0x55AAif((guIPRxByteNum >= u8DataLen) && (u8DataLen > 0)) //如果接收的字節數大于協議中的幀長{//先把數據取出(gUart0RxStartIndex讀取的偏置先不變)做校驗u16TempRxStartIndex = gUart0RxStartIndex;for(i = 0; i < u8DataLen; i++){u8UartParaBuf[i] = pUart0RxDataMem[u16TempRxStartIndex];//校驗通過將數據取出存放到u8UartParaBufu16TempRxStartIndex = (u16TempRxStartIndex + 1) % UART0_RX_BUFF_LEN;}if(CheckReciveDataCRC(u8UartParaBuf, u8DataLen))//如果校驗正確即可從原大緩存中清除對應的字節數后按照協議提取數據{//怎么判斷這是一個有效處理還是無效處理u16UIPOneFrameOutLen = ProcessingProtocolData(u8UartParaBuf, u8DataLen);if(NETWORK_RETURN_BUFLEN - u16UIPDataSendLen >= u16UIPOneFrameOutLen){//拷貝送到uIP緩存,與串口不一樣的地方在這里memcpy(&pUIPData[u16UIPDataSendLen], u8UartParaBuf, u16UIPOneFrameOutLen * sizeof(unsigned char));u16UIPDataSendLen += u16UIPOneFrameOutLen;guIPOutFrameCount++;for(i = 0; i < u8DataLen; i++) //清除取出的數據{pUart0RxDataMem[gUart0RxStartIndex] = 0;gUart0RxStartIndex = (gUart0RxStartIndex + 1) % UART0_RX_BUFF_LEN;guIPRxByteNum--;guIPRxByteNum = ( guIPRxByteNum <= 0) ? 0 : guIPRxByteNum; //防接收負數處理}}else{//數據到這里了表示返回內存已滿,不能再存了uip_send(pUIPData, u16UIPDataSendLen);return;}}else{//如果校驗錯誤,前面的數據已經被清除為0,因此這楨數據就丟棄pUart0RxDataMem[gUart0RxStartIndex] = 0; //只需清除一個字節讓while循環去清除其他數據gUart0RxStartIndex = (gUart0RxStartIndex + 1) % UART0_RX_BUFF_LEN;guIPRxByteNum--;guIPRxByteNum = ( guIPRxByteNum <= 0) ? 0 : guIPRxByteNum; //防接收負數處理}}}uip_send(pUIPData, u16UIPDataSendLen);}//2022.02.19使用ACK作為判斷還是有BUG,因為當UI上位機發送數據過快粘包時未來得及處理的包返回緩存依舊超過//這樣新數據中會帶有ACK標志位,這樣既需要處理上一次的數據,也需要處理newdata,會造成丟包if(uip_acked()) //接收到回應表明上一個數據包發送完成,開始處理下一個包{//開始解析數據while(guIPRxByteNum >= 3){while(guIPRxByteNum >= 3) //如果接收到的數據長度大于3個字節,就可以進行幀頭0x55AA的判斷{//用楨頭完整字節判斷,一個字節容易出錯if((pUart0RxDataMem[gUart0RxStartIndex] == 0x55) &&(pUart0RxDataMem[(gUart0RxStartIndex + 1) % UART0_RX_BUFF_LEN] == 0xAA)){u8DataLen = pUart0RxDataMem[((gUart0RxStartIndex + 2) % UART0_RX_BUFF_LEN)];//第3個字節時本幀的長度,取出來判斷一下是不是14或者56if((UART_RX_PROTOCOL_SIZE == u8DataLen) ||(UART_RX_ExPROTOCOL_SIZE == u8DataLen) ) //檢測到指定的協議長度就結束循環{break;}}//如果幀長不等于14或者56,那么把幀頭的第一個字節置0,去掉一個字節,再循環判斷,直到檢測到0x55AA就跳出循環pUart0RxDataMem[gUart0RxStartIndex % UART0_RX_BUFF_LEN] = 0;gUart0RxStartIndex = (gUart0RxStartIndex + 1) % UART0_RX_BUFF_LEN; //gUart0RxStartIndex的大小始終都在0~UART0_RX_BUFF_LEN之間,防止溢出guIPRxByteNum--; //接收到的總字節數減掉1guIPRxByteNum = ( guIPRxByteNum <= 0) ? 0 : guIPRxByteNum; //防接收負數處理}//執行到這里,表示檢測到了0x55AAif((guIPRxByteNum >= u8DataLen) && (u8DataLen > 0)) //如果接收的字節數大于協議中的幀長{//先把數據取出(gUart0RxStartIndex讀取的偏置先不變)做校驗u16TempRxStartIndex = gUart0RxStartIndex;for(i = 0; i < u8DataLen; i++){u8UartParaBuf[i] = pUart0RxDataMem[u16TempRxStartIndex];//校驗通過將數據取出存放到u8UartParaBufu16TempRxStartIndex = (u16TempRxStartIndex + 1) % UART0_RX_BUFF_LEN;}if(CheckReciveDataCRC(u8UartParaBuf, u8DataLen))//如果校驗正確即可從原大緩存中清除對應的字節數后按照協議提取數據{u16UIPOneFrameOutLen = ProcessingProtocolData(u8UartParaBuf, u8DataLen);if(NETWORK_RETURN_BUFLEN - u16UIPDataSendLen >= u16UIPOneFrameOutLen){//拷貝送到uIP緩存,與串口不一樣的地方在這里memcpy(&pUIPData[u16UIPDataSendLen], u8UartParaBuf, u16UIPOneFrameOutLen * sizeof(unsigned char));u16UIPDataSendLen += u16UIPOneFrameOutLen;guIPOutFrameCount++;for(i = 0; i < u8DataLen; i++) //清除取出的數據{pUart0RxDataMem[gUart0RxStartIndex] = 0;gUart0RxStartIndex = (gUart0RxStartIndex + 1) % UART0_RX_BUFF_LEN;guIPRxByteNum--;guIPRxByteNum = ( guIPRxByteNum <= 0) ? 0 : guIPRxByteNum; //防接收負數處理}}else{//數據到這里了表示返回內存已滿,不能再存了uip_send(pUIPData, u16UIPDataSendLen);return;}}else{//如果校驗錯誤,前面的數據已經被清除為0,因此這楨數據就丟棄pUart0RxDataMem[gUart0RxStartIndex] = 0; //只需清除一個字節讓while循環去清除其他數據gUart0RxStartIndex = (gUart0RxStartIndex + 1) % UART0_RX_BUFF_LEN;guIPRxByteNum--;guIPRxByteNum = ( guIPRxByteNum <= 0) ? 0 : guIPRxByteNum; //防接收負數處理}}}uip_send(pUIPData, u16UIPDataSendLen);}}
總結
以上是生活随笔為你收集整理的uIP1.0 主动发送的问题理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在线编辑word文档,weboffice
- 下一篇: Java基础_集合框架1