TCP紧急数据称为"带外数据"。
TCP用户数据称为"带内数据"。
1、先了解数据结构
脱离以太网帧数据结构,谈TCP/IP都是在瞎讲。因为大多人都停留在调用阶段,我以前也一样,只会调用,离开例子,就不知道下手了。
struct TCP_IPv4_Packet_Type
{
u8 dest_MAC[6]; //存放目的MAC地址
u8 src_MAC[6]; //存放源MAC地址
u8 type[2];
//太网帧的类型
//0x0800表示后面跟着的是IPV4数据包;
//0x0806表示后面跟着的是ARP数据包;
//0x86dd表示后面跟着的是IPV6数据包;
///IP头部/IPV4头部(IPv4 header),占20个字节///
u8 vhl;
//IP版本和IP头部长度:
//版本(Version):0x45 的高4位为0x04表示IPv4版本
//IPV4头部长度(Header Length):低4位为0x05,表示IPv4头部长度,单位为32位字
//即IPV4头部为:5 * 4 = 20 字节。
u8 tos; //服务类型
u8 len[2]; //IP报文总长度2个字节,它是"IP头,TCP头部和TCP数据的长度"
u8 ipid[2];
//TCP数据包ID,2字节,表示数据包唯一ID,分片时,所有分片段使用相同ID,表示属于同一包数据。
//是"发送方"采用"ID计数器"得到的数值
u8 ipoffset[2]; //标志字段和片偏移字段
//标志字段第1位(保留位,bit15)始终为0;
//标志字段第2位(DF位,bit14),DF=1禁止对该数据包分片;DF=0允许分片;
//标志字段第3位(MF位,bit13),MF=1表示后续还有分片;MF=0表示当前是最后一个分片;
//片偏移字段占13位,bit12:0,表示"当前分片"在原始数据包中的起始位置。
//计算方式:偏移值 = 起始字节数 / 8。如果偏移值为5,则表示分片在原始数据包中,是从第5*8=40字节开始。
//0x40 0x00, 禁止对该数据包分片。
//DF=0允许分片,但MF=0表示当前是最后一个分片,也就是没有分片。
u8 ttl; //数据包生存时间
u8 protocol; //协议:1字节,如6表示TCP,17表示UDP
u8 ipchksum[2]; //检验和
u8 Send_IP[4];//发送方IP地址,4字节
u8 Receive_IP[4];//接收方IP地址,4字节
////TCP头部(TCP header)总共为20字节////
//但是,在某些场合,特别是在处理选项(Options)时,TCP头部的长度为24字节。
u8 Send_Port[2]; //发送方端口,0x06 0x10,表示发送方端口为1552
u8 Receive_Port[2]; //接收方端口,0x13 0x88,表示接收方端口:5000
u8 seqno[4]; //序列号,比如:0x4B 0xF8 0x27 0x37
u8 ackno[4]; //确认号,比如:0x00 0x00 0x00 0x00
u8 tcpoffset;//表示TCP头部的长度,即(0x70>>4)*4=28;
u8 flags;
//TCP标志位定义如下:
#define TCP_FIN 0x01
//bit0终止标志,表示关闭连接。如果发送带有"FIN标志位的TCP数据包",则连接将被断开
#define TCP_SYN 0x02
//bit1为同步请求标志,表示这是一个同步请求:如果客户端发送SYN包,则服务器响应SYN-ACK包,然后客户端发送ACK确认包。
#define TCP_RST 0x04
//bit2为复位标志,当发送RST包后,发送方会立即关闭连接,而不需要等待对方的确认。接收方收到RST包后,也会立即关闭连接。
#define TCP_PSH 0x08
//bit3推送标志,表示推送。push操作是指将"数据包"立即发送给应用程序,而不是在缓冲区中排队。
#define TCP_ACK 0x10 //bit4确认标志,表示响应。
#define TCP_URG 0x20 //bit5紧急标志,表示紧急指针。
#define TCP_CTL 0x3f
u8 wnd[2];
//窗口大小, 对于发送方来说告诉接收方,当前"发送方接收的缓冲区"的大小
u8 tcpchksum[2]; //TCP头部校验和
u8 urgp[2];
//紧急指针,必须配合URG标志位一起使用的,只有在URG被置位时才有意义。
u8 optdata[4];
//optdata[0]为"最大段大小"占用的字节数
//optdata[1]为TCP选项中MSS选项的长度
//MSS选项的长度占1个字节,最大段高8数值,最大段低8数值
};
//定义连接表
struct ConnectTableType
{
u8 Remote_IP[4]; //远程设备的IP地址
u8 Local_Port[2]; //本地TCP端口
u8 Remote_Port[2]; //远程TCP端口
u8 Receive_next[4]; //记录期望收到的"下一包数据的序列号"
u8 Send_next[4]; //记录"上次发送的数据包序列号"
u16 len; //记录"上次发送的用户数据长度"
u8 MSS[2];
//记录"远程接收窗口"大小
//对IPv4来说,MSS值为1518-14-20-20-4=1460;
//对IPv6来说,MSS值为1518-14-40-20-4=1440;
u8 initialMSS[2]; //记录"远程接收窗口"大小
u8 ConnectFlag;
//如果建立TCP连接,则TCP_Connect_Flag置1
u8 TCPStateFlag; //TCP状态:关闭,连接,等待
//StateFlag的值如下:
#define TCP_CLOSED 0 //TCP断开连接
#define TCP_SYN_RECEIVED 1 //TCP接收到SYN请求数据包
#define TCP_SYN_SENT 2 //TCP发送SYN数据包
#define TCP_ESTABLISHED 3 //TCP已经建立"连接"
#define TCP_FIN_WAIT_1 4
#define TCP_FIN_WAIT_2 5
#define TCP_CLOSING 6 //TCP正在关闭中
#define TCP_TIME_WAIT 7
#define TCP_LAST_ACK 8
#define TCP_TS_MASK 15
#define TCP_STOPPED 16
u16 Time;
//连接表更新计数器
//如果使用这个连接,则令Time=0;
//0.5秒时间到,如果这个IP地址不为0,则令Time=Time+1;
//设计的目的是为了将长时间不用的IP地址清除。
u8 *pUserData;
u16 UserDatalength;
#define UIP_URGDATA 1 //支持"紧急指针"
#if UIP_URGDATA > 0
void *uip_urgdata;
//The uip_urgdata pointer points to urgent data (out-of-band data), if present.
u16 uip_urglen;
u16 uip_surglen;
#endif
};
2、TCP紧急数据
"TCP紧急数据"本质上是用户数据流中的"一个特殊字节",其位置由"紧急指针"定义。
如果发送"多个紧急数据",则只有最后一个字节会被保留,之前的紧急数据会被覆盖。
接收端通过读取"紧急指针",分析它指向的字节,从而得到"紧急数据",而用户数据,
则按顺序从接收缓冲区读取。
如果紧急标志URG=0,则IP头部中的len为"IP头长度+TCP头长度+用户数据长度"。
如果紧急标志URG=1,则IP头部中的len为"IP头长度+TCP头长度+用户数据长度+1"。
注意:
紧急指针值 > 用户数据长度值 + 1个字节的紧急数据,说明有多个紧急数据,即紧急数据长度大于1。
紧急指针值 == 用户数据长度值 + 1个字节的紧急数据,说明只有一个紧急数据,即紧急数据长度等于1。
紧急数据下标值(urgSeq) = 用户数据起始下标值(seq) + 紧急指针值(urgpoint)-1
这里的下标值是数组的下标值
注意:紧急数据必须随"用户数据"一起发送,TCP不会单独发送紧急数据。
因为只有一个紧急指针,这就意味着它只有"一个字节的紧急数据"。
紧急指针值是"紧急数据下一个字节"的下标。
比如:
如果用户数据起始下标值seq = 10,紧急指针值urgpoint = 5,
那么紧急数据下标值urgSeq = 10 + 5 -1 = 14
当urgpoint=5时,如果用户数据和紧急数据为"Hello",则紧急指针值是"紧急数据o"的下一个字节的下标值。
用户数据长度值为4。
如果只有1个字节的紧急数据,则紧急指针值 = 用户数据长度值 + 1个字节的紧急数据
如果有多个字节的紧急数据,则紧急指针值 > 用户数据长度值 + 1个字节的紧急数据
当urgpoint=0时,如果用户数据和紧急数据为"HelloBaby",则指针指向H,无紧急数据。
void URG_Data_packet (void)
{
//目的MAC
TCP_RX_buf[0]=0xB4;TCP_RX_buf[1]=0x2E;TCP_RX_buf[2]=0x99;
TCP_RX_buf[3]=0x59;TCP_RX_buf[4]=0xEC;TCP_RX_buf[5]=0x1E;
//源MAC
TCP_RX_buf[6]=0x00;TCP_RX_buf[7]=0x08;TCP_RX_buf[8]=0xDC;
TCP_RX_buf[9]=0x11;TCP_RX_buf[10]=0x11;TCP_RX_buf[11]=0x02;
TCP_RX_buf[12]=0x08;TCP_RX_buf[13]=0x00; //0x0800表示后面跟着的是IPV4数据包;
TCP_RX_buf[14]=0x45;
TCP_RX_buf[15]=0x00;
TCP_RX_buf[16]=0x00;TCP_RX_buf[17]=0x2C;
//报文总长度44
//如果没有用户数据和紧急数据,则它是"IP头,TCP头部,TCP头部数据的长度"
//如果有用户数据和紧急数据,则它是"IP头,TCP头部,TCP头部的长度,用户数据长度,紧急数据长度"
TCP_RX_buf[18]=0x00;TCP_RX_buf[19]=0x04; //TCP数据包ID,表示数据包唯一ID
TCP_RX_buf[20]=0x00;TCP_RX_buf[21]=0x00;
//0x40 0x00, 禁止对该数据包分片。0x0000表示最后一包,这里就是没有分包。
TCP_RX_buf[22]=0x40;//IP数据包生存时间
TCP_RX_buf[23]=0x06;//6表示TCP协议
TCP_RX_buf[24]=0xF6;TCP_RX_buf[25]=0xA8; //IP头部检验和
TCP_RX_buf[26]=0xC0;TCP_RX_buf[27]=0xA8;TCP_RX_buf[28]=0x01;TCP_RX_buf[29]=0x11;
//发送方IP
TCP_RX_buf[30]=0xC0;TCP_RX_buf[31]=0xA8;TCP_RX_buf[32]=0x01;TCP_RX_buf[33]=0xBE;
//接收方IP
TCP_RX_buf[34]=0x03;TCP_RX_buf[35]=0xE8;//发送方端口
TCP_RX_buf[36]=0x13;TCP_RX_buf[37]=0x88;//接收方端口
TCP_RX_buf[38]=0x00;TCP_RX_buf[39]=0x00;TCP_RX_buf[40]=0xA0;TCP_RX_buf[41]=0xB5;
//序列号
TCP_RX_buf[42]=0x12;TCP_RX_buf[43]=0xDC;TCP_RX_buf[44]=0x05;TCP_RX_buf[45]=0x74;
//确认号
TCP_RX_buf[46]=0x50; //表示TCP头部的长度,即(0x60>>4)*4=20;
TCP_RX_buf[47]=0x38; //TCP标志位
TCP_RX_buf[48]=0x05;TCP_RX_buf[49]=0xB4;
// 告诉接收方,当前"发送方接收的缓冲区"的大小
TCP_RX_buf[50]=0xC8;TCP_RX_buf[51]=0xCC; //TCP头部校验和
TCP_RX_buf[52]=0x00;TCP_RX_buf[53]=0x04;//紧急指针
TCP_RX_buf[54]='E';TCP_RX_buf[55]='F';TCP_RX_buf[56]='G';//用户数据
TCP_RX_buf[57]='H';//紧急数据
}
//函数功能:发送PSH数据包,用户数据是以pData为首地址的前len个字节
/*
发送PSH数据包时,只是发送用户数据:
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x2B 0x00 0x05 0x00 0x00 0x40 0x06 0xF6 0xA8
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x09 0xC4 0x13 0x88
0x00 0x00 0xA0 0xB2 0x14 0xE2 0x2F 0xDE
0x50 0x18 0x05 0xB4 0x9E 0xF4 0x00 0x00
ABC
发送PSH数据包时,不但发送了用户数据,而且还连带发送了紧急数据:
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x2C 0x00 0x04 0x00 0x00 0x40 0x06 0xF6 0xA8
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x03 0xE8 0x13 0x88
0x00 0x00 0xA0 0xB5 0x12 0xDC 0x05 0x74
0x50 0x38 0x05 0xB4 0xC8 0xCC 0x00 0x04
EFGH
*/
void ENC28J60_Send_PSH_data_packet(struct ConnectTableType *pConnect,u8 *pData, u16 len,u8 *urgData)
{
u8 ret;
struct TCP_IPv4_Packet_Type *pTX;
u16 tmp,chksum;
int t;
ret=Load_ConnectParameter_To_MyNetworkInformation_After_TCP_Established(pConnect);
//将"pConnect指向的连接参数"装载到MyNetworkInformation中
//主要是用来装载"连接参数",如:远程IP,远程MAC,远程端口,本地端口和连接表指针
if(ret==1)
{
pTX=(struct TCP_IPv4_Packet_Type *)&TCP_TX_buf[0];
memcpy(pTX->dest_MAC, MyNetworkInformation.Remote_MAC, 6);
//添加远程MAC地址
memcpy(pTX->src_MAC,MyNetworkInformation.Local_MAC, 6);
//添加本地MAC地址
pTX->type[0] = (u8)(UIP_ETHTYPE_IP4>>8);
pTX->type[1] = (u8)(UIP_ETHTYPE_IP4);
//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;
pTX->vhl = 0x45;//设置"IP版本和头部长度"
pTX->tos = 0;//设置"服务类型"
tmp = UIP_IPH_LEN + UIP_TCPH_LEN + len;
//"IP头部的大小20"+"TCP头部的大小20"+"用户数据长度len" ,得到tmp
if(urgData)
{
tmp=tmp+1;//紧急数据占1个字节
}
#if UIP_CONF_IPV6
//For IPv6, the IP length field does not include the IPv6 IP header length.
pTX->len[0] = ((tmp - UIP_IPH_LEN) >> 8);
pTX->len[1] = ((tmp - UIP_IPH_LEN) & 0xff);
#else
pTX->len[0] = (tmp >> 8);
pTX->len[1] = (tmp & 0xff);
//设置IP头部中的"IP头部,TCP头部和可选项"的字节总数
#endif
++My_IPID;
pTX->ipid[0] = My_IPID >> 8;
pTX->ipid[1] = My_IPID & 0xff;//设置"标识"
pTX->ipoffset[0] =0;
pTX->ipoffset[1] = 0;
//设置"标志字段和片偏移字段",0x0000表示不分包
pTX->ttl = UIP_TTL;//修改IP头部中的"生存时间值"
pTX->protocol = UIP_PROTO_TCP;//修改"IP头部中的协议"为6
memcpy(pTX->Send_IP,MyNetworkInformation.Local_IP,4);
//设置"发送方ip地址"
memcpy(pTX->Receive_IP,MyNetworkInformation.Remote_IP,4);
//设置"接收方IP地址"
pTX->ipchksum[0] = 0;pTX->ipchksum[1] = 0;
chksum=~(IPv4_checksum(&TCP_TX_buf[UIP_LLH_LEN]));
pTX->ipchksum[0] = (u8)(chksum>>8);
pTX->ipchksum[1] = (u8)(chksum);
//设置"IPV4检验和"
memcpy(pTX->Send_Port, MyNetworkInformation.Local_Port, 4);
//设置"发送方端口"
memcpy(pTX->Receive_Port, MyNetworkInformation.Remote_Port, 4);
//设置"接收方端口
//使用接收到的确认号作为"TCP头部中的序列号"
pTX->seqno[0] = pConnect->Send_next[0];
pTX->seqno[1] = pConnect->Send_next[1];
pTX->seqno[2] = pConnect->Send_next[2];
pTX->seqno[3] = pConnect->Send_next[3];
//修改"TCP头部中的确认号"
//因为这里不是应答远程设备,所以不用将"接收到的序列号+用户数据长度"作为确认号
pTX->ackno[0] = pConnect->Receive_next[0];
pTX->ackno[1] = pConnect->Receive_next[1];
pTX->ackno[2] = pConnect->Receive_next[2];
pTX->ackno[3] = pConnect->Receive_next[3];
t=pTX->len[0];t=(u16)(t<<8);
t=t|pTX->len[1];
t=t-UIP_TCPH_LEN - UIP_IPH_LEN;
if(t<=0)t=1;
pConnect->len = t;
pTX->tcpoffset = (UIP_TCPH_LEN / 4) << 4;
//TCP数据包无optdata[],BUF->tcpoffset=0x50
//UIP_TCPH_LEN=20,设置"TCP头部的长度"为5*4=20
if(urgData)
{
pTX->flags = (TCP_PSH | TCP_ACK|TCP_URG);
//TCP头部的标志位为"PSH+ACK+URG"
}
else
{
pTX->flags = (TCP_PSH | TCP_ACK);
//TCP头部的标志位为"PSH+ACK
}
pTX->wnd[0] = ((UIP_TCP_MSS) >> 8);//修改"TCP头部中的窗口大小"
pTX->wnd[1] = ((UIP_TCP_MSS) & 0xff);//修改"TCP头部中的窗口大小"
if(urgData)
{
TCP_TX_Length = UIP_LLH_LEN + UIP_IPH_LEN + UIP_TCPH_LEN + len + 1;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20"+"用户数据"+"紧急数据",得到TCP_TX_Length
tmp=len+1;
pTX->urgp[0] = (u8)(tmp>>8);
pTX->urgp[1] = (u8)(tmp);//修改TCP头部中的"紧急指针"
//添加用户数据
tmp = UIP_LLH_LEN + UIP_IPH_LEN + UIP_TCPH_LEN;
for(;tmp<TCP_TX_Length-1;tmp++)
{
TCP_TX_buf[tmp]=*pData;
pData++;
}
if(urgData)
{
TCP_TX_buf[tmp]=*urgData;//紧急数据
}
}
else
{
tmp = UIP_LLH_LEN + UIP_IPH_LEN + UIP_TCPH_LEN;
TCP_TX_Length = UIP_LLH_LEN + UIP_IPH_LEN + UIP_TCPH_LEN + len;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20"+"用户数据",得到TCP_TX_Length
pTX->urgp[0] = 0;
pTX->urgp[1] = 0;//修改TCP头部中的"紧急指针"
//添加用户数据
for(;tmp<TCP_TX_Length;tmp++)
{
TCP_TX_buf[tmp]=*pData;
pData++;
}
}
//计算TCP头部校验和
pTX->tcpchksum[0] = 0;pTX->tcpchksum[1] = 0;
chksum=~(TCPv4_header_checksume());
//计算TCP头部的校验和
pTX->tcpchksum[0] = (u8)(chksum>>8);
pTX->tcpchksum[1] = (u8)(chksum);
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
}
}
//更新序号和确认号,并打印接收到的用户数据和紧急数据
void Update_SequenceNumber_AcknowledgeNumber_And_Calculate_UserDataLength(
struct ConnectTableType *pConnect,struct TCP_IPv4_Packet_Type *pRX)
{
int t;
u8 iplen,tcplen;
u16 len;
pConnect->Receive_next[0] = pRX->seqno[0];//保存"收到的序列号"
pConnect->Receive_next[1] = pRX->seqno[1];
pConnect->Receive_next[2] = pRX->seqno[2];
pConnect->Receive_next[3] = pRX->seqno[3];
pConnect->Send_next[0]=pRX->ackno[0];
pConnect->Send_next[1]=pRX->ackno[1];
pConnect->Send_next[2]=pRX->ackno[2];
pConnect->Send_next[3]=pRX->ackno[3];
//在后面发送ACK数据包时,用作序列号
iplen=pRX->vhl;
iplen=(u8)(iplen&0x0f)*4;
//低4位表示IPv4头部长度,单位为32位字
//如果IPV4头部长度低4位为5,则5 * 4 = 20 字节。
tcplen=pRX->tcpoffset;
//高4位表示TCP头部长度,单位为32位字
//如果TCP头部长度高4位为5,则5 * 4 = 20 字节。
tcplen=(u8)(tcplen>>4)*4;
t=pRX->len[0];t=(u16)(t<<8);
t=t|pRX->len[1];
//如果URG标志位为1,则以太网数据格式为:以太网头+IP头+TCP头+用户数据+紧急数据
//IP头部中的len为"IP头长度+TCP头长度+用户数据长度+紧急数据长度"。
t=t-iplen-tcplen;//"用户数据+紧急数据"的长度
len=UIP_LLH_LEN + iplen + tcplen;
if(t<=0)//无"用户数据+紧急数据"
{
pConnect->len =1;
pConnect->pUserData=NULL;
}
else//有用户数据
{
/*检查URG标志。若该标志已设置,则表示该数据段携带紧急数据,必须传递至应用程序。
Check the URG flag. If this is set, the segment carries urgent data that we must pass to the application. */
if((pRX->flags & TCP_URG) != 0)//有紧急数据
{//标志位中的URG:bit5紧急指针有效,表示数据需立即处理 。
#if UIP_URGDATA > 0 //支持紧急数据
pConnect->uip_urglen = (pRX->urgp[0] << 8) | pRX->urgp[1];
if(pConnect->uip_urglen > t)
{
//紧急指针值 > 用户数据长度值 + 1个字节的紧急数据,说明紧急数据的长度大于1
//紧急指针值 == 用户数据长度值 + 1个字节的紧急数据,说明紧急数据的长度等于1
//There is more urgent data in the next segment to come.
pConnect->uip_urglen = t;//修改紧急指针值
}
pConnect->len=pConnect->uip_urglen;//记录数据长度
pConnect->pUserData = &TCP_RX_buf[len];
pConnect->uip_urgdata =pConnect->pUserData + pConnect->uip_urglen-1;
#if TCP_Debug == 1
Print_Receive_Package(pConnect->pUserData,pConnect->len);
Print_Receive_Package(pConnect->uip_urgdata,1);
#endif
#endif
}
else//无紧急数据
{
// pConnect->uip_urglen = 0;//修改紧急指针值
pConnect->pUserData = &TCP_RX_buf[len];
pConnect->len= t;
#if TCP_Debug == 1
Print_Receive_Package(pConnect->pUserData,pConnect->len);
#endif
}
}
}
接收解析结果:

