学习和SYN相关的数据包,先了解TCP状态,TCP连接建立(SYN/SYN-ACK/ACK三次握手) 。学习TCP/IP,最好找ENC28J60这样的芯片,因为它没有硬件TCP/IP栈,有利于我们深入学习各种数据包的结构。我学习过W5500,它有硬件TCP/IP栈,学完之后,并不了解以太网数据的结构。在接触uip之后,我才明白以太网结构。
- 关闭(Closed)
这是TCP连接的初始状态。
- 同步已发送(SYN-SENT)
在主动打开连接时,客户端会发送一个连接请求(SYN数据包)给服务器,在没有收到服务器的确认请求(SYN+ACK数据包)
之前的这段时间,就是同步已发送状态。
- 同步收到(SYN-RECEIVED)
当收到客户端的连接请求(SYN数据包)时,服务器就会发送确认请求(SYN+ACK数据包),
在没有收到客户端的确认(ACK数据包)之前的这段时间,此时,服务器处于SYN-RECEIVED状态。
- 打开(ESTABLISHED)
一旦客户端发送确认(ACK数据包),则客户端进入ESTABLISHED状态。
当收到客户端发送的确认(ACK数据包),则服务端进入ESTABLISHED状态。
这表示TCP连接已经建立,可以开始数据的传输。
一、相关宏和变量定义
//以太网帧的类型:
#define UIP_ETHTYPE_ARP 0x0806 //ARP请求包类型
#define UIP_ETHTYPE_IP4 0x0800 //IPV4数据包类型
#define UIP_ETHTYPE_IP6 0x86dd //IPv6包类型
//以太网中的数据包协议:
#define UIP_PROTO_ICMP 1 //ICMP协议编号定义为1
#define UIP_PROTO_TCP 6 //TCP协议编号定义为6
#define UIP_PROTO_UDP 17 //TCP协议编号定义为17
#define UIP_PROTO_ICMP6 58 //ICMP6协议编号定义为58
#define UIP_TTL 64 //IP包的生存时间(TTL)
/*
1、以太网头部的大小固定为14个字节。
2、IPv4头部的大小是20个字节,而IPv6头部的大小是40个字节;
3、IPv4和IPv6的TCP头部的大小都是20字节
4、以太网帧校验序列FCS位于以太网帧的末尾,它是是基于循环冗余校验CRC算法,占4个字节。
TCP有一个最大分段大小MSS,其目的是告诉对方其接收缓存的大小。
以太网帧的最大长度为1518字节。
对IPv4来说,MSS值为1518-14-20-20-4=1460;
对IPv6来说,MSS值为1518-14-40-20-4=1440;
*/
#define UIP_BUFSIZE 1518 //uIP缓存大小
#define UIP_LLH_LEN 14 //以太网头部的大小
#if UIP_CONF_IPV6
#define UIP_IPH_LEN 40 //IPV6的IP头的大小
#else
#define UIP_IPH_LEN 20 //IPV4的IP头的大小
#endif
#define UIP_TCPH_LEN 20 //TCP头部的大小
#define UIP_FCS 4 //以太网帧校验序列的大小为4个字节
#define UIP_TCP_MSS (UIP_BUFSIZE - UIP_LLH_LEN - UIP_IPH_LEN - UIP_TCPH_LEN - UIP_FCS)
//"本地的TCP最大段大小"为1518-14-20-20-4,计算机是1460
#define UIP_OptdataIndex ( UIP_TCPH_LEN + UIP_IPH_LEN + UIP_LLH_LEN )
//即可选项位于54
u8 TCP_TX_buf[UIP_BUFSIZE];//ENC28JI60发送数据的缓存
u16 TCP_TX_Length; //记录TCP_TX_buf[]的有效字节数
u8 TCP_RX_buf[UIP_BUFSIZE];//ENC28JI60接收数据的缓存
u16 TCP_RX_Length;//记录TCP_RX_buf[]的有效字节数
//定义连接表
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状态:关闭,连接,等待
// TCPStateFlag的值如下:
#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地址清除。
};
#define My_ConnectTable_SIZE 3
struct ConnectTableType MyConnectTable[My_ConnectTable_SIZE];
//定义网络信息
struct NetworkInformationType
{
u8 Local_MAC[6]; //本地MAC地址
u8 Local_IP[4]; //本地IP地址
u8 Subnet_Mask[4];//子网掩码
u8 Router_IP[4]; //网关IP地址
u8 Local_Port[2]; //本地TCP端口
u8 Remote_IP[4]; //远程设备的IP地址
u8 Remote_MAC[6]; //远程设备的MAC地址
u8 Remote_Port[2]; //远程设备的TCP端口
struct ConnectTableType *pConnect;//记录连接表指针
};
struct NetworkInformationType MyNetworkInformation;
//定义ARP表
struct ARPTableType
{
u8 Remote_IP[4]; //远程设备的IP地址
u8 Remote_MAC[6]; //远程设备的MAC地址
u16 Time;
//ARP表更新计数器
//如果使用这个远程MAC地址,则令Time=0;
//10秒时间到,如果这个IP地址不为0,则令Time=Time+1;
//设计的目的是为了将长时间不用的IP地址和MAC地址清除。
};
#define ARPTable_SIZE 6
struct ARPTableType ARPTable[ARPTable_SIZE];
u16 My_IPID; //每个带有"IP头部"的数据包均有一个唯一的id
//ARP数据包结构
struct ARP_Packet_Type
{
u8 dest_MAC[6]; //存放目的MAC地址
u8 src_MAC[6]; //存放源MAC地址
u8 type[2];
//以太网帧的类型
//0x0800表示后面跟着的是IPV4数据包;
//0x0806表示后面跟着的是ARP数据包;
//0x86dd表示后面跟着的是IPV6数据包;
u8 hwtype[2]; //硬件类型,若是以太网,值是0x0001
u8 protocol[2]; //协议类型,若是ipv4,值是0x0800
u8 hwlen; //硬件长度,定义物理地址(MAC地址)的长度,是6
u8 protolen; //协议长度,定义ip地址长度,是4
u8 opcode[2]; //ARP操作码:ARP请求是1,ARP回复是2
u8 Send_MAC[6]; //发送方的6字节mac地址
u8 Send_IP[4]; //发送方的4字节ip地址
u8 Receive_MAC[6]; //接收方的6字节mac地址
u8 Receive_IP[4]; //接收方的4字节ip地址
};
struct 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版本
//头部长度(Header Length):低4位为0x05,表示IPv4头部长度,单位为32位字
//即IP头部/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字节
};
//ICMP数据包结构
struct ICMP_Packet_Type
{
u8 dest_MAC[6]; //存放目的MAC地址
u8 src_MAC[6]; //存放源MAC地址
u8 type[2];
//以太网帧的类型
//0x0800表示后面跟着的是IPV4数据包;
//0x0806表示后面跟着的是ARP数据包;
//0x86dd表示后面跟着的是IPV6数据包;
///////IPV4头部(IPv4 header),占20个字节///////
u8 vhl;
//IP版本和IP头部长度:
//版本(Version):0x45 的高4位为0x04表示IPv4版本
//头部长度(Header Length):低4位为0x05,表示IPv4头部长度,单位为32位字
//即IP头部/IPV4头部为:5 * 4 = 20 字节。
u8 tos;//服务类型
u8 len[2];//从"vhl开始到用户数据"的字节总数
u8 ipid[2];//标识,2字节,用于分片重组
u8 ipoffset[2];//片偏移,2字节,包含DF/MF标志和偏移量
u8 ttl;//生存时间
u8 protocol;//协议:如1表示ICMP,6表示TCP,17表示UDP
u8 ipchksum[2]; //检验和
u8 Send_IP[4];//发送方IP地址,4字节
u8 Receive_IP[4];//接收方IP地址,4字节
//////ICMP (echo) header/////
u8 ICMPtype; //ICMP请求填ICMP_ECHO=8,ICMP应答填ICMP_ECHO_REPLY=0;
u8 icode; //这里填0即可
u8 icmpchksum[2]; //包括数据在内的整个ICMP数据包的校验和
u8 id[2]; //id可以固定为1
u8 seqno[2];//序列号
};
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版本
//头部长度(Header Length):低4位为0x05,表示IPv4头部长度,单位为32位字
//即IP头部/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]; //校验和
u8 urgp[2]; //紧急指针
u8 optdata[4];
//optdata[0]为"最大段大小"占用的字节数
//optdata[1]为TCP选项中MSS选项的长度
//MSS选项的长度占1个字节,最大段高8数值,最大段低8数值
};
#define ENC28J60_Interface_Debug 1
#define Min_Local_Port 2500 //本地端口最小值为1000
#define UIP_LITTLE_ENDIAN 3412 //小端存储方式
#define UIP_BIG_ENDIAN 1234 //大端存储方式
#define UIP_BYTE_ORDER UIP_LITTLE_ENDIAN
#if UIP_BYTE_ORDER == UIP_BIG_ENDIAN
#define HTONS(n) (n)
#else
#define HTONS(n) (u16)( ( ( (u16) (n)) << 8 ) | ( ( (u16) (n) ) >> 8 ) )
//小端存储方式将val的高8位值和低8位值交换
#endif
#define HEX16(d) (u16)( ( (d[0]<<8 )&0xFF00 )|( d[1]&0xFF ) )
#define ARP_REPLY_RX_Flag 1
#define ICMP_REPLY_RX_Flag 2
#define TCP_SYN_ACK_RX_Flag 3
二、相关接口函数
//配置网卡硬件,并设置MAC地址
//返回值:0,正常;1,失败;
u8 Network_Init(void)
{
u8 res=0;
//本地IP地址,如:"192.168.1.17"
MyNetworkInformation.Local_IP[0] = 192;
MyNetworkInformation.Local_IP[1] = 168;
MyNetworkInformation.Local_IP[2] = 1;
MyNetworkInformation.Local_IP[3] = 17;
//本地子网掩码: 255.255.255.0
MyNetworkInformation.Subnet_Mask[0] = 255;
MyNetworkInformation.Subnet_Mask[1] = 255;
MyNetworkInformation.Subnet_Mask[2] = 255;
MyNetworkInformation.Subnet_Mask[3] = 0;
//本地网关:192.168.1.1,其实就是你路由器的IP地址
MyNetworkInformation.Router_IP[0] = 192;
MyNetworkInformation.Router_IP[1] = 168;
MyNetworkInformation.Router_IP[2] = 1;
MyNetworkInformation.Router_IP[3] = 1;
//本地MAC地址:0x00 0x08 0xDC 0x11 0x11 0x02
MyNetworkInformation.Local_MAC[0] = 0x00;
MyNetworkInformation.Local_MAC[1] = 0x08;
MyNetworkInformation.Local_MAC[2] = 0xDC;
MyNetworkInformation.Local_MAC[3] = 0x11;
MyNetworkInformation.Local_MAC[4] = 0x11;
MyNetworkInformation.Local_MAC[5] = 0x02;
MyNetworkInformation.Local_Port[0]=(u8)(Min_Local_Port>>8);
MyNetworkInformation.Local_Port[1]=(u8)(Min_Local_Port);
//初始化本地端口值,假定本地端口为Min_Local_Port;
printf("IP:%d.%d.%d.%d\n",\
MyNetworkInformation.Local_IP[0],\
MyNetworkInformation.Local_IP[1],\
MyNetworkInformation.Local_IP[2],\
MyNetworkInformation.Local_IP[3]);
printf("Mask:%d.%d.%d.%d\n",\
MyNetworkInformation.Subnet_Mask[0],\
MyNetworkInformation.Subnet_Mask[1],\
MyNetworkInformation.Subnet_Mask[2],\
MyNetworkInformation.Subnet_Mask[3]);
printf("Gate:%d.%d.%d.%d\n",\
MyNetworkInformation.Router_IP[0],\
MyNetworkInformation.Router_IP[1],\
MyNetworkInformation.Router_IP[2],\
MyNetworkInformation.Router_IP[3]);
printf("Local_MAC:%02x-%02x-%02x-%02x-%02x-%02x\n",\
MyNetworkInformation.Local_MAC[0],\
MyNetworkInformation.Local_MAC[1],\
MyNetworkInformation.Local_MAC[2],\
MyNetworkInformation.Local_MAC[3],\
MyNetworkInformation.Local_MAC[4],\
MyNetworkInformation.Local_MAC[5]);
res=ENC28J60_Init((u8*)MyNetworkInformation.Local_MAC);//初始化ENC28J60
ENC28J60_PHY_Write(PHLCON,0x0476);
//PHLCON:PHY模块LED控制寄存器
//指示灯状态:0x476 is PHLCON LEDA(绿)=links status, LEDB(红)=receive/transmit
TIM4_Interrupt_Initializtion(10000,72);//当arr=10000,psc=72时,则为10ms,误差为1us;
timer_set(&TCP_Timer,50);//50*10=500毫秒
timer_set(&ARP_Timer,1000);//1000*10=10000毫秒=10秒
memset( ARPTable, 0, sizeof(ARPTable) );
//清除ARP表
memset( MyConnectTable, 0, sizeof(MyConnectTable) );
//清除连接表
return res;
}
//函数功能:将的data[]中的前len个字节按照"双字节进行累加和校验"
//data为"待校验数据缓存"的起始地址
//len为待校验数据的字节总数
u16 chksum(u16 sum, const u8 *data, u16 len)
{
u16 t;
const u8 *dataptr;
const u8 *last_byte;
dataptr = data; //指向"待校验数据缓存"的起始地址
last_byte = data + len - 1; //指向"待校验数据缓存"的结束地址
while(dataptr < last_byte)//至少有两个字节才能执行此while循环
{
t = (dataptr[0] << 8) + dataptr[1];//生成"大端存储方式"的16位整型数据
sum += t;//计算"累加和"
if(sum < t)//sum越过0x0000,发生进位
{
sum++;//若有进位,则加1
}
dataptr += 2;//修改指针,为下次计算"累加和"做准备
}
if(dataptr == last_byte)//剩余一个字节
{
t = (dataptr[0] << 8) + 0;//生成"大端存储方式"的16位整型数据
sum += t;//计算"累加和"
if(sum < t)//sum越过0x0000,发生进位
{
sum++;//若有进位,则加1
}
}
return sum; //sum按照小端存储方式返回
}
//函数功能:将首地址为&uip_buf[14]的缓存中的前20个字节按照"双字节进行累加和校验";
//IPV4头部(IPv4 header),占20个字节
u16 IPv4_checksum(u8 *tmpIPDataPointer)
{
u16 sum;
sum = chksum(0, tmpIPDataPointer, UIP_IPH_LEN);
//将"大端存储方式"的tmpIPDataPointer[]中的前20个字节按照"双字节进行累加和校验"
//定义UIP_LLH_LEN=14,IP头的大小为UIP_IPH_LEN=20,IP头部/IPV4头部(IPv4 header),占20个字节
return (sum == 0) ? 0xffff : sum;
//sum按照小端存储方式返回
}
//函数功能:计算"ICMP4头数据"的校验和
u16 ICMP4_TX_chksum(void)
{
u16 upper_layer_len;
u16 sum;
struct ICMP_Packet_Type *pTX;
pTX=(struct ICMP_Packet_Type *)&TCP_TX_buf[0];
upper_layer_len = ((u16)(pTX->len[0]) << 8) + pTX->len[1];
upper_layer_len = upper_layer_len - UIP_IPH_LEN;
//上层长度upper_layer_len = ICMP报文总长度 - IP头的大小20
sum=0;
sum = chksum(sum, &TCP_TX_buf[UIP_IPH_LEN + UIP_LLH_LEN],upper_layer_len);
//&TCP_TX_buf[34]为TCP头部在TCP_TX_buf[]中的首地址
return (sum == 0) ? 0xffff : sum;
//sum按照小端存储方式返回
}
//函数功能:计算"ICMP4头数据"的校验和
u16 ICMP4_RX_chksum(void)
{
u16 upper_layer_len;
u16 sum;
struct ICMP_Packet_Type *pRX;
pRX=(struct ICMP_Packet_Type *)&TCP_RX_buf[0];
upper_layer_len = ((u16)(pRX->len[0]) << 8) + pRX->len[1];
upper_layer_len = upper_layer_len - UIP_IPH_LEN;
//上层长度upper_layer_len = ICMP报文总长度 - IP头的大小20
sum=0;
sum = chksum(sum, &TCP_RX_buf[UIP_IPH_LEN + UIP_LLH_LEN],upper_layer_len);
//&TCP_RX_buf[35]为TCP头部在TCP_RX_buf[]中的首地址
return (sum == 0) ? 0xffff : sum;
//sum按照小端存储方式返回
}
//函数功能:如果接收到的"ICMP4头部校验和"正确,返回1
u8 Check_the_ICMP4_header_checksume(void)
{
u8 ret;
u16 sum;
u16 upper_layer_len;
struct ICMP_Packet_Type *pRX;
pRX=(struct ICMP_Packet_Type *)&TCP_RX_buf[0];
upper_layer_len = ((u16)(pRX->len[0]) << 8) + pRX->len[1];
upper_layer_len = upper_layer_len - UIP_IPH_LEN;
//上层长度upper_layer_len = ICMP报文总长度 - IP头的大小20
sum=0;
sum = chksum(sum, &TCP_RX_buf[UIP_IPH_LEN + UIP_LLH_LEN],upper_layer_len);
//&TCP_RX_buf[35]为TCP头部在TCP_RX_buf[]中的首地址
/*累加"TCP头和数据"时需要考虑加法进位。 Sum TCP header and data. */
sum = (sum == 0) ? 0xffff : sum;
//sum按照小端存储方式返回
ret=0;
if(sum == 0xffff)//"ICMP4头部校验和"正确
{
ret=1;//"ICMP4头部校验和"正确,返回1
return(ret);
}
return(ret);
}
//函数功能:如果接收到的"IPV4头部校验和"正确,返回1
u8 Check_the_ipV4_header_checksume(u8 *tmpIPDataPointer)
{
u8 ret;
u16 sum;
ret=0;
/* Compute and check the IP header checksum. */
sum=IPv4_checksum(tmpIPDataPointer);
//计算"IP头部/IPV4头部(IPv4 header)"20个字节的校验和
if(sum == 0xffff)//"IP头部校验和"正确
{
ret=1;//"IP头部校验和"正确,返回1
return(ret);
}
return(ret);
}
//函数功能:计算TCP头部的校验和
u16 TCPv4_header_checksume(void)
{
u16 upper_layer_len;
u16 sum;
struct TCP_IPv4_Packet_Type *pTX;
pTX=(struct TCP_IPv4_Packet_Type *)&TCP_TX_buf[0];
#if UIP_CONF_IPV6
upper_layer_len = (((u16_t)(pTX->len[0]) << 8) + pTX->len[1]);
#else /* UIP_CONF_IPV6 */
upper_layer_len = (((u16)(pTX->len[0]) << 8) + pTX->len[1]) - UIP_IPH_LEN;
//上层长度upper_layer_len = IP报文总长度 - IP头的大小
#endif /* UIP_CONF_IPV6 */
sum = upper_layer_len + UIP_PROTO_TCP;
//累加"上层长度和IP协议"时,无需考虑进位。
//IP protocol and length fields. This addition cannot carry.
sum = chksum(sum, (u8 *)&pTX->Send_IP[0], 8);
//发送方IP和接收方IP累加时考虑进位。Sum IP source and destination addresses.
sum = chksum(sum, &TCP_TX_buf[UIP_IPH_LEN + UIP_LLH_LEN],upper_layer_len);
//&TCP_TX_buf[34]为TCP头部在TCP_TX_buf[]中的首地址
/*累加"TCP头和数据"时需要考虑加法进位。 Sum TCP header and data. */
sum= (sum == 0) ? 0xffff : sum;
return (sum);
//按照"TCP协议头"计算校验和
}
//函数功能:如果接收到的"TCP头部校验和"正确,返回1
u8 Check_Receive_TCPv4_header_checksume(void)
{
u8 ret;
u16 upper_layer_len;
u16 sum;
struct TCP_IPv4_Packet_Type *pTCPIP4_HeaderRX;
pTCPIP4_HeaderRX=(struct TCP_IPv4_Packet_Type *)&TCP_RX_buf[0];
#if UIP_CONF_IPV6
upper_layer_len = (((u16_t)(pTCPIP4_HeaderRX->len[0]) << 8) + pTCPIP4_HeaderRX->len[1]);
#else
upper_layer_len = (((u16)(pTCPIP4_HeaderRX->len[0]) << 8) + pTCPIP4_HeaderRX->len[1]) - UIP_IPH_LEN;
//上层长度upper_layer_len = IP报文总长度 - IP头的大小
#endif /* UIP_CONF_IPV6 */
sum = upper_layer_len + UIP_PROTO_TCP;
/*累加"上层长度和IP协议"时,无需考虑进位。
IP protocol and length fields. This addition cannot carry. */
sum = chksum(sum, (u8 *)&pTCPIP4_HeaderRX->Send_IP[0], 8);
//发送方IP和接收方IP需要考虑累加时考虑进位。Sum IP source and destination addresses.
sum = chksum(sum, &TCP_RX_buf[UIP_IPH_LEN + UIP_LLH_LEN],upper_layer_len);
//&TCP_RX_buf[34]为TCP头部在TCP_RX_buf[]中的首地址
/*累加"TCP头和数据"时需要考虑加法进位。 Sum TCP header and data. */
sum= (sum == 0) ? 0xffff : HTONS(sum);
ret=0;
if(sum == 0xffff)//"TCP头部校验和"正确
{
ret=1;//"TCP头部校验和"正确,返回1
return(ret);
}
return(ret);
}
//函数功能:打印发送数据包;
void Print_Send_Package(unsigned char* buf,u16 len)
{
u16 i;
u8 temp;
printf("\r\nSend_Len=%u\r\n",len);
for(i=0;i<len;i++)
{
temp=0;
if( ( (buf[i]==0x0D)||(buf[i]==0x0A) ) )
{
printf("%c",buf[i]);
temp=1;
}
if(temp==0)
{
if( ( (buf[i]>0x20)&&(buf[i]<='~') ) ) printf("%c",buf[i]);
else
{
printf(" 0x%02X",buf[i]);
printf(" ");
}
}
}
printf("\r\n");//将"\r\n"发送到调试串口,由PC显示;
}
//函数功能:打印接收数据包;
void Print_Receive_Package(unsigned char* buf,u16 len)
{
u16 i;
u8 temp;
if(len) printf("\r\nReceive_Len=%u\r\n",len);
for(i=0;i<len;i++)
{
temp=0;
if( ( (buf[i]==0x0D)||(buf[i]==0x0A) ) )
{
printf("%c",buf[i]);
temp=1;
}
if(temp==0)
{
if( ( (buf[i]>0x20)&&(buf[i]<='~') ) ) printf("%c",buf[i]);
else
{
printf(" 0x%02X",buf[i]);
printf(" ");
}
}
}
if(len) printf("\r\n");//将"\r\n"发送到调试串口,由PC显示;
}
//函数功能:打印"连接表MyConnectTable[]"
void Print_MyConnectTable(void)
{
u8 i;
struct ConnectTableType *p;
printf("\r\n");
for(i=0;i<My_ConnectTable_SIZE;i++)
{
p = &MyConnectTable[i];
printf("%u, ip=%u:%u:%u:%u ",
i,p->Remote_IP[0],p->Remote_IP[1],
p->Remote_IP[2],p->Remote_IP[3]
);
printf("lport=%u, rport=%u ",
HEX16(p->Local_Port),HEX16(p->Remote_Port)
);
printf("Receive_next=%02X_%02X_%02X_%02X ",
p->Receive_next[0],p->Receive_next[1],
p->Receive_next[2],p->Receive_next[3]
);
printf("Send_next=%02X_%02X_%02X_%02X ",
p->Send_next[0],p->Send_next[1],
p->Send_next[2],p->Send_next[3]
);
printf("DataLength=%u ",p->len);
printf("mss=%u ",HEX16(p->MSS) );
printf("initialmss=%u ",HEX16(p->initialMSS) );
printf("ConnectFlag=%u ",p->ConnectFlag);
printf("TCPTime=%u\r\n",p->Time);
}
}
//函数功能:打印"ARP表ARPTable[]"
void Print_ARP_Table(void)
{
u8 i;
struct ARPTableType *pARP_Table;
printf("\r\n");
for(i=0;i<ARPTable_SIZE;i++)
{
pARP_Table = &ARPTable[i];
printf("arp_table[%u].ip=%u:%u:%u:%u ",
i,pARP_Table->Remote_IP[0],pARP_Table->Remote_IP[1],
pARP_Table->Remote_IP[2],pARP_Table->Remote_IP[3]
);
printf("arp_table[%u].mac=%02X:%02X:%02X:%02X:%02X:%02X ",
i,pARP_Table->Remote_MAC[0],pARP_Table->Remote_MAC[1],
pARP_Table->Remote_MAC[2],pARP_Table->Remote_MAC[3],
pARP_Table->Remote_MAC[4],pARP_Table->Remote_MAC[5]
);
printf("arp_table[%u].time=%u\r\n",i,pARP_Table->Time);
}
}
//函数功能:
//如果"ARP表"中的"某个IP地址"在20分钟内未更新,则将"该条目"设置为0
//注意:ARP_Table_Update_Timer()每10秒执行一次
#define ARP_Table_UpdateTime 120
void ARP_Table_Update_Timer(void)
{
u8 i,ch;
struct ARPTableType *pARP_Table;
u16 tmp;
for(i = 0; i < ARPTable_SIZE; ++i)
{
pARP_Table = &ARPTable[i];//获取首地址
ch=0;
tmp=pARP_Table->Remote_IP[0];
tmp=tmp<<8;
tmp=tmp|pARP_Table->Remote_IP[1];
if(tmp)ch=1;
tmp=pARP_Table->Remote_IP[2];
tmp=tmp<<8;
tmp=tmp|pARP_Table->Remote_IP[3];
if(tmp)ch=1;
if(ch==1) pARP_Table->Time++;
else pARP_Table->Time=0;
if(pARP_Table->Time >= ARP_Table_UpdateTime)ch++;
//发现ARP表中有一个"IP地址和MAC地址"建立时间,长期没有更新,因此将这个IP地址设置为0
if( ch==2)
{//远程设备IP地址不为0,且建立时间超过120*10=1200秒=20分钟
memset( pARP_Table,0,sizeof(struct ARPTableType) );
}
}
#if ENC28J60_Interface_Debug == 1
Print_ARP_Table();
Print_MyConnectTable();
#endif
}
//函数功能:
//如果"连接表"中的"某个IP地址"在2分钟内未更新,则将"该条目"设置为0
#define MyConnectTable_UpdateTime 240
void MyConnectTable_Update_Timer(void)
{
u8 i,ch;
struct ConnectTableType *pMyConnectTable;
u16 tmp;
for(i = 0; i < My_ConnectTable_SIZE; ++i)
{
pMyConnectTable = &MyConnectTable[i];//获取首地址
ch=0;
tmp=pMyConnectTable->Remote_IP[0];
tmp=tmp<<8;
tmp=tmp|pMyConnectTable->Remote_IP[1];
if(tmp)ch=1;
tmp=pMyConnectTable->Remote_IP[2];
tmp=tmp<<8;
tmp=tmp|pMyConnectTable->Remote_IP[3];
if(tmp)ch=1;
if(ch==1) pMyConnectTable->Time++;
else pMyConnectTable->Time=0;
if(pMyConnectTable->Time >= MyConnectTable_UpdateTime)ch++;
//发现ARP表中有一个"IP地址和MAC地址"建立时间,长期没有更新,因此将这个IP地址设置为0
if( ch==2)
{//远程设备IP地址不为0,且建立时间超过120*10=1200秒=20分钟
pMyConnectTable->TCPStateFlag=TCP_CLOSED;
ENC28J60_Send_RST_data_packet(pMyConnectTable);
//超时断开连接
memset( pMyConnectTable,0,sizeof(struct ConnectTableType) );
}
}
}
//函数功能:
//如果"ARP表"里没有pRemote_IP指向的IP地址,则返回0;否则返回1
u8 Check_Remote_MAC_In_ARPTable_Array(u8 *pRemote_IP)
{
u8 i,ret;
struct ARPTableType *p1;
memset(MyNetworkInformation.Remote_MAC, 0xFF, 6);
//先假定"接收方的MAC地址"为"0xFF,0xFF,0xFF,0xFF,0xFF,0xFF"
ret=0;
for(i=0; i<ARPTable_SIZE;i++)
{//设置合适的本地端口
p1 = &ARPTable[i];
if( memcmp(p1->Remote_IP,pRemote_IP,4)==0 )
{//发现远程IP地址
memcpy(MyNetworkInformation.Remote_MAC, p1->Remote_MAC, 6);
//更新"接收方的MAC地址"
p1->Time=0;//因为准备使用这个远程MAC地址,则将ARP表更新计数器清零。
ret=1;//ARPTable[]有所给的IP地址
break;
}
}
memcpy(MyNetworkInformation.Remote_IP, pRemote_IP, 4);
//更新"接收方的IP地址"
return(ret);
}
//函数功能:
//从以太网接收应答或数据
u8 ENC28J60_Receive_Data_From_Ethernet_Network(void)
{
u16 type;
u8 ret,ch,t;
struct IPv4_Packet_Type *pRX;
ret=0;
TCP_RX_Length=ENC28J60_Packet_Receive(UIP_BUFSIZE,TCP_RX_buf);
//读ENC28J60的接收缓存
if(TCP_RX_Length > UIP_LLH_LEN )
{//接收到的数据长度大于"以太网头部"的长度
pRX=(struct IPv4_Packet_Type *)&TCP_RX_buf[0];
type=HEX16(pRX->type);
if(type== UIP_ETHTYPE_IP4 )
{
ch=0;
if( TCP_RX_Length>sizeof(struct IPv4_Packet_Type) ) ch++;
if( pRX->Receive_IP[0]==MyNetworkInformation.Local_IP[0] ) ch++;
if( pRX->Receive_IP[1]==MyNetworkInformation.Local_IP[1] ) ch++;
if( pRX->Receive_IP[2]==MyNetworkInformation.Local_IP[2] ) ch++;
if( pRX->Receive_IP[3]==MyNetworkInformation.Local_IP[3] ) ch++;
if( Check_the_ipV4_header_checksume(&TCP_RX_buf[UIP_LLH_LEN]) ) ch++;
//"IPV4头部校验和"正确,返回1
if(ch==6)
{
t=ICMP_Work();
if(t==1)ret=ICMP_REPLY_RX_Flag;
t=TCPIP4_Work();
if(t==1)ret=TCP_SYN_ACK_RX_Flag;
}
}
else if( type==UIP_ETHTYPE_ARP)
{
t=ARP_Work();
if(t==1)ret=ARP_REPLY_RX_Flag;
}
else
{
}
}
return(ret);
}
三、ARP数据包函数
#define ARP_Debug 1
//ARP操作码:
#define ARP_REQUEST 1 //ARP请求
#define ARP_REPLY 2 //ARP应答
/*
ARP的工作原理是通过"广播请求",在局域网内解析IP地址对应的MAC地址。
由于"广播域"受路由器隔离,无法跨越外网,
这限制了ARP的范围仅限于同一子网或广播域内。
*/
void ENC28J60_Send_ARP_Reply(void);
void ENC28J60_Send_ARP_REQUEST(void);
u8 ARP_Work(void);
void Test_ARP(void);
/*
接收"来自计算机的ARP请求"
0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x06
0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x01
0xB4 0x2E 0x99 0x59 0xEC 0x1E
0xC0 0xA8 0x01 0xBE
0x00 0x00 0x00 0x00 0x00 0x00
0xC0 0xA8 0x01 0x11
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
接收"来自计算机的ARP请求"
0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x06
0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x01
0xB4 0x2E 0x99 0x59 0xEC 0x1E
0xC0 0xA8 0x01 0xBE
0x00 0x08 0xDC 0x11 0x11 0x02
0xC0 0xA8 0x01 0x11
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
发送ARP应答
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x06
0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x02 0x00 0x08 0xDC 0x11 0x11 0x02
0xC0 0xA8 0x01 0x11 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0xC0 0xA8 0x01 0xBE
*/
//函数功能:发送"ARP应答"数据包
//当收到来远程设备的ARP请求时,则调用该函数
void ENC28J60_Send_ARP_Reply(void)
{
struct ARP_Packet_Type *pTX;
struct ARP_Packet_Type *pRX;
pTX=(struct ARP_Packet_Type *)&TCP_TX_buf[0];
pRX=(struct ARP_Packet_Type *)&TCP_RX_buf[0];
TCP_TX_Length = 0;
//设置"目的MAC地址
memcpy(pTX->dest_MAC, pRX->Send_MAC, 6);
memcpy(pTX->src_MAC, MyNetworkInformation.Local_MAC, 6);
//设置"源MAC地址"为ENC28J60的MAC地址
//因为对方有可能不知道我的MAC地址,所以使用本地MAC地址设置
pTX->type[0] = (u8)(UIP_ETHTYPE_ARP>>8);
pTX->type[1] = (u8)(UIP_ETHTYPE_ARP);
//设置太网帧的类型为0x0806,表示后面跟着的是ARP数据包;
pTX->hwtype[0]=(u8)(0x0001>>8);
pTX->hwtype[1]=(u8)(0x0001);
pTX->protocol[0]=(u8)(UIP_ETHTYPE_IP4>>8);
pTX->protocol[1]=(u8)(UIP_ETHTYPE_IP4);
pTX->hwlen=6;
pTX->protolen=4;
pTX->opcode[0] = (u8)(ARP_REPLY>>8);
pTX->opcode[1] = (u8)(ARP_REPLY);
//设置操作码:ARP应答是2
memcpy(pTX->Send_MAC, MyNetworkInformation.Local_MAC, 6);
//设置"发送方MAC地址"为ENC28J60的MAC地址
//因为对方有可能不知道我的MAC地址,所以使用本地MAC地址设置
memcpy(pTX->Send_IP, pRX->Receive_IP, 4);
//设置"发送方ip地址"
memcpy(pTX->Receive_MAC, pRX->Send_MAC, 6);
//设置"接收方MAC地址"
memcpy(pTX->Receive_IP, pRX->Send_IP, 4);
//设置"接收方ip地址"
TCP_TX_Length = sizeof(struct ARP_Packet_Type);
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
#if ARP_Debug == 1
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
#endif
TCP_TX_Length=0;
}
//函数功能:发送ARP请求,为的是获取"该局域网"内的某个设备的MAC地址
/*
发送ARP数据格式:
0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x06
0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x01 0x00 0x08 0xDC 0x11 0x11 0x02
0xC0 0xA8 0x01 0x11 0x00 0x00 0x00 0x00 0x00 0x00 0xC0 0xA8 0x01 0xBE
*/
void ENC28J60_Send_ARP_REQUEST(void)
{
struct ARP_Packet_Type *pTX;
u8 ch;
pTX=(struct ARP_Packet_Type *)&TCP_TX_buf[0];
TCP_TX_Length = 0;
memcpy(pTX->dest_MAC, MyNetworkInformation.Remote_MAC, 6);
//设置"目的MAC地址"
memcpy(pTX->src_MAC, MyNetworkInformation.Local_MAC, 6);
//设置"源MAC地址"为ENC28J60的MAC地址
pTX->type[0] = (u8)(UIP_ETHTYPE_ARP>>8);
pTX->type[1] = (u8)(UIP_ETHTYPE_ARP);
//设置太网帧的类型为0x0806,表示后面跟着的是ARP数据包;
pTX->hwtype[0]=(u8)(0x0001>>8);
pTX->hwtype[1]=(u8)(0x0001);
pTX->protocol[0]=(u8)(UIP_ETHTYPE_IP4>>8);
pTX->protocol[1]=(u8)(UIP_ETHTYPE_IP4);
pTX->hwlen=6;
pTX->protolen=4;
pTX->opcode[0] = (u8)(ARP_REQUEST>>8);
pTX->opcode[1] = (u8)(ARP_REQUEST);
//设置操作码:ARP请求是1
memcpy(pTX->Send_MAC, MyNetworkInformation.Local_MAC, 6);
//设置"发送方MAC地址"为ENC28J60的MAC地址
memcpy(pTX->Send_IP, MyNetworkInformation.Local_IP, 4);
//设置"发送方ip地址"
//设置"接收方MAC地址"
ch=0;
if(MyNetworkInformation.Remote_MAC[0]==0xFF) ch++;
if(MyNetworkInformation.Remote_MAC[1]==0xFF) ch++;
if(MyNetworkInformation.Remote_MAC[2]==0xFF) ch++;
if(MyNetworkInformation.Remote_MAC[3]==0xFF) ch++;
if(MyNetworkInformation.Remote_MAC[4]==0xFF) ch++;
if(MyNetworkInformation.Remote_MAC[5]==0xFF) ch++;
if(ch==6)
{
memset(pTX->Receive_MAC, 0x00, 6);
//设置"接收方MAC地址
}
else
{
memcpy(pTX->Receive_MAC,MyNetworkInformation.Remote_MAC, 6);
//设置"接收方MAC地址
}
memcpy(pTX->Receive_IP,MyNetworkInformation.Remote_IP, 4);
//设置"接收方ip地址"
TCP_TX_Length = sizeof(struct ARP_Packet_Type);
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
}
//函数功能:更新ARP表
void Update_ARPTable_Array(u8 *ip,u8* mac)
{
u8 i,ch,ret;
struct ARPTableType *p1,*p2;
ret=0;
for(i=0; i<ARPTable_SIZE;i++)
{//查找接收到的"发送方IP地址"是否存在ARPTable[]中
p1 = &ARPTable[i];
ch=0;
if( ip[0]==p1->Remote_IP[0] ) ch++;
if( ip[1]==p1->Remote_IP[1] ) ch++;
if( ip[2]==p1->Remote_IP[2] ) ch++;
if( ip[3]==p1->Remote_IP[3] ) ch++;
if( ch==4 )
{
ret=1;
break;
}
}
if(ret==0)
{
for(i=0; i<ARPTable_SIZE;i++)
{//查找"IP地址为0"的存储位置
p1 = &ARPTable[i];
ch=0;
if( p1->Remote_IP[0]==0 ) ch++;
if( p1->Remote_IP[1]==0 ) ch++;
if( p1->Remote_IP[2]==0 ) ch++;
if( p1->Remote_IP[3]==0 ) ch++;
if( ch==4 )
{
ret=1;
break;
}
}
}
if(ret==0)
{
p1 = &ARPTable[0];
for(i=1; i<ARPTable_SIZE;i++)
{//查找建立时间最长IP地址位置
p2 = &ARPTable[i];
if(p2->Time>p1->Time) p1=p2;
}
}
memcpy(p1->Remote_IP, ip, 4);//更新IP地址
memcpy(p1->Remote_MAC,mac,6);//更新MAC地址
p1->Time=0;
}
//函数功能:接收来自远程设备的ARP应答
u8 ARP_Work(void)
{
u16 opcode;
u8 ch1,ret;
struct ARP_Packet_Type *pRX;
ret=0;
pRX=(struct ARP_Packet_Type *)&TCP_RX_buf[0];
ch1=0;
if( TCP_RX_Length>=sizeof(struct ARP_Packet_Type) ) ch1++;
if( pRX->Receive_IP[0]==MyNetworkInformation.Local_IP[0] ) ch1++;
if( pRX->Receive_IP[1]==MyNetworkInformation.Local_IP[1] ) ch1++;
if( pRX->Receive_IP[2]==MyNetworkInformation.Local_IP[2] ) ch1++;
if( pRX->Receive_IP[3]==MyNetworkInformation.Local_IP[3] ) ch1++;
if(ch1==5)//接收到ARP数据包
{
opcode = HEX16(pRX->opcode);
if( opcode==ARP_REQUEST )//接收到ARP请求
{
ENC28J60_Send_ARP_Reply();
//发送"ARP应答"数据包
Update_ARPTable_Array(pRX->Send_IP,pRX->src_MAC);//更新ARP表
#if ARP_Debug == 1
Print_ARP_Table();
#endif
}
if( opcode==ARP_REPLY )//接收到ARP应答
{
memcpy(MyNetworkInformation.Remote_MAC,pRX->src_MAC,6);
//更新MAC地址
Update_ARPTable_Array(pRX->Send_IP,pRX->src_MAC);
//更新ARP表
ret=1;
#if ARP_Debug == 1
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
Print_ARP_Table();
#endif
}
}
return(ret);
}
//函数功能:测试发送ARP请求和接收ARP应答
void Test_ARP(void)
{
u8 pRemote_IP[4];
u8 loop,cnt,ret;
pRemote_IP[0]=192;pRemote_IP[1]=168;pRemote_IP[2]=1;pRemote_IP[3]=190;
//远程IP地址,如:"192.168.1.190"
ret=Check_Remote_MAC_In_ARPTable_Array(pRemote_IP);
//如果"ARP表"里没有pRemote_IP指向的IP地址,则返回0;否则返回1
loop=2;
while(loop)
{
ENC28J60_Send_ARP_REQUEST();
//发送ARP请求
loop--;
cnt=UIP_TTL;
while(cnt)
{
delay_ms(1);
cnt--;
ret=ENC28J60_Receive_Data_From_Ethernet_Network();
//接收ARP应答
if(ret==ARP_REPLY_RX_Flag)
{
cnt=0;
loop=0;
break;
}
}
}
}
四、ICMP数据包函数
/*
ICMP是"Internet Control Message Protocol"的缩写,它是一种网络层协议,
主要用于发送错误消息和操作信息。
Ping命令是通过ICMP回显请求(Echo Request)和响应(Echo Reply)来测试主机连通性。
*/
/*
计算机发送ping命令给ENC28J60
如果计算机不知道我的MAC地址,则先发送ARP请求数据包,再发送ICMP请求数据包;
如果计算机知道我的MAC地址,则先发送ICMP请求数据包,再发送ARP请求数据包;
1、接收的到的ARP请求数据包:
0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x06
0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x01 0xB4 0x2E 0x99 0x59 0xEC 0x1E
0xC0 0xA8 0x01 0xBE 0x00 0x00 0x00 0x00 0x00 0x00 0xC0 0xA8 0x01 0x11
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
2、发送ARP应答数据包:
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x06
0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x02 0x00 0x08 0xDC 0x11 0x11 0x02
0xC0 0xA8 0x01 0x11 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0xC0 0xA8 0x01 0xBE
3、接收到的ICMP请求数据包:
0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00
0x45 0x00 0x00 0x3C 0x43 0xE3 0x00 0x00 0x40 0x01 0xB2 0xBE
0xC0 0xA8 0x01 0xBE
0xC0 0xA8 0x01 0x11
0x08 0x00 0x4D 0x56 0x00 0x01 0x00 0x05
abcdefghijklmnopqrstuvwabcdefghi
4、发送ICMP应答数据包:
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x3C 0x00 0x01 0x00 0x00 0x40 0x01 0xF6 0xA0
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x00 0x00 0x55 0x56 0x00 0x01 0x00 0x05
abcdefghijklmnopqrstuvwabcdefghi
5、接收到的ICMP请求数据包:
0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00
0x45 0x00 0x00 0x3C 0x43 0xE4 0x00 0x00 0x40 0x01 0xB2 0xBD
0xC0 0xA8 0x01 0xBE
0xC0 0xA8 0x01 0x11
0x08 0x00 0x4D 0x55 0x00 0x01 0x00 0x06
abcdefghijklmnopqrstuvwabcdefghi
6、发送ICMP应答数据包:
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x3C 0x00 0x02 0x00 0x00 0x40 0x01 0xF6 0x9F
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x00 0x00 0x55 0x55 0x00 0x01 0x00 0x06
abcdefghijklmnopqrstuvwabcdefghi
7、接收到的ICMP请求数据包:
0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00
0x45 0x00 0x00 0x3C 0x43 0xE5 0x00 0x00 0x40 0x01 0xB2 0xBC
0xC0 0xA8 0x01 0xBE
0xC0 0xA8 0x01 0x11
0x08 0x00 0x4D 0x54 0x00 0x01 0x00 0x07
abcdefghijklmnopqrstuvwabcdefghi
8、发送ICMP应答数据包:
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x3C 0x00 0x03 0x00 0x00 0x40 0x01 0xF6 0x9E
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x00 0x00 0x55 0x54 0x00 0x01 0x00 0x07
abcdefghijklmnopqrstuvwabcdefghi
9、接收到的ICMP请求数据包:
0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00
0x45 0x00 0x00 0x3C 0x43 0xE6 0x00 0x00 0x40 0x01 0xB2 0xBB
0xC0 0xA8 0x01 0xBE
0xC0 0xA8 0x01 0x11
0x08 0x00 0x4D 0x53 0x00 0x01 0x00 0x08
abcdefghijklmnopqrstuvwabcdefghi
10、发送ICMP应答数据包:
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x3C 0x00 0x04 0x00 0x00 0x40 0x01 0xF6 0x9D
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x00 0x00 0x55 0x53 0x00 0x01 0x00 0x08
abcdefghijklmnopqrstuvwabcdefghi
11、接收"来自计算机的ARP请求"
0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x06
0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x01 0xB4 0x2E 0x99 0x59 0xEC 0x1E
0xC0 0xA8 0x01 0xBE 0x00 0x08 0xDC 0x11 0x11 0x02 0xC0 0xA8 0x01 0x11
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
12、发送ARP应答
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x06
0x00 0x01 0x08 0x00 0x06 0x04 0x00 0x02 0x00 0x08 0xDC 0x11 0x11 0x02
0xC0 0xA8 0x01 0x11 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0xC0 0xA8 0x01 0xBE
*/
#define ICMP_Debug 1
#define ICMP_ECHO_REPLY 0 //ICMP应答
#define ICMP_ECHO 8 //ICMP请求
u16 ICMP_SequenceNumber;
void ENC28J60_Send_ICMP_Reply(void);
void ENC28J60_Send_ICMP_REQUEST(void);
u8 ICMP_Work(void);
void ENC28J60_Send_PING_Command(u8 *pRemote_IP);
//函数功能:发送"ICMP应答"数据包
//当接收到来自远程设备的ICMP请求,则调用该函数
/*
发送ICMP应答数据包:
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x3C 0x00 0x01 0x00 0x00 0x40 0x01 0xF6 0xA0
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x00 0x00 0x55 0x56 0x00 0x01 0x00 0x05
abcdefghijklmnopqrstuvwabcdefghi
*/
void ENC28J60_Send_ICMP_Reply(void)
{
struct ICMP_Packet_Type *pRX;
struct ICMP_Packet_Type *pTX;
u16 chksum;
u8 i;
pTX=(struct ICMP_Packet_Type *)&TCP_TX_buf[0];
pRX=(struct ICMP_Packet_Type *)&TCP_RX_buf[0];
/////////以太网头部//////////////
memcpy(pTX->dest_MAC,pRX->src_MAC, 6);
//设置"目的MAC地址"
memcpy(pTX->src_MAC, pRX->dest_MAC, 6);
//设置"源MAC地址"为ENC28J60的MAC地址
pTX->type[0] = (u8)(UIP_ETHTYPE_IP4>>8);
pTX->type[1] = (u8)(UIP_ETHTYPE_IP4);
//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;
/////////IPV4头部//////////////
pTX->vhl=0x45;//设置"IP版本和头部长度"
pTX->tos=0x00;//设置"服务类型"
pTX->len[0]=pRX->len[0]; pTX->len[1]=pRX->len[1];
//设置ICMP数据包的"IP头和ICMP头的长度"
++My_IPID;//IP数据包唯一ID
pTX->ipid[0] = My_IPID >> 8;
pTX->ipid[1] = My_IPID & 0xff;//设置"IP数据包唯一ID"
pTX->ipoffset[0] =0;
pTX->ipoffset[1] = 0;//设置"标志字段和片偏移字段"
pTX->ttl=UIP_TTL;//设置"IP数据包生存时间值"
pTX->protocol=UIP_PROTO_ICMP;
memcpy(pTX->Send_IP, pRX->Receive_IP, 4);
//设置"发送方ip地址"
memcpy(pTX->Receive_IP, pRX->Send_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检验和"
/////////ICMP头部//////////////
pTX->ICMPtype=ICMP_ECHO_REPLY;//ICMP报文类型:应答类型
pTX->icode=0;
memcpy(pTX->id, pRX->id, 2);
memcpy(pTX->seqno, pRX->seqno, 2);
//添加用户数据"abcdefghijklmnopqrstuvwabcdefghi"
for(i=sizeof(struct ICMP_Packet_Type);i<TCP_RX_Length;i++)
{
TCP_TX_buf[i]=TCP_RX_buf[i];
}
memcpy(pTX->len, pRX->len, 2);
//设置ICMP数据包的"IP头部,ICMP头部和用户数据的长度"
pTX->icmpchksum[0]=0;pTX->icmpchksum[1]=0;//准备ICMP4校验和
chksum=~ICMP4_TX_chksum();
pTX->icmpchksum[0] = (u8)(chksum>>8);
pTX->icmpchksum[1] = (u8)(chksum);
//设置ICMP4校验和
TCP_TX_Length = TCP_RX_Length;
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
#if ICMP_Debug == 1
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
#endif
TCP_TX_Length=0;
}
//函数功能:发送ICMP请求数据包
/*
向计算机发送ICMP请求:
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x3C 0x00 0x02 0x00 0x00 0x40 0x01 0xF6 0x9F
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x08 0x00 0x4B 0x5B 0x00 0x01 0x02 0x00
abcdefghijklmnopqrstuvwabcdefghi
接收ICMP应答:
0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00
0x45 0x00 0x00 0x3C 0x1E 0xB8 0x00 0x00 0x80 0x01 0x97 0xE9
0xC0 0xA8 0x01 0xBE
0xC0 0xA8 0x01 0x11
0x00 0x00 0x54 0x5B 0x00 0x01 0x01 0x00
abcdefghijklmnopqrstuvwabcdefghi
*/
const char ICMP_REQUEST_Data_REG[]="abcdefghijklmnopqrstuvwabcdefghi";
//注意:要关闭计算机的防火墙,否则通讯会失败
void ENC28J60_Send_ICMP_REQUEST(void)
{
struct ICMP_Packet_Type *pTX;
u16 i;
u16 chksum;
pTX=(struct ICMP_Packet_Type *)&TCP_TX_buf[0];
/////////以太网头部//////////////
memcpy(pTX->dest_MAC, MyNetworkInformation.Remote_MAC, 6);
//设置"目的MAC地址"
memcpy(pTX->src_MAC, MyNetworkInformation.Local_MAC, 6);
//设置"源MAC地址"为ENC28J60的MAC地址
pTX->type[0] = (u8)(UIP_ETHTYPE_IP4>>8);
pTX->type[1] = (u8)(UIP_ETHTYPE_IP4);
//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;
/////////ICMP头部//////////////
pTX->ICMPtype=ICMP_ECHO;//ICMP请求
pTX->icode=0;
pTX->id[0]=(u8)(1>>8);
pTX->id[1]=(u8)(1);
ICMP_SequenceNumber++;
pTX->seqno[0]=(u8)(ICMP_SequenceNumber>>8);
pTX->seqno[1]=(u8)(ICMP_SequenceNumber);
//设置发送序列号
//添加用户数据"abcdefghijklmnopqrstuvwabcdefghi"
i=sizeof(struct ICMP_Packet_Type );//计算"以太网头部,IP头部和ICMP头部"的长度
strcpy( (char*)(&TCP_TX_buf[i]),ICMP_REQUEST_Data_REG );
////////计算"ICMP数据包"字节总数////////
TCP_TX_Length=strlen(ICMP_REQUEST_Data_REG);//计算用户数据的长度
TCP_TX_Length=TCP_TX_Length+i;//计算"ICMP数据包"字节总数
i=TCP_TX_Length-UIP_LLH_LEN;
//计算ICMP数据包的"IP头部,ICMP头部和用户数据"的长度
pTX->len[0]=(u8)(i>>8);
pTX->len[1]=(u8)(i);
//设置ICMP数据包的"IP头部,ICMP头部和用户数据" 的长度
pTX->icmpchksum[0]=0;pTX->icmpchksum[1]=0;//准备计算ICMP4校验和
chksum=~ICMP4_TX_chksum();
pTX->icmpchksum[0] = (u8)(chksum>>8);
pTX->icmpchksum[1] = (u8)(chksum);
//设置ICMP4校验和
/////////IPV4头部//////////////
pTX->vhl=0x45;//设置"IP版本和头部长度"
pTX->tos=0x00;//设置"服务类型"
My_IPID++;//IP数据包唯一ID
pTX->ipid[0] = My_IPID >> 8;
pTX->ipid[1] = My_IPID & 0xff;//设置"IP数据包唯一ID"
pTX->ipoffset[0] =0;
pTX->ipoffset[1] = 0;//设置"标志字段和片偏移字段"
pTX->ttl=UIP_TTL;//设置"生存时间值"
pTX->protocol=UIP_PROTO_ICMP;
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检验和"
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
}
//函数功能:接收ICMP请求或ICMP应答
u8 ICMP_Work(void)
{
struct ICMP_Packet_Type *pRX;
u16 opcode;
u8 ch1,ret;
pRX=(struct ICMP_Packet_Type *)&TCP_RX_buf[0];
ch1=0;ret=0;
if( Check_the_ICMP4_header_checksume() ) ch1=1;
//"接收ICMP数据包之IPV4头部校验和"正确,返回1
if(ch1==1)//接收到ICMP数据包
{
opcode = pRX->ICMPtype;
if( opcode==ICMP_ECHO )//接收到ICMP请求
{
ENC28J60_Send_ICMP_Reply();
//发送"ICMP应答"数据包
}
if( opcode==ICMP_ECHO_REPLY )//接收到ICMP应答
{
ret=1;
#if ICMP_Debug == 1
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
#endif
}
}
return(ret);
}
//函数功能:ENC28J60发送PING命令
//pRemote_IP[]为远程IP地址,如:"192.168.1.190"
void ENC28J60_Send_PING_Command(u8 *pRemote_IP)
{
u8 loop,cnt,ret,t;
ret=0;
t=Check_Remote_MAC_In_ARPTable_Array(pRemote_IP);
//如果"ARP表"里没有pRemote_IP指向的IP地址,则返回0;否则返回1
if(t==0) loop=2;
else
{//ARP表里有"pRemote_IP指向的IP地址"
ret=ARP_REPLY_RX_Flag;
loop=0;
}
while(loop)
{//ARP表里没有"pRemote_IP指向的IP地址"
ENC28J60_Send_ARP_REQUEST();
loop--;
cnt=UIP_TTL;
while(cnt)
{
delay_ms(1);
cnt--;
ret=ENC28J60_Receive_Data_From_Ethernet_Network();
if(ret==ARP_REPLY_RX_Flag)
{
cnt=0;
loop=0;
break;
}
}
}
if(ret==ARP_REPLY_RX_Flag) loop=4;
while(loop)
{
ENC28J60_Send_ICMP_REQUEST();
//发送ICMP请求数据包
cnt=UIP_TTL;
while(cnt)
{
delay_ms(1);
cnt--;
ret=ENC28J60_Receive_Data_From_Ethernet_Network();
//接收ICMP应答
if(ret==ICMP_REPLY_RX_Flag)
{
cnt=0;
loop--;
}
}
}
if(t==1)
{//ARP表里有"pRemote_IP指向的IP地址"
loop=2;
while(loop)
{
ENC28J60_Send_ARP_REQUEST();
//发送ARP请求
loop--;
cnt=UIP_TTL;
while(cnt)
{
delay_ms(1);
cnt--;
ret=ENC28J60_Receive_Data_From_Ethernet_Network();
//接收ARP应答
if(ret==ARP_REPLY_RX_Flag)
{
cnt=0;
loop=0;
break;
}
}
}
}
}
五、TCP之SYN数据包,SYN+ACK数据包,ACK数据包和RST数据包
#define TCP_Debug 1
#define TCP_OPT_END 0 //TCP 选项列表的结尾,End of TCP options list
#define TCP_OPT_NOOP 1 //不执行操作",TCP选项,"No-operation" TCP option
#define TCP_OPT_MSS 2 //最大段大小的字节总数为2,Maximum segment size TCP option
#define TCP_OPT_MSS_LEN 4 //TCP选项中MSS选项的长度。 Length of TCP MSS option.
u8 iss[4];//发送的序列号,The iss variable is used for the TCP initial sequence number.
u8 uip_acc32[4];//uip_acc32[]是以"大端存储方式"工作的
void uip_add_Receive_next(struct ConnectTableType *p,u16 n);
void Create_New_Connect_Parameter(struct ConnectTableType *pConnect,u8 *pRemote_IP,u16 tmpRemote_Port);
void Parse_the_TCP_MSS_option(struct ConnectTableType *p);
void ENC28J60_Send_TCP_SYN(void);
void TCP_Client_Connect_To_Server(struct ConnectTableType *pConnect);
u8 Load_ConnectParameter_To_MyNetworkInformation_After_TCP_Established(struct ConnectTableType *pConnect);
void ENC28J60_Send_ACK_data_packet(struct ConnectTableType *pConnect);
void ENC28J60_Send_RST_data_packet(struct ConnectTableType *pConnect);
u8 TCPIP4_Work(void);
//函数功能:
//将op32[]和op16相加,结果保存在uip_acc32[]
//uip_acc32[]是"大端存储方式"的数据
//op32[]是"大端存储方式"的数据
//op16是"小端存储方式"的数据
void uip_add32(u8 *op32, u16 op16)
{
uip_acc32[3] = op32[3] + (op16 & 0xff);//先计算最低8位
uip_acc32[2] = op32[2] + (op16 >> 8);//接着计算次低8位
uip_acc32[1] = op32[1];
uip_acc32[0] = op32[0];
if( uip_acc32[2] < (op16 >> 8) )//"次低8位相加"有进位
{
++uip_acc32[1];
if(uip_acc32[1] == 0)
{//"次高8位和次低8位的进位相加"有进位
++uip_acc32[0];
}
}
if( uip_acc32[3] < (op16 & 0xff) )//"最低8位相加"有进位
{
++uip_acc32[2];
if(uip_acc32[2] == 0)//"次低8位相加"有进位
{
++uip_acc32[1];
if(uip_acc32[1] == 0)
{//"次高8位和次低8位的进位相加"有进位
++uip_acc32[0];
}
}
}
}
//函数功能:接收端下一个期望接收的序列号
//p->Receive_next[]"大端存储方式"的数据和n"小端存储方式"的数据,相加,结果保存在p->Receive_next[]
void uip_add_Receive_next(struct ConnectTableType *p,u16 n)
{
/////将p->Receive_next[]和n相加,结果保存在uip_acc32[]////
uip_acc32[3] = p->Receive_next[3] + (n & 0xff);//先计算最低8位
uip_acc32[2] = p->Receive_next[2] + (n >> 8);//接着计算次低8位
uip_acc32[1] = p->Receive_next[1];
uip_acc32[0] = p->Receive_next[0];
if( uip_acc32[2] < (n >> 8) )//"次低8位相加"有进位
{
++uip_acc32[1];
if(uip_acc32[1] == 0)
{//"次高8位和次低8位的进位相加"有进位
++uip_acc32[0];
}
}
if( uip_acc32[3] < (n & 0xff) )//"最低8位相加"有进位
{
++uip_acc32[2];
if(uip_acc32[2] == 0)//"次低8位相加"有进位
{
++uip_acc32[1];
if(uip_acc32[1] == 0)
{//"次高8位和次低8位的进位相加"有进位
++uip_acc32[0];
}
}
}
// uip_add32(p->Receive_next, n);
//将p->Receive_next[]和n相加,结果保存在uip_acc32[]
p->Receive_next[0] = uip_acc32[0];//更新p->Receive_next[0]
p->Receive_next[1] = uip_acc32[1];//更新p->Receive_next[1]
p->Receive_next[2] = uip_acc32[2];//更新p->Receive_next[2]
p->Receive_next[3] = uip_acc32[3];//更新p->Receive_next[3]
}
//函数功能:创建"新连接参数",为"发送SYN数据包,连接到TCP服务器",作准备。
void Create_New_Connect_Parameter(struct ConnectTableType *pConnect,u8 *pRemote_IP,u16 tmpRemote_Port)
{
u8 i;
struct ARPTableType *p1;
u8 j,ch;
struct ConnectTableType *p2;
u16 tmpLocal_port;
u8 loop,cnt,ret;
ret=Check_Remote_MAC_In_ARPTable_Array(pRemote_IP);
//如果"ARP表"里没有pRemote_IP指向的IP地址,则返回0;否则返回1
if(ret==0) loop=2;//若没有远程MAC地址,则发送ARP请求
else//ARP表里已有这个IP地址对应的MAC地址
{
ret=ARP_REPLY_RX_Flag;
loop=0;
}
while(loop)
{
ENC28J60_Send_ARP_REQUEST();
loop--;
cnt=UIP_TTL;
while(cnt)
{
delay_ms(1);
cnt--;
ret=ENC28J60_Receive_Data_From_Ethernet_Network();
if(ret==ARP_REPLY_RX_Flag)
{
cnt=0;
loop=0;
break;
}
}
}
MyNetworkInformation.pConnect=NULL;
if(ret==ARP_REPLY_RX_Flag)
{
///////设置MyNetworkInformation.Remote_IP[]/////////
memcpy(MyNetworkInformation.Remote_IP, pRemote_IP, 4);
//保存"新连接"的远程IP地址
///////设置MyNetworkInformation.Remote_MAC[]/////////
for(i=0; i<ARPTable_SIZE;i++)
{//设置合适的本地端口
p1 = &ARPTable[i];
if( memcmp(p1->Remote_IP,MyNetworkInformation.Remote_IP,4)==0 )
{//发现远程IP地址
memcpy(MyNetworkInformation.Remote_MAC, p1->Remote_MAC, 6);
//保存"新连接"的远程MAC地址
p1->Time=0;//因为准备使用这个远程MAC地址,则将ARP表更新计数器清零。
break;
}
}
///////设置MyNetworkInformation.Remote_Port[]/////////
MyNetworkInformation.Remote_Port[0]=(u8)(tmpRemote_Port>>8);
MyNetworkInformation.Remote_Port[1]=(u8)(tmpRemote_Port);
///////设置MyNetworkInformation.Local_Port[]/////////
tmpLocal_port=Min_Local_Port;
for(i=0; i<My_ConnectTable_SIZE;i++)
{//查找未用的本地端口
for(j=0; j<My_ConnectTable_SIZE;j++)
{ //设置合适的本地端口
p2 = &MyConnectTable[j];
ch=0;
if( p2->Local_Port[0] == (u8)(tmpLocal_port>>8) ) ch++;
if( p2->Local_Port[1] == (u8)(tmpLocal_port) ) ch++;
if(ch==2)
{//发现端口被占用了
tmpLocal_port++;
//保存"新连接"的本地端口
if(tmpLocal_port<Min_Local_Port)
{
tmpLocal_port=Min_Local_Port;
}
break;
}
}
if(j==My_ConnectTable_SIZE) break;
}
MyNetworkInformation.Local_Port[0]=(u8)(tmpLocal_port>>8);
MyNetworkInformation.Local_Port[1]=(u8)(tmpLocal_port);
//保存本地端口
///////设置MyNetworkInformation.pConnect/////////
MyNetworkInformation.pConnect=pConnect;
}
}
//函数功能:解析MSS选项,获取发送方接收缓存有大;
//Parse the TCP MSS option, if present.
void Parse_the_TCP_MSS_option(struct ConnectTableType *p)
{
u16 loop;
u16 tmp16,t;
u8 c,opt;
struct TCP_IPv4_Packet_Type *pRX;
pRX=(struct TCP_IPv4_Packet_Type *)&TCP_RX_buf[0];
if((pRX->tcpoffset & 0xf0) > 0x50)//"TCP头部的长度"大于20,表示有"可选项optdata[]"
{//(0x50>>4)*4=20,即TCP头部的长度为20个字节;
loop=( (pRX->tcpoffset >> 4) - 5 ) << 2;
//计算"可选项optdata[]"有多少个字节
//比如:TCP_RX_buf[55]=0x02,TCP_RX_buf[56]=0x04,TCP_RX_buf[57]=0x05,TCP_RX_buf[58]=0xB4,
//TCP_RX_buf[59]=0x01,TCP_RX_buf[60]=0x01,TCP_RX_buf[61]=0x04,TCP_RX_buf[62]=0x02
for(c = 0; c < loop;)
{
opt = TCP_RX_buf[UIP_OptdataIndex + c];
//UIP_OptdataIndex=54
if(opt == TCP_OPT_END)//读到"TCP选项列表的结尾
{
/* End of options. */
break;
}
else if(opt == TCP_OPT_NOOP)//TCP_OPT_NOOP=1不执行操作
{
++c; /* NOP option. */
}
else if(opt == TCP_OPT_MSS && TCP_RX_buf[UIP_OptdataIndex + 1 + c] == TCP_OPT_MSS_LEN)
{//比如:TCP_RX_buf[55]=0x02,TCP_RX_buf[56]=0x04,TCP_RX_buf[57]=0x05,TCP_RX_buf[58]=0xB4
//"接收到的最大段大小"的字节总数为TCP_OPT_MSS=2:TCP选项中MSS选项的长度为TCP_OPT_MSS_LEN=4
tmp16 = (TCP_RX_buf[UIP_OptdataIndex + 2 + c] << 8) | TCP_RX_buf[UIP_OptdataIndex + 3 + c];
//计算"接收到的最大段大小"
t=tmp16 > UIP_TCP_MSS? UIP_TCP_MSS: tmp16;
p->initialMSS[0] = (u8)(t>>8);
p->initialMSS[1] = (u8)(t);
//如果"接收到的最大段大小"大于"本地的TCP最大段大小",则使用"本地的TCP最大段大小"
//如果"接收到的最大段大小"小于或等于"本地的TCP最大段大小",则使用"接收到的最大段大小"
//"本地的TCP最大段大小"为1514-14-40-40,计算机是1460
p->MSS[0]=p->initialMSS[0];p->MSS[1]=p->initialMSS[1];
//记录"远程接收窗口"大小
break;
}
else
{
/* 其他所有选项均设有长度字段,便于我们直接跳过。
All other options have a length field, so that we easily can skip past them. */
if(TCP_RX_buf[UIP_OptdataIndex + 1 + c] == 0)
{
/*若长度字段为零,则表示选项格式异常,系统将不再处理。
If the length field is zero, the options are malformed and we don't process them further. */
break;
}
c += TCP_RX_buf[UIP_OptdataIndex + 1 + c];
}
}
}
}
//函数功能:发送"SYN数据包",为的是连接到TCP服务器
/*
1、发送"SYN数据包":
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x2C 0x00 0x01 0x00 0x00 0x40 0x06 0xF6 0xAB
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x06 0x10 0x13 0x88
序列号和确认号0x00 0x00 0xA0 0xB1 0x00 0x00 0x00 0x00
0x60 0x02 0x05 0xB4 0x54 0x09 0x00 0x00
0x02 0x04 0x05 0xB4
2、接收"SYN+ACK数据包"
0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00
0x45 0x00 0x00 0x2C 0xEE 0x70 0x40 0x00 0x80 0x06 0x88 0x3B 0xC0 0xA8 0x01 0xBE 0xC0 0xA8 0x01 0x11
0x13 0x88 0x06 0x10
序列号和确认号0x66 0x9A 0x9B 0x49 0x00 0x00 0xA0 0xB2
0x60 0x12 0xFA 0xF0 0x5C 0xD7 0x00 0x00
0x02 0x04 0x05 0xB4 0x00 0x00
3、发送"ACK数据包"
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x28 0x00 0x02 0x00 0x00 0x40 0x06 0xF6 0xAE
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x06 0x10 0x13 0x88
序列号和确认号0x00 0x00 0xA0 0xB2 0x66 0x9A 0x9B 0x4A
0x50 0x10 0x05 0xB4 0x69 0xD1 0x00 0x00
0x02 0x04 0x05 0xB4
*/
void ENC28J60_Send_TCP_SYN(void)
{
struct TCP_IPv4_Packet_Type *pTX;
u16 chksum;
u16 tmp;
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地址"为ENC28J60的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 + TCP_OPT_MSS_LEN;
//"IP头部的大小20"+"TCP头部的大小20"+"可选项大小4",得到len=44
#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,即TCP
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头部中的序列号"
tmp=rand();
tmp++;
pTX->seqno[0] = (u8)(tmp>>24);//记忆ENC28J60发送的序列号
pTX->seqno[1] = (u8)(tmp>>16);
pTX->seqno[2] = (u8)(tmp>>8);
pTX->seqno[3] = (u8)(tmp&0xFF);
//修改"TCP头部中的确认号"
pTX->ackno[0] = 0;
pTX->ackno[1] = 0;
pTX->ackno[2] = 0;
pTX->ackno[3] = 0;
pTX->tcpoffset = ( (UIP_TCPH_LEN + TCP_OPT_MSS_LEN) / 4 ) << 4;
//计算"TCP头部的长度",TCP数据包有optdata[],pTX->tcpoffset=0x60
pTX->flags = TCP_SYN;//TCP客户端主动发送TCP_SYN请求时,TCP头部的标志位为"TCP_SYN"
//设置我的接收窗口大小
pTX->wnd[0] = ((UIP_TCP_MSS) >> 8);//修改"TCP头部中的窗口大小"
pTX->wnd[1] = ((UIP_TCP_MSS) & 0xff);//修改"TCP头部中的窗口大小"
pTX->urgp[0] = 0;
pTX->urgp[1] = 0;//修改TCP头部中的"紧急指针"
pTX->optdata[0] = TCP_OPT_MSS;//最大段字节总数为2
pTX->optdata[1] = TCP_OPT_MSS_LEN;//TCP选项中MSS选项的长度
pTX->optdata[2] = (UIP_TCP_MSS) / 256;//最大段高8数值,TCP最大段大小为(1500-14-40)
pTX->optdata[3] = (UIP_TCP_MSS) & 255;//最大段低8数值,TCP最大段大小为(1500-14-40)
pTX->tcpchksum[0] = 0;pTX->tcpchksum[1] = 0;
chksum=~(TCPv4_header_checksume());
//计算TCP头部的校验和
pTX->tcpchksum[0] = (u8)(chksum>>8);
pTX->tcpchksum[1] = (u8)(chksum);
TCP_TX_Length=UIP_LLH_LEN+UIP_IPH_LEN + UIP_TCPH_LEN + TCP_OPT_MSS_LEN;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20"+"可选项大小4",得到len=58
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
}
//函数功能:使用新连接参数,连接到远程服务器
void TCP_Client_Connect_To_Server(struct ConnectTableType *pConnect)
{
u8 loop,cnt,ret;
loop=2;
while(loop)
{
ENC28J60_Send_TCP_SYN();
pConnect->TCPStateFlag=TCP_SYN_SENT;
loop--;
cnt=UIP_TTL;
while(cnt)
{
delay_ms(1);
cnt--;
ret=ENC28J60_Receive_Data_From_Ethernet_Network();
if(ret==TCP_SYN_ACK_RX_Flag)
{
cnt=0;
loop=0;
}
}
}
if(ret==TCP_SYN_ACK_RX_Flag)
{
#if TCP_Debug == 1
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
Print_MyConnectTable();
#endif
ENC28J60_Send_ACK_data_packet(pConnect);//发送ACK应答
pConnect->TCPStateFlag=TCP_ESTABLISHED;
#if TCP_Debug == 1
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
#endif
}
}
//函数功能:将"pConnect指向的连接参数"装载到MyNetworkInformation中
//主要是用来装载"连接参数",如:远程IP,远程MAC,远程端口,本地端口和连接表指针
//注意:在TCP建立连接后,才可以调用该函数
u8 Load_ConnectParameter_To_MyNetworkInformation_After_TCP_Established(struct ConnectTableType *pConnect)
{
u8 i,ret;
struct ARPTableType *p1;
ret=0;
for(i=0; i<ARPTable_SIZE;i++)
{//设置合适的本地端口
p1 = &ARPTable[i];
if( memcmp(p1->Remote_IP,pConnect->Remote_IP,4)==0 )
{//发现远程IP地址
memcpy(MyNetworkInformation.Remote_MAC, p1->Remote_MAC, 6);
//保存"新连接"的远程MAC地址
memcpy(MyNetworkInformation.Remote_IP, pConnect->Remote_IP, 4);
memcpy(MyNetworkInformation.Remote_Port, pConnect->Remote_Port,2);
memcpy(MyNetworkInformation.Local_Port, pConnect->Local_Port,2);
MyNetworkInformation.pConnect=pConnect;
pConnect->Time=0;//因为准备使用这个连接,则将连接表更新计数器清零。
p1->Time=0;//因为准备使用这个远程MAC地址,则将ARP表更新计数器清零。
ret=1;//ARPTable[]有所给的IP地址
break;
}
}
return(ret);
}
//函数功能:发送"ACK应答数据包"
/*
发送"SYN"
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x2C 0x00 0x01 0x00 0x00 0x40 0x06 0xF6 0xAB
发送方的IP:0xC0 0xA8 0x01 0x11
接收方的IP:0xC0 0xA8 0x01 0xBE
发送方端口和接收方端口:0x03 0xE8 0x13 0x88
发送方的序列号:0x00 0x00 0xA0 0xB1
发送方期望收到的序列号:0x00 0x00 0x00 0x00
0x60 0x02 0x05 0xB4 0x56 0x31 0x00 0x00
0x02 0x04 0x05 0xB4
接收"SYN+ACK"
0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00
0x45 0x00 0x00 0x2C 0x93 0x6B 0x40 0x00 0x80 0x06 0xE3 0x40
发送方的IP:0xC0 0xA8 0x01 0xBE
接收方的IP:0xC0 0xA8 0x01 0x11
发送方端口和接收方端口:0x13 0x88 0x03 0xE8
发送方的序列号:0x83 0xAF 0x3E 0xDF
发送方期望收到的序列号:0x00 0x00 0xA0 0xB2
0x60 0x12 0xFA 0xF0 0x9E 0x54 0x00 0x00
0x02 0x04 0x05 0xB4
0x00 0x00
发送"ACK"
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x28 0x00 0x02 0x00 0x00 0x40 0x06 0xF6 0xAE
发送方的IP:0xC0 0xA8 0x01 0x11
接收方的IP:0xC0 0xA8 0x01 0xBE
发送方端口和接收方端口:0x03 0xE8 0x13 0x88
发送方的序列号:0x00 0x00 0xA0 0xB2
发送方期望收到的序列号:0x83 0xAF 0x3E 0xE0
0x50 0x10 0x05 0xB4 0xAB 0x4E 0x00 0x00
*/
void ENC28J60_Send_ACK_data_packet(struct ConnectTableType *pConnect)
{
u8 ret;
struct TCP_IPv4_Packet_Type *pTX;
u16 tmp;
u16 chksum;
ret=Load_ConnectParameter_To_MyNetworkInformation_After_TCP_Established(pConnect);
//将"pConnect指向的连接参数"装载到MyNetworkInformation中
//主要是用来装载"连接参数",如:远程IP,远程MAC,远程端口,本地端口和连接表指针
if(ret)
{
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;
//"IP头部的大小20"+"TCP头部的大小20"",得到len=40
#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];
uip_add_Receive_next(pConnect,1);
//将"收到的序列号pConnect->Receive_next[]"加"1"
//在后面发送ACK数据包时,用作确认号
//修改"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];
pTX->tcpoffset = (UIP_TCPH_LEN / 4) << 4;
//TCP数据包无optdata[],BUF->tcpoffset=0x50
//UIP_TCPH_LEN=20,设置"TCP头部的长度"为5*4=20
pTX->flags = TCP_ACK;
//TCP头部的标志位为"ACK"
//设置我的接收窗口大小
if(pConnect->TCPStateFlag & TCP_STOPPED)
{//设置零窗口机制,使远程主机停止发送数据。
//we advertise a zero window so that the remote host will stop sending data.
pTX->wnd[0] = 0;//修改"TCP头部中的窗口大小"
pTX->wnd[1] = 0;//修改"TCP头部中的窗口大小"
}
else
{
pTX->wnd[0] = ((UIP_TCP_MSS) >> 8);//修改"TCP头部中的窗口大小"
pTX->wnd[1] = ((UIP_TCP_MSS) & 0xff);//修改"TCP头部中的窗口大小"
}
pTX->urgp[0] = 0;
pTX->urgp[1] = 0;//修改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);
TCP_TX_Length=UIP_LLH_LEN+UIP_IPH_LEN + UIP_TCPH_LEN;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20",得到len=54
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
}
}
//函数功能:发送"RST"数据包,用来断开与TCP服务器的连接
/*
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x28 0x00 0x03 0x00 0x00 0x40 0x06 0xF6 0xAD
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x06 0x10 0x13 0x00 0x00 0xA0 0xB2 0x06 K8iP 0x04 0x00 0x00 0xBE 6 0x00 0x00
*/
void ENC28J60_Send_RST_data_packet(struct ConnectTableType *pConnect)
{
u8 ret;
struct TCP_IPv4_Packet_Type *pTX;
u16 tmp,chksum;
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;
//"IP头部的大小20"+"TCP头部的大小20"",得到len=40
#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];
uip_add_Receive_next(pConnect,1);
//将"收到的序列号pConnect->Receive_next[]"加"1"
//修改"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];
pTX->tcpoffset = (UIP_TCPH_LEN / 4) << 4;
//TCP数据包无optdata[],BUF->tcpoffset=0x50
//UIP_TCPH_LEN=20,设置"TCP头部的长度"为5*4=20
pTX->flags = TCP_RST;
//TCP头部的标志位为"RST"
//设置"零窗口机制",使远程主机停止发送数据。
pTX->wnd[0] = 0;//修改"TCP头部中的窗口大小"
pTX->wnd[1] = 0;//修改"TCP头部中的窗口大小"
pTX->urgp[0] = 0;
pTX->urgp[1] = 0;//修改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);
TCP_TX_Length=UIP_LLH_LEN+UIP_IPH_LEN + UIP_TCPH_LEN;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20",得到len=54
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
memset( pConnect,0,sizeof(struct ConnectTableType) );
//清除该连接信息
#if TCP_Debug == 1
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
Print_MyConnectTable();
#endif
}
}
//函数功能:接收来自计算机的TCP请求/TCP应答
u8 TCPIP4_Work(void)
{
struct TCP_IPv4_Packet_Type *pRX;
u16 opcode;
u8 ch1,ret;
struct ConnectTableType *pConnect;
pRX=(struct TCP_IPv4_Packet_Type *)&TCP_RX_buf[0];
ch1=0;ret=0;
if( Check_Receive_TCPv4_header_checksume() ) ch1=1;
//"接收TCPIP数据包之TCP头部校验和"正确,返回1
if(ch1==1)//接收到TCPIP数据包
{
opcode=(u16)(pRX->flags & TCP_CTL);
if( opcode== (TCP_SYN | TCP_ACK) )//接收到"syn+ack应答"
{
pConnect=MyNetworkInformation.pConnect;
Parse_the_TCP_MSS_option(MyNetworkInformation.pConnect);
//解析TCP MSS选项,获取发送方接收缓存有大。Parse the TCP MSS option, if present.
memcpy(MyNetworkInformation.Remote_IP,pRX->Send_IP, 4);//保存远程IP
memcpy(MyNetworkInformation.Remote_Port,pRX->Send_Port, 2);
//保存远程端口
memcpy(MyNetworkInformation.Local_Port,pRX->Receive_Port, 2);
//保存本地端口
memcpy(pConnect->Remote_IP,pRX->Send_IP, 4);//保存远程IP
memcpy(pConnect->Remote_Port,pRX->Send_Port, 2);//保存远程端口
memcpy(pConnect->Local_Port,pRX->Receive_Port, 2);//保存本地端口
pConnect->TCPStateFlag = TCP_SYN_RECEIVED;//系统进入ESTABLISHED状态
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数据包时,用作序列号
pConnect->len = 0;
pConnect->Time=0;
pConnect->ConnectFlag=1;//客户端建立连接
ret=1;
}
}
return(ret);
}
六、定时器
u32 My_uipTimer;//计时器,每10ms增加1.
struct strTimer
{
int start; //start为int型
int interval; //interval为int型
};
struct strTimer TCP_Timer;//Connect表刷新定时器
struct strTimer ARP_Timer;//ARP表刷新定时器
int clock_time(void);
void timer_set(struct strTimer *t, int interval);
void timer_reset(struct strTimer *t);
void timer_restart(struct strTimer *t);
int timer_expired(struct strTimer *t);
void TIM4_Interrupt_Initializtion(u16 arr,u16 psc);
//My_uipTimer每10ms增加1
int clock_time(void)
{
return My_uipTimer; /* 10ms 单位 */
}
//令t->interval=interval,令t->start=My_uipTimer,My_uipTimer每10ms增加1
void timer_set(struct strTimer *t, int interval)
{
t->interval = interval;
t->start = clock_time();
//令t->start=My_uipTimer
//My_uipTimer每10ms增加1
}
void timer_reset(struct strTimer *t)
{
t->start += t->interval;
}
void timer_restart(struct strTimer *t)
{
t->start = clock_time();
}
int timer_expired(struct strTimer *t)
{
return (int)(clock_time() - t->start) >= (int)t->interval;
}
//通用定时器2中断初始化
//APB1时钟为72MHz
//arr:自动重装值。
//psc:时钟预分频数
//TIM_CKD_DIV1:定时器时钟 = 输入频率
//TIM_CKD_DIV2:定时器时钟 = 输入频率/2
//TIM_CKD_DIV4:定时器时钟 = 输入频率/4
//TIM4_Interrupt_Initializtion(1000,72);//当arr=1000,psc=72时,则为1ms,误差为1us;
void TIM4_Interrupt_Initializtion(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
//使能定时器TIM4的APB1外设时钟
//定时器TIM4初始化
TIM_TimeBaseStructure.TIM_Period = arr-1;
//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc-1;
//设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//设置时钟分割:TDTS = Tck_tim
//计算公式:arr*psc/72000000/1,当arr=1000,psc=72时,则为1ms,误差为1us;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//TIM向上计数模式
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;//TIM4~TIM7不用设置也可以
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
//根据指定的参数初始化TIMx的时间基数单位
TIM_SetCounter(TIM4,0); //设置TIM4的计数器值为0;
TIM_ClearFlag(TIM4, TIM_FLAG_Update); //清除TIM4溢出的待处理标志位
TIM_ClearITPendingBit(TIM4, TIM_IT_Update ); //清除TIM4中断的待处理位
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); //允许TIM4溢出产生中断
//中断优先级NVIC设置
//NVIC_PriorityGroup_4设置NVIC中断分组4:表示抢占优先级为4位,取值为0~15,没有响应优先级,取值为0
//NVIC_PriorityGroup_3设置NVIC中断分组3:表示抢占优先级为3位,取值为0~7,响应优先级只有1位,取值为0~1
//NVIC_PriorityGroup_2设置NVIC中断分组3:表示抢占优先级为2位,取值为0~3,响应优先级只有2位,取值为0~3
//NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //TIM4中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 11; //设置抢占优先级为11
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //设置响应优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure);
//根据NVIC_InitStruct中指定的参数初始化NVIC嵌套向量中断控制寄存器
TIM_Cmd(TIM4, ENABLE);//使能TIM4外设
}
//函数功能:TIM4每10ms中断一次
void TIM4_IRQHandler()
{
if (TIM_GetITStatus(TIM4,TIM_IT_Update) != RESET) //TIM4计数器溢出产生中断
{
My_uipTimer++;//uip计时器增加1
TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //清除TIM4计数器的溢出中断标志;
}
}
七、应用
#include "stm32f10x.h"//使能uint8_t,uint16_t,uint32_t,uint64_t,int8_t,int16_t,int32_t,int64_t
#include "stdio.h" //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()
#include "delay.h"
#include "USART1.h"
#include "IWDG.h"
#include "ENC28J60_Variable.h"
#include "Timer4.h"
#include "enc28j60.h"
#include "ENC28J60_Interface.h"
#include "ARP.h"
#include "ICMP.h"
#include "TCP.h"
//PLL配置: PLLCLK = HSE * 6 = 72 MHz,2025年11月8日修改
u8 Main_cnt;
const char CPU_Reset_REG[]="\r\nCPU reset!\r\n";
int main(void)
{
u8 Remote_ip[4];
HSE_SetSysClock(RCC_PLLMul_6); // 设置系统时钟为12MHz * 6 = 72MHz
// SystemInit();//在使用IAP时,该语句需要屏蔽
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
USART1_Serial_Interface_Enable(115200);
SysRstSrcRecord();//系统复位记录
delay_ms(1000);
Network_Init();
// Test_ARP();
Remote_ip[0]=192;Remote_ip[1]=168;Remote_ip[2]=1;Remote_ip[3]=190;
//远程IP地址,如:"192.168.1.190"
// ENC28J60_Send_PING_Command(Remote_ip);
// //ENC28J60发送PING命令
Create_New_Connect_Parameter(&MyConnectTable[0],Remote_ip,5000);
//创建连接参数,为建立TCP连接做准备
TCP_Client_Connect_To_Server(&MyConnectTable[0]);
////使用指定的连接表,连接到远程服务器
Create_New_Connect_Parameter(&MyConnectTable[1],Remote_ip,5000);
//创建连接参数,为建立TCP连接做准备
TCP_Client_Connect_To_Server(&MyConnectTable[1]);
////使用指定的连接表,连接到远程服务器
delay_ms(5000);
ENC28J60_Send_RST_data_packet(&MyConnectTable[0]);
ENC28J60_Send_RST_data_packet(&MyConnectTable[1]);
while(1)
{
ENC28J60_Receive_Data_From_Ethernet_Network();
if(timer_expired(&ARP_Timer))
{
timer_reset(&ARP_Timer);
ARP_Table_Update_Timer();
//10秒执行一次,如果"远程设备IP地址不为0,且建立时间超过20分钟",则将其IP地址设置为0
}
if(timer_expired(&TCP_Timer))
{
timer_reset(&TCP_Timer);//50*10=500毫秒
MyConnectTable_Update_Timer();
//0.5秒执行一次,如果"远程设备IP地址不为0,且建立时间超过2分钟",则将其IP地址设置为0
}
}
}
八、测试结果

