当前位置:网站首页>uIP1.0 主动发送的问题理解
uIP1.0 主动发送的问题理解
2022-04-23 19:24:00 【ToneChip】
最近在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);
}
}
版权声明
本文为[ToneChip]所创,转载请带上原文链接,感谢
https://tonechip.blog.csdn.net/article/details/123004518
边栏推荐
- Some ideas about time-consuming needs assessment
- JVM的类加载过程
- Openlayers 5.0 thermal diagram
- 为何PostgreSQL即将超越SQL Server?
- [报告] Microsoft :Application of deep learning methods in speech enhancement
- Openlayers 5.0 discrete aggregation points
- Client interns of a large factory share their experience face to face
- [play with lighthouse] Tencent cloud lightweight server builds a full platform video analysis video download website
- ArcMap connecting ArcGIS Server
- 命令-sudo
猜你喜欢
Using oes texture + glsurfaceview + JNI to realize player picture processing based on OpenGL es
优先使用组合而不使用继承
JVM的类加载过程
An algorithm problem was encountered during the interview_ Find the mirrored word pairs in the dictionary
5 minutes to achieve wechat cloud applet payment function (including source code)
No, some people can't do the National Day avatar applet (you can open the traffic master and earn pocket money)
Matlab 2019 installation of deep learning toolbox model for googlenet network
FTP, SSH Remote Access and control
Kubernetes入门到精通-KtConnect(全称Kubernetes Toolkit Connect)是一款基于Kubernetes环境用于提高本地测试联调效率的小工具。
The platinum library cannot search the debug process records of some projection devices
随机推荐
Common SQL commands
Codeforces Round #783 (Div. 2) D题解
Some records used by VS2010
SQL常用的命令
OpenHarmony开源开发者成长计划,寻找改变世界的开源新生力!
Openlayers 5.0 thermal diagram
goroutine
Garbage collector and memory allocation strategy
UML类图几种关系的总结
Summary of several relationships of UML class diagram
Partage de la conception de l'alimentation électrique de commutation et illustration des compétences en conception de l'alimentation électrique
openlayers 5.0 当地图容器大小改变时,重新加载地图
Pdf reference learning notes
Solve the problem of invalid listview Click
视频理解-Video Understanding
Keysight has chosen what equipment to buy for you
First experience of using fluent canvas
No, some people can't do the National Day avatar applet (you can open the traffic master and earn pocket money)
Kubernetes入门到精通-KtConnect(全称Kubernetes Toolkit Connect)是一款基于Kubernetes环境用于提高本地测试联调效率的小工具。
MySQL restores or rolls back data through binlog