项目背景:在Android系统中,Java层通过localsocket与Framework C层进行通信。
1、通信协议
帧头 |
源地址 |
目的地址 |
帧类型 |
内容长度 |
正文内容 |
帧尾 |
1字节 0xaf |
1字节
|
1字节 |
1字节 |
2字节 |
不定长 发往高清板的内容依据“HD3_LS_LB001_V1_02_2”组织 发往前面板的内容依据操作码协议组织 发往rs232的内容依据RS232协议组织 |
1字节 0x5f |
帧头:一个字节,固定位0xaf.地址(共2字节):源地址1字节;目的地址1字节
地址定义
FRONTPANEL_ADDR 1(0X1)
HIFIPANEL_ADDR 2(0X2)
ANDROIDPANEL_ADDRESS 3(0X3)
APP_ADDRESS 4(0X4)
COMPUTER_ADDR 10(0XA)
IR_ADDR 010000(0X)
红外模块地址用01000表示(避开1111的取值范围)
帧类型:1字节
类型分类:(M3-àM4时有用。)M4-àM3时,类型为默认为0,M3端不解析
0=应答帧
1=不需要应答
2=接收到最后数据帧应答(默认)
3=接收后立即应答
内容长度:2字节
内容:不定长
帧尾:1字节 固定0x5f
==================================================
在我的项目中,Java端处理完逻辑后需要快速地向C层发送多个长短不一的数据(在代码中就是顺序多次调用发送函数)。在测试中发现,在接收端会出现粘包现象。
注意:使用socket通信时,如果采用长连接、TCP、快速发送小数据包,就很有很能产生粘包现象。
为解决粘包可以采取以下几种解析方法。
方法一:在接收端先找到0xaf字节表示一帧开始,接着再找下一个0x5f字节,如果找到,就把该区间的内容作为一帧进行进一步解析(地址、帧类型、内容长度、内容)。然后以0x5f为基准,接着找下一帧。
for(i=0;i<len_buffer_socket_parse;i++)
{
if(buffer_socket_parse[i]==Packat_Header)
{
index_begin=i;
index_temp=index_begin;
for(;index_temp<len_buffer_socket_parse;index_temp++)
{
if(buffer_socket_parse[index_temp]==Packat_End)
{
index_end=index_temp;
//发送到socket处理
for(cursor=index_begin,j=0;cursor<=index_end;cursor++,j++)
{
socket_frame_content[j]=buffer_socket_parse[cursor];
SLOGE("cursor=%d,socket_frame_content[%d]=%x",cursor,j,socket_frame_content[j]);
}
SLOGE("socket_frame_content[0]=%x,socket_frame_content[%d]=%d",socket_frame_content[0],j-1,socket_frame_content[j-1]);
addr = socket_frame_content[1]>>2;
contentlen = socket_frame_content[2]; //正文长度
for(index=0;index<contentlen;index++)
{
content[index]=socket_frame_content[index+3];
SLOGE("addr=%d,contentlen=%d,content[%d]=%x",addr,contentlen,index,content[index]);
}
switch(addr)
{
case 20: //音量控制
//TODO 转发至高清板
write(fd_uart_debug,buffer_socket_parse,len_buffer_socket_parse);
TWE_COMM_TX(content,contentlen,0x32,CommPackType_LastAck);
send(s_fdCommand,buffer_socket_parse,len_buffer_socket_parse,0);
break;
case FRONTPANEL_ADDR://发送到前面板
SLOGE("COMMAND TO frontpanel");
TWE_COMM_TX(content,contentlen,0x31,CommPackType_LastAck);
write(fd_uart_debug,content,contentlen);
break;
case HIFIPANEL_ADDR://发送到高清板
SLOGE("COMMAND TO hifipanel");
TWE_COMM_TX(content,contentlen,0x32,CommPackType_LastAck);
break;
case COMPUTER_ADDR://发送到电脑
SLOGE("COMMAND TO computer");
break;
case IR_ADDR://发送到红外模块
SLOGE("COMMAND TO ir module");
sendlen=send(fd_domain,&buffer_socket_parse[3],sizeof(uchar),0);
if(sendlen<0)
{
SLOGE("send command to ir module error");
} else {
SLOGE("send command to ir module sendlen=%d",sendlen);
}
break;
case 6:
break;
case 7:
break;
case 8:
break;
case 9:
break;
case 11:
break;
case 12:
break;
case 13:
break;
default:
SLOGE("ERROR:TWE_Socket_Loop switch");
break;
}
i=index_end;
break;
}
}
}
}
该种解析方法的优点是思路简单,缺点是如果内容包含字节0xaf或0x5f时,该方法不能准确地从粘包中提取出内容。
方法二:从接收缓冲区接收到数据,无论粘包或是不粘包,第一个字节必然是0xaf。基于这个前提,我们可以从0xaf开始,使用状态机,依据协议一个字节一个字节的开始解析缓冲区的数据,依据内容长度找出内容。
该方法的缺点是在基于跨网络的socket通信中可能会出现问题,优点是可以准确地识别出内容,即使内容中包含0xaf或0x5f。代码如下:
if(len_buffer_socket_parse>0)
{
SLOGE("buffer_socket_parse[0]=%x,s_fdCommand=%d",buffer_socket_parse[0],s_fdCommand);
for(i=0;i<len_buffer_socket_parse;i++)
{
dataIn = buffer_socket_parse[i];
SLOGE("status_socket=%d,dataIn=%x",status_socket,dataIn);
switch(status_socket)
{
case 0:
if(dataIn==Packat_Header)
{
status_socket++;
index=0; //要记得清零,我在这里犯过一次错误
}else{
i=len_buffer_socket_parse; //该数据报有问题、丢弃
SLOGE("第一个字节有误");
}
break;
case 1: //源地址
if(dataIn==HIFIPANEL_ADDR||dataIn==FRONTPANEL_ADDR||dataIn==IR_ADDR||dataIn==ANDROIDPANEL_ADDRESS)
{
source_addr=dataIn;
status_socket++;
}else{
status_socket=0;
i=len_buffer_socket_parse;
SLOGE("源地址有误");
}
break;
case 2: //目的地址
if(dataIn==HIFIPANEL_ADDR||dataIn==FRONTPANEL_ADDR||dataIn==IR_ADDR)
{
dest_addr=dataIn;
status_socket++;
}else{
SLOGE("目的地址有误");
status_socket=0;
i=len_buffer_socket_parse;
}
break;
case 3://帧类型
if(dataIn==CommPackType_ACK||dataIn==CommPackType_NoAck||dataIn==CommPackType_EachFrameACK||dataIn==CommPackType_LastAck)
{
frame_type=dataIn;
status_socket++;
}else{
SLOGE("帧类型有误");
status_socket=0;
i=len_buffer_socket_parse;
}
break;
case 4: //内容长度
contentlen=dataIn;
contentlen_temp=contentlen;
status_socket++;
break;
case 5: //内容获取
content[index]=dataIn;
index++;
if(--contentlen_temp==0)
{
status_socket++;
for(j=0;j<contentlen;j++)
{
SLOGE(" to send content[%d]=%x",j,content[j]);
}
}
break;
case 6://帧尾
if(dataIn==Packet_End)
{
status_socket=0;
SLOGE("dest_addr=%d",dest_addr);
switch(dest_addr)
{
case 20: //音量控制
//TODO 转发至高清板
write(fd_uart_debug,buffer_socket_parse,len_buffer_socket_parse);
TWE_COMM_TX(content,contentlen,0x32,CommPackType_LastAck);
send(s_fdCommand,buffer_socket_parse,len_buffer_socket_parse,0);
break;
case FRONTPANEL_ADDR: //发送到前面板
SLOGE("COMMAND TO frontpanel");
TWE_COMM_TX(content,contentlen,0x31,CommPackType_LastAck);
write(fd_uart_debug,content,contentlen);
break;
case HIFIPANEL_ADDR: //发送到高清板
SLOGE("COMMAND TO hifipanel");
TWE_COMM_TX(content,contentlen,0x32,CommPackType_LastAck);
break;
case COMPUTER_ADDR: //发送到电脑
SLOGE("COMMAND TO computer");
break;
case IR_ADDR: //发送到红外模块
SLOGE("COMMAND TO ir module");
sendlen=send(fd_domain,&content[0],sizeof(uchar),0);
if(sendlen<0)
{
SLOGE("send command to ir module error");
}else{
SLOGE("send command to ir module sendlen=%d",sendlen);
}
break;
default:
SLOGE("ERROR:TWE_Socket_Loop send error");
break;
}
}else{
status_socket=0; //丢弃
i=len_buffer_socket_parse;
}
break;
default:
break;
}
}
}
len_buffer_socket_parse = 0;