最近在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++; //接收到的个数加1
if(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或者56
if((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--; //接收到的总字节数减掉1
guIPRxByteNum = ( guIPRxByteNum <= 0) ? 0 : guIPRxByteNum; //防接收负数处理
}
//执行到这里,表示检测到了0x55AA
if((guIPRxByteNum >= u8DataLen) && (u8DataLen > 0)) //如果接收的字节数大于协议中的帧长
{
//先把数据取出(gUart0RxStartIndex读取的偏置先不变)做校验
u16TempRxStartIndex = gUart0RxStartIndex;
for(i = 0; i < u8DataLen; i++)
{
u8UartParaBuf[i] = pUart0RxDataMem[u16TempRxStartIndex];//校验通过将数据取出存放到u8UartParaBuf
u16TempRxStartIndex = (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或者56
if((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--; //接收到的总字节数减掉1
guIPRxByteNum = ( guIPRxByteNum <= 0) ? 0 : guIPRxByteNum; //防接收负数处理
}
//执行到这里,表示检测到了0x55AA
if((guIPRxByteNum >= u8DataLen) && (u8DataLen > 0)) //如果接收的字节数大于协议中的帧长
{
//先把数据取出(gUart0RxStartIndex读取的偏置先不变)做校验
u16TempRxStartIndex = gUart0RxStartIndex;
for(i = 0; i < u8DataLen; i++)
{
u8UartParaBuf[i] = pUart0RxDataMem[u16TempRxStartIndex];//校验通过将数据取出存放到u8UartParaBuf
u16TempRxStartIndex = (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);
}
}