数据打包及拆包.docx
- 文档编号:11840408
- 上传时间:2023-04-05
- 格式:DOCX
- 页数:8
- 大小:350.30KB
数据打包及拆包.docx
《数据打包及拆包.docx》由会员分享,可在线阅读,更多相关《数据打包及拆包.docx(8页珍藏版)》请在冰豆网上搜索。
数据打包及拆包
第一章封包
1.封包流程
封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了。
包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,这是个很重要的变量,其他的结构体成员可根据需要自己定义.根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
数据封装流程图
1.1TCP段包头
TCP段包头
源端口(SourcePort)和目地端口(DestinationPort)--字段长度为16位,它们为封装的数据指定了源和目的应用程序。
序列号(SequenceNumber)--字段长度为32位,序列号确定了发送方发送的数据流中被封装的数据所在位置。
确认号(AcknowledgmentNumber)--字段长度为32,确认号确定了源点下一次希望从目标接收的序列号。
报头长度(HeaderLength)--字段长度为4位,又称数据偏移量,报头长度指定了以32位为单位的报头长度。
保留(Reserved)--字段长度为6位,通常设置为0。
标记(Flag)--包括8个1位的标记,用于流和连接控制。
它们从左到右分别是:
拥塞窗口减少(CongestionWindowReduced,CWR)、ECN-Echo(ECE)、紧急(URG)、确认(ACK)、弹出(PSH)、复位(RST)、同步(SYN)和结束(FIN)。
窗口大小(WindowSize)--字段长度为16位,主要用于流控制。
校验和(Checksum)--字段长度为16位,它包括报头和被封装的数据,校验和允许错误检测。
紧急指针(UrgentPointer)--字段长度16位,仅当URG标记位置时才被使用,这个16位数被添加到序列号上用于指明紧急数据的结束。
可选项(Options)--字段用于指明TCP的发送进程要求的选项。
最常用的可选项是最大段长度,最大段长度通知接收者发送者愿意接收的最大段长度。
为了保证报头的长度是32位的倍数,所以使用0填充该字段的剩余部分。
1.2UDP段包头
UDP段包头
源端口(SourcePort)和目地端口(DestinationPort)--为封装的数据指定了源和目的应用程序。
UDP长度(Length)--指明了以八位组为单位的整个段长度。
校验和(Checksum)--包括整个段的校验,但不同于TCP,UDP的校验和是可选的,当不使用校验和时,此字段全部设置为0。
1.3IPV4数据包包头
IPV4数据包包头
报头长度(headerlength)--字段长度为4位,它标识32位字长的IP报头长度。
服务类型(TypeofService,ToS)--字段长度为8位,用来指定特殊的数据包处理方式。
实际上被划分为两个字段:
优先权和ToS.优先权用来设置数据包处理的优先级;ToS允许按照吞吐量、时延、可靠性和费用选择传输方式(ToS所有位通常被设置为0--普通)。
总长度(TotalLength)--字段长度为16位,标识数据长度,以八位组为单位计,其中包括IP报头。
16位的二进制数用十进制表示为IP数据包的最大长度是65535。
标识符(Identifier)--字段长度为16位。
当数据包原始长度大于链路MTU,则会在数据成帧之前进行分段,而标识符则是这些数据分段的标记。
标记字段(Flag)--字段长度为3位,第一位没有使用;第2位表示数据不分段(DF),当DF设置为1时,路由器不会对数据包进行分段处理,如果此数据包的大小多于MTU值,路由器会将此包丢弃,并向源点发送一个错误消息扩展Ping可以对DF进行设置,用于链路MTU的测试;第三位表示还有更多字段(MF),路由器在对数据分段时,最后一个分段的MF设为0,其他分段设为1,以便接收者接收数据直到MF位为0的分段为止。
分段偏移(FragmentOffset)--字段长度为13位,以8个八位组为单位。
用于指明分段起点相对于报头起点的偏移量,以便对方接到数据帧能够按正确的顺序重组数据包。
生存时间(Timetolive,TTL)--字段长度为8位,每到一台路由器都会降低TTL值,当TTL为0时,路由器会丢弃该数据包并向源点发送错误信息,TTL可以防止数据包在网上无休止地被传输。
协议(Protocol)--字段长度为8位,指定了数据包中信息的类型。
其中众所周知的协议号:
ICMP为1、IGMP为2、被IP协议封装的IP为4、TCP为6、UDP为17、IDRP为45、RSVP为46、GRE为47、NHRP(NBMA下一跳解悉协议)为54、IGRP为88、OSPF为89。
报头校验和(HeaderChechsum)--字段长度为16位,校验报头在传输中是否发生错误(只计算机报头,不计算被封装的实据)。
由于TTL值会减少,所以每台路由器都要对报头进行校验重新计算。
源地址和目的地址(SourceandDestinationAddress)--字段长度为32位,分别表示发送数据包源点和目的地的IP地址。
可选项(Options)--字段长度可变,内容也是可选的,该字段主要是用于测试。
包括以下四方面:
松散路由选择(LooseSourceRouting)--它给出了一连串路由器接口的IP地址序列。
数据包必须沿着地址序列传送,但允许相继两个地址间跳过多台路由器。
严格源路由选择(StrictSourceRouting)--与松散路由选择差不多,不过当下一跳路由IP地址不在地址序列表中,则会发生错误。
记录路由(RecordRouting)--此选项提供路由追踪功能,记录双向路径上的出站接口IP地址信息。
时间戳(Timestamp)--记录数据到达该路由器的时间。
填充(Padding)--该字段通过在可选项后面添加0来补足32位,这样保证报文长度是32位的倍数。
1.4以太网帧
以太网帧
CRC字段用于帧内后续字节差错的循环冗余码检验(检验和),(它也被称为FCS或帧检验序列)。
802.3标准定义的帧和以太网的帧都有最小长度要求。
802.3规定数据部分必须至少为38字节,而对于以太网,则要求最少要有46字节。
为了保证这一点,必须在不足的空间插入填充(PAD)字节。
第二章拆包
2.1拆包流程图
拆包目前常用方式.
1.动态缓冲区暂存方式.之所以说缓冲区是动态的是因为当需要缓冲的数据长度超出缓冲区的长度时会增大缓冲区长度.
大概过程描述如下:
A,为每一个连接动态分配一个缓冲区,同时把此缓冲区和SOCKET关联,常用的是通过结构体关联.
B,当接收到数据时首先把此段数据存放在缓冲区中.
C,判断缓存区中的数据长度是否够一个包头的长度,如不够,则不进行拆包操作.
D,根据包头数据解析出里面代表包体长度的变量.
E,判断缓存区中除包头外的数据长度是否够一个包体的长度,如不够,则不进行拆包操作.
F,取出整个数据包.这里的"取"的意思是不光从缓冲区中拷贝出数据包,而且要把此数据包从缓存区中删除掉.删除的办法就是把此包后面的数据移动到缓冲区的起始地址.
图2.0拆包流程图
2.2拆包相关代码.
先看包头结构定义
#pragmapack(push,1)//开始定义数据包,采用字节对齐方式
/*----------------------包头---------------------*/
typedefstructtagPACKAGEHEAD
{
BYTEVersion;
WORDCommand;
WORDnDataLen;//包体的长度
}PACKAGE_HEAD;
#pragmapack(pop)//结束定义数据包,恢复原来对齐方式
然后看存放数据和"取"数据函数.
/*****************************************************************************
Description:
添加数据到缓存
Input:
pBuff[in]-待添加的数据;nLen[in]-待添加数据长度
Return:
如果当前缓冲区没有足够的空间存放pBuff则返回FALSE;否则返回TRUE。
******************************************************************************/
BOOLCDataBufferPool:
:
AddBuff(char*pBuff,intnLen)
{
m_cs.Lock();///临界区锁
if(nLen<0)
{
m_cs.Unlock();
returnFALSE;
}
if(nLen<=GetFreeSize())///判断剩余空间是否足够存放nLen长的数据
{
memcpy(m_pBuff+m_nOffset,pBuff,nLen);
m_nOffset+=nLen;
}
else///若不够则扩充原有的空间
{
char*p=m_pBuff;
m_nSize+=nLen*2;//每次增长2*nLen
m_pBuff=newchar[m_nSize];
memcpy(m_pBuff,p,m_nOffset);
delete[]p;
memcpy(m_pBuff+m_nOffset,pBuff,nLen);
m_nOffset+=nLen;
m_cs.Unlock();
returnFALSE;
}
m_cs.Unlock();
returnTRUE;
}
/*****************************************************************************
Description:
获取一个完整的包
Input:
Buf[out]-获取到的数据;nLen[out]-获取到的数据长度
Return:
1、当前缓冲区不够一个包头的数据2、当前缓冲区不够一个包体的数据
******************************************************************************/
int CDataBufferPool:
:
GetFullPacket(char*Buf,int&nLen)
{
m_cs.Lock();
if(m_nOffset { m_cs.Unlock(); return1; } PACKAGE_HEAD*p=(PACKAGE_HEAD*)m_pBuff; if((m_nOffset-m_PacketHeadLen)<(int)p->nDataLen)//当前缓冲区不够一个包体的数据 { m_cs.Unlock(); return2; } //判断包的合法性 /* intIsIntegrallity=ValidatePackIntegrality(p); if(IsIntegrallity! =0) { m_cs.Unlock(); returnIsIntegrallity; } */ nLen=m_PacketHeadLen+p->nDataLen; memcpy(Buf,m_pBuff,nLen); m_nOffset-=nLen; memcpy(m_pBuff,m_pBuff+nLen,m_nOffset); m_cs.Unlock(); return0; } 第三章SQLite数据库 3.1Android数据库简介 Android系统提供了三种数据存储方式,第一种是文件存储;第二种是SharedPreferences存储;第三种就是SQLite数据库存储。 本设计选用SQLite数据库存储技术,SQLite是一款轻型的数据库,它的设计目标是嵌入式的,而且目前己经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中只需要几百K的内存。 它能够支持Windows/Linux/Unix等主流的操作系统,同时能够跟多种程序语言相结合,比如PHP、Java等。 图34表示数据库内部结构。 图34SQLite数据库内部结构 (1)创建数据库 Android不自动提供数据库。 在Android应用程序中使用SQLite,必须自创建数据库,然后创建表索引,填充数据。 Android提供了SQLiteOpenHelper助你创建一个数据库,只要继SQLiteOpenHelper类,就可以轻松的创建需的数据库。 SQLiteopenHelper类根据开发应用程序的需要,封装了创建和新数据库使用的逻辑。 SQLiteOpenHelper: 的子类,至少需要实现三个方法: 构造函数,调用父类SQLiteOpenHelper的构造函数。 这个方法需要四个数: 上下文环境(例如,一个Activity),数据库名字,一个可选的游标工厂(通常是Null),一个代表你正在使用的数据库模型版本的整数。 OnCreate()方法,它需要一个SQLiteDatabase对象作为参数,根据需要对这对象填充表和初始化数据。 OnUpgrage()方法,它需要三个参数,一个SQLiteDatabase对象,一个旧的本号和一个新的版本号,这样就可以清楚如何把一个数据库从旧的模型转变新的模型。 (2)创建表和索引为了创建表和索引,需要调用SQLite数据库中execSQL()方法来执行DDL语句。 如果没有异常,这个方法没有返回值。 这条语句会创建一个名为mytable的表,表有一个列名为id,并且是主键,这列的值是会自动增长的整数(例如,当你插入一行时,SQLite会给这列自动赋值),另外还有两列: tit1e(字符)和Value(浮点数)。 SQLite会自动为主键去创建索引。 通常情况下,第一次创建数据库时创建了表和索引。 (3)数据库操作 完成数据库的创建和表,索引的创建后,可以进行各种数据操作,包括插入(INSERT),删除(DELETE),更新(UPDATE),查询(QUERY)。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据 打包