学习TCP/IP,选择没有硬件TCP/IP栈的芯片,有利于我们深入学习各种数据包的结构,比如ENC28J60这样的芯片。W5500因为有硬件TCP/IP栈,所以放着硬件栈不搞,去搞软件TCP/IP栈,心里肯定迈不出这个槛。网上能把TCP/IP驱动讲得清楚明白的文章并不多。uip的代码虽说不怎么好,但能从中抠出很多有用的东西。uip堪称屎山代码,用了很多goto语句。而且还用上了pt协程,就更让人难懂了。但是,还是感谢这个代码,否则就很难理解软件TCP/IP栈了。
可以去下载阅读:
https://download.csdn.net/download/weixin_42550185/92625275
本文程序分为几个部分完成,如下:
1、以太网数据结构及相关宏定义;
2、我的数据结构;
3、刷新"连接表和ARP表"的定时器;
4、TCPIP的相关宏定义和一些公用的函数;
5、ARP数据包;
ARP数据包主要是指"发送ARP请求,接收ARP请求,发送ARP应答和接收ARP应答"。
6、ICMP数据包;
ICMP数据包主要是发送ICMP请求,接收ICMP请求,发送ICMP应答和接收ICMP应答。比如计算机发送的ping命令就是"发送ICMP请求"。
7、TCP数据包;
TCP数据包主要有:SYN同步数据包,SYN+ACK数据包,PSH+ACK数据包,FIN+ACK终止数据包,RST数据包和ACK数据包。
一、以太网数据结构及相关宏定义
先讲解数据结构,不要象我以前只知道人云亦云。因为这样,更容易理解软件TCP/IP栈的数据包。
1.1、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地址
};
1.2、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];
//标志字段和片偏移字段
//标志字段第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;//ICMP包生存时间
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]; //包括数据在内的整个ETHERNETBUFFERSIZE的校验和
u8 id[2]; //id可以固定为1
u8 seqno[2];//序列号
};
1.3、TCP数据包结构
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头部数据的长度"
//如果有用户数据和紧急数据,则它是"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; //TCP数据包生存时间
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紧急标志,表示紧急指针。
//ECN:扩展的拥塞通知,用于指示网络拥塞。
//CWR:拥塞窗口警告,用于指示接收端拥塞。
#define TCP_CTL 0x3f
u8 wnd[2];
//窗口大小, 对于发送方来说告诉接收方,当前"发送方接收的缓冲区"的大小
u8 tcpchksum[2]; //校验和
u8 urgp[2];
//紧急指针,必须配合URG标志位一起使用的,只有在URG被置位时才有意义。
#define TCP_OPT_MSS_LEN 4
//TCP选项中MSS选项的长度。 Length of TCP MSS option.
u8 optdata[TCP_OPT_MSS_LEN];
//optdata[0]为"最大段大小"占用的字节数
//optdata[1]为TCP选项中MSS选项的长度
//MSS选项的长度占1个字节,最大段高8数值,最大段低8数值
};
1.4、IP数据帧结构
通过比较ICMP和TCP数据包,我们发现,它们都有以太网头部和IP头部,为了方便分析数据,我们定义一个"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; //服务类型,一般为0
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字节
};
1.5、以太网帧的类型
#define EthernetDataPacketType_ARP 0x0806 //ARP请求包类型
#define EthernetDataPacketType_IP4 0x0800 //IPV4数据包类型
#define EthernetDataPacketType_IP6 0x86dd //IPv6包类型,本程序不支持IPv6
1.6、在IP头部中定义的协议
#define ProtocolType_ICMP 1 //ICMP协议编号定义为1
#define ProtocolType_TCP 6 //TCP协议编号定义为6
#define ProtocolType_UDP 17 //TCP协议编号定义为17
#define ProtocolType_ICMP6 58 //ICMP6协议编号定义为58
1.7、以太网数据包的生存时间
#define EthernetDataPacket_TTL 64 //以太网数据包的生存时间(TTL)
1.8、和计算有关的宏定义
通过前面的数据结构,我们知道,以太网头部的大小固定为14个字节;IPv4头部的大小是20个字节,而IPv6头部的大小是40个字节;不含"可选项optdata[]"的IPv4和IPv6的TCP头部的大小都是20字节。含"可选项optdata[]"的IPv4和IPv6的TCP头部的大小都是24字节。"可选项optdata[]"的长度,计算机中是8个字节,在ENC28J60中为4个字节。
查阅相关资料,我们知道:"以太网帧校验序列FCS"位于以太网帧的末尾,它是是基于循环冗余校验CRC算法,占4个字节。TCP有一个最大分段大小MSS,其目的是告诉对方其接收缓存的大小。以太网帧的最大长度为1518字节,因此,超过这个长度,就要分包发送。对IPv4来说,MSS值为1518-14-20-20-4=1460;对IPv6来说,MSS值为1518-14-40-20-4=1440。
#define EthernetHeaderLength 14 //以太网头部的大小
#if UIP_CONF_IPV6
#define IPHeaderLength 40 //IPV6的IP头的大小
#else
#define IPHeaderLength 20 //IPV4的IP头的大小
#endif
#define TCPHeaderLength 20
//TCP头部的大小
//不含"可选项optdata[]"的IPv4和IPv6的TCP头部的大小都是20字节。
//含"可选项optdata[]"的IPv4和IPv6的TCP头部的大小都是24字节。
#define TCP_OptdataIndex ( EthernetHeaderLength + IPHeaderLength + TCPHeaderLength )
//在缓冲去中,"可选项optdata[]"的下标值是54
#define FrameCheckSequence 4 //以太网帧校验序列的大小为4个字节
#define EthernetBufferSize 1518
//以太网帧的最大长度为1518字节,因此,超过这个长度,就要分包发送。
#define TCP_MSS (EthernetBufferSize - EthernetHeaderLength - IPHeaderLength - TCPHeaderLength - FrameCheckSequence)
//"本地的TCP最大段大小"为1518-14-20-20-4,计算机是1460
u8 TCP_TX_buf[EthernetBufferSize];//ENC28JI60发送数据的缓存
u16 TCP_TX_Length; //记录TCP_TX_buf[]的有效字节数
u8 TCP_RX_buf[EthernetBufferSize];//ENC28JI60接收数据的缓存
u16 TCP_RX_Length;//记录TCP_RX_buf[]的有效字节数
二、我的数据结构
2.1、连接表
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];
//记录"远程接收窗口"大小
//如果"接收到的最大段大小"大于"本地的TCP最大段大小",则使用"本地的TCP最大段大小"
//如果"接收到的最大段大小"小于或等于"本地的TCP最大段大小",则使用"接收到的最大段大小"
//"本地的TCP最大段大小"为1514-14-40-40,计算机是1460
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
};
#define My_ConnectTable_SIZE 3 //连接表最多有3个连接
struct ConnectTableType MyConnectTable[My_ConnectTable_SIZE];
2.2、网络信息
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;
const char Local_Port[]={0x03,0xE8};//本地端口为"0x03E8=1000"
const char Server_Port[]={0x03,0xE8};//用作服务器时的端口为"0x03E9=1000"
2.3、连接参数
在接收时,程序会自动更新MyNetworkInformation,因此,在发送前要做好"连接参数"备份,连接参数备份定义如下:
struct ConnectType
{
u8 Remote_IP[4]; //远程设备的IP地址
u8 Remote_MAC[6]; //远程设备的MAC地址
u8 Remote_Port[2]; //远程设备的TCP端口
u8 Local_Port[2]; //本地TCP端口
struct ConnectTableType *pConnect;//记录连接表指针
};
struct ConnectType ConnectBackup;
4)、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 //ARP表最多有6远程IP地址和MAC地址
struct ARPTableType ARPTable[ARPTable_SIZE];
u16 My_IPID; //每个带有"IP头部"的数据包均有一个唯一的id
三、刷新"连接表和ARP表"的定时器
3.1、定时器变量
u32 My_uipTimer;//计时器,每10ms增加1.
struct strTimer
{
int start; //start为int型
int interval; //interval为int型
};
struct strTimer TCP_Timer;//"连接表"刷新定时器
struct strTimer ARP_Timer;//"ARP表"刷新定时器
3.2、定时器用到的函数
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外设
My_uipTimer=0;
}
//函数功能:TIM4每10ms中断一次
void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4,TIM_IT_Update) != RESET) //TIM4计数器溢出产生中断
{
My_uipTimer++;//uip计时器增加1
TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //清除TIM4计数器的溢出中断标志;
}
}
四、TCPIP的相关宏定义和一些公用的函数
4.1、TCPIP的共用的宏定义有:
#define ENC28J60_Interface_Debug 1
#define KEIL_LITTLE_ENDIAN 3412 //小端存储方式
#define IAR_BIG_ENDIAN 1234 //大端存储方式
#define MY_BYTE_ORDER KEIL_LITTLE_ENDIAN
#if MY_BYTE_ORDER == IAR_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
#define TCP_ACK_RX_Flag 4
#define TCP_FIN_ACK_RX_Flag 5
4.2、TCPIP的共用函数
u16 chksum(u16 sum, const u8 *data, u16 len);
u16 IPv4_checksum_In_TCP_Program(u8 *tmpIPDataPointer);
u8 Check_the_ipV4_header_checksume(u8 *tmpIPDataPointer);
u8 Network_Init(void);
u8 Check_Remote_MAC_In_ARPTable_Array(u8 *pRemote_IP);
void Update_ARPTable_Array_In_ARPDataPacket(void);
struct ARPTableType * Update_ARPTable_In_Received_IPDataPacket(void);
void Update_MyNetworkInformation_In_Received_TCPDataPacket(struct ConnectTableType *pConnect);
void Print_Send_Package(unsigned char* buf,u16 len);
void Print_Receive_Package(unsigned char* buf,u16 len);
void Print_MyConnectTable(void);
void Print_ARP_Table(void);
void ARP_Table_Update_Timer(void);
void MyConnectTable_Update_Timer(void);
u8 ENC28J60_Receive_Data_From_Ethernet_Network(void);
//函数功能:将的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_In_TCP_Program(u8 *tmpIPDataPointer)
{
u16 sum;
sum = chksum(0, tmpIPDataPointer, IPHeaderLength);
//将"大端存储方式"的tmpIPDataPointer[]中的前20个字节按照"双字节进行累加和校验"
//定义EthernetHeaderLength=14,IP头的大小为IPHeaderLength=20,IP头部/IPV4头部(IPv4 header),占20个字节
return (sum == 0) ? 0xffff : sum;
//sum按照小端存储方式返回
}
//函数功能:如果接收到的"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_In_TCP_Program(tmpIPDataPointer);
//计算"IP头部/IPV4头部(IPv4 header)"20个字节的校验和
if(sum == 0xffff)//"IP头部校验和"正确
{
ret=1;//"IP头部校验和"正确,返回1
return(ret);
}
return(ret);
}
//配置网卡硬件,并设置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]=Local_Port[0];
MyNetworkInformation.Local_Port[1]=Local_Port[1];
//初始化本地端口值为1000;
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) );
//清除连接表
MyNetworkInformation.pConnect=&MyConnectTable[0];
//初始化指针MyNetworkInformation.pConnect
ConnectBackup.pConnect=NULL;//初始化指针ConnectBackup.pConnect
return res;
}
//函数功能:
//如果"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地址,则设置ret=1
break;
}
}
memcpy(MyNetworkInformation.Remote_IP, pRemote_IP, 4);
//更新"接收方的IP地址"
return(ret);
}
//函数功能:如果ARP表里没有接收到的MAC地址,则更新ARP表,返回"ARP表"的指针
void Update_ARPTable_Array_In_ARPDataPacket(void)
{
u8 i,ch,ret;
struct ARPTableType *p1,*p2;
struct ARP_Packet_Type *pRX;
pRX=(struct ARP_Packet_Type *)&TCP_RX_buf[0];
ret=0;
for(i=0; i<ARPTable_SIZE;i++)
{//查找接收到的"发送方IP地址"是否存在ARPTable[]中
p1 = &ARPTable[i];
ch=0;
if( memcmp(p1->Remote_IP,pRX->Send_IP,4)==0 )//发现远程IP地址
{
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, pRX->Send_IP, 4);//更新IP地址
memcpy(p1->Remote_MAC,pRX->src_MAC,6);//更新MAC地址
p1->Time=0;
}
//如果ARP表里没有接收到的MAC地址,则更新ARP表,返回"ARP表"的指针
//ICMP和TCP数据包均含有"以太网头部和IP头部"
struct ARPTableType * Update_ARPTable_In_Received_IPDataPacket(void)
{
struct IPv4_Packet_Type *pRX;
struct ARPTableType *p1,*p2;
u8 i,ch,ret;
pRX=(struct IPv4_Packet_Type *)&TCP_RX_buf[0];
ret=0;
for(i=0; i<ARPTable_SIZE;i++)
{//查找接收到的"发送方IP地址"是否存在ARPTable[]中
p1 = &ARPTable[i];
ch=0;
if( memcmp(p1->Remote_IP,pRX->Send_IP,4)==0 )//发现远程IP地址
{
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, pRX->Send_IP, 4);//更新IP地址
memcpy(p1->Remote_MAC,pRX->src_MAC,6);//更新MAC地址
p1->Time=0;
return(p1);
}
//如果接收到正确的TCPIP数据包,则强制更新MyNetworkInformation结构变量
void Update_MyNetworkInformation_In_Received_TCPDataPacket(struct ConnectTableType *pConnect)
{
struct TCP_IPv4_Packet_Type *pRX;
pRX=(struct TCP_IPv4_Packet_Type *)&TCP_RX_buf[0];
memcpy(MyNetworkInformation.Remote_MAC, pRX->src_MAC, 6);
//保存"新连接"的远程MAC地址
memcpy(MyNetworkInformation.Remote_IP, pRX->Send_IP, 4);
memcpy(MyNetworkInformation.Remote_Port, pRX->Send_Port,2);
memcpy(MyNetworkInformation.Local_Port, pRX->Receive_Port,2);
MyNetworkInformation.pConnect=pConnect;
}
//函数功能:打印发送数据包;
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("TCPStateFlag=%u ",p->TCPStateFlag);
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;
if( Check_Remote_MAC_In_ARPTable_Array(pMyConnectTable->Remote_IP) )
{
ENC28J60_Send_RST_data_packet( pMyConnectTable->Remote_IP,
pMyConnectTable->Remote_Port,
pMyConnectTable->Local_Port);
//超时断开连接
}
memset( pMyConnectTable,0,sizeof(struct ConnectTableType) );
}
}
}
//函数功能:
//从以太网接收应答或数据
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(EthernetBufferSize,TCP_RX_buf);
//读ENC28J60的接收缓存
if(TCP_RX_Length > EthernetHeaderLength )
{//接收到的数据长度大于"以太网头部"的长度
pRX=(struct IPv4_Packet_Type *)&TCP_RX_buf[0];
type=HEX16(pRX->type);
if(type== EthernetDataPacketType_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[EthernetHeaderLength]) ) 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;
if(t==2)ret=TCP_ACK_RX_Flag;
if(t==4)ret=TCP_FIN_ACK_RX_Flag;
}
}
else if( type==EthernetDataPacketType_ARP)
{
t=ARP_Work();
if(t==1)ret=ARP_REPLY_RX_Flag;
}
else
{
}
}
return(ret);
}
五、ARP数据包
ARP的工作原理是通过"广播请求",在局域网内解析IP地址对应的MAC地址。由于"广播域"受路由器隔离,无法跨越外网,这限制了ARP的范围仅限于同一子网或广播域内。
5.1、ARP数据包的相关宏定义
#define ARP_Debug 1
#define ARP_REQUEST 1 // ARP操作码:ARP请求
#define ARP_REPLY 2 // ARP操作码:ARP应答
5.2、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)(EthernetDataPacketType_ARP>>8);
pTX->type[1] = (u8)(EthernetDataPacketType_ARP);
//设置太网帧的类型为0x0806,表示后面跟着的是ARP数据包;
pTX->hwtype[0]=(u8)(0x0001>>8);
pTX->hwtype[1]=(u8)(0x0001);
pTX->protocol[0]=(u8)(EthernetDataPacketType_IP4>>8);
pTX->protocol[1]=(u8)(EthernetDataPacketType_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)(EthernetDataPacketType_ARP>>8);
pTX->type[1] = (u8)(EthernetDataPacketType_ARP);
//设置太网帧的类型为0x0806,表示后面跟着的是ARP数据包;
pTX->hwtype[0]=(u8)(0x0001>>8);
pTX->hwtype[1]=(u8)(0x0001);
pTX->protocol[0]=(u8)(EthernetDataPacketType_IP4>>8);
pTX->protocol[1]=(u8)(EthernetDataPacketType_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应答
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);
printf("\r\nARP opcode=0x%02X\r\n",opcode);
if( opcode==ARP_REQUEST )//接收到ARP请求
{
ENC28J60_Send_ARP_Reply();
//发送"ARP应答"数据包
Update_ARPTable_Array_In_ARPDataPacket();//更新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_In_ARPDataPacket();
//更新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=EthernetDataPacket_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;
}
}
}
}
5.3、Test_ARP()测试结果

六、ICMP数据包
ICMP是"Internet Control Message Protocol"的缩写,它是一种网络层协议,
主要用于发送错误消息和操作信息。Ping命令是通过ICMP回显请求(Echo Request)和响应(Echo Reply)来测试主机连通性。当 ENC28J60 主动 ping 计算机时,一定要关闭计算机的防火墙 ,否则,ICMP Ping会失败。因为ICMP Ping: ICMP(Internet Control Message Protocol)是一种网络层协议,主要用于发送错误消息和操作信息。Ping命令通过ICMP回显请求(Echo Request)和响应(Echo Reply)来测试主机连通性。见下图:

.1、ICMP数据包的变量和相关宏定义
#define ICMP_Debug 1
#define ICMP_ECHO_REPLY 0 //ICMP应答
#define ICMP_ECHO 8 //ICMP请求
u16 ICMP_SequenceNumber;
6.2、ICMP数据包的相关函数
u16 ICMP4_TX_chksum(void);
u16 ICMP4_RX_chksum(void);
u8 Check_the_ICMP4_header_checksume(void);
void ENC28J60_Send_ICMP_Reply(void);
void ENC28J60_Send_ICMP_REQUEST(void);
u8 ICMP_Work(void);
void ENC28J60_Send_PING_Command(u8 *pRemote_IP);
//函数功能:将首地址为&uip_buf[14]的缓存中的前20个字节按照"双字节进行累加和校验";
//IPV4头部(IPv4 header),占20个字节
u16 IPv4_checksum_In_ICMP_Program(u8 *tmpIPDataPointer)
{
u16 sum;
sum = chksum(0, tmpIPDataPointer, IPHeaderLength);
//将"大端存储方式"的tmpIPDataPointer[]中的前20个字节按照"双字节进行累加和校验"
//定义EthernetHeaderLength=14,IP头的大小为IPHeaderLength=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 - IPHeaderLength;
//上层长度upper_layer_len = ICMP报文总长度 - IP头的大小20
sum=0;
sum = chksum( sum,
&TCP_TX_buf[IPHeaderLength + EthernetHeaderLength],
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 - IPHeaderLength;
//上层长度upper_layer_len = ICMP报文总长度 - IP头的大小20
sum=0;
sum = chksum( sum,
&TCP_RX_buf[IPHeaderLength + EthernetHeaderLength],
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 - IPHeaderLength;
//上层长度upper_layer_len = ICMP报文总长度 - IP头的大小20
sum=0;
sum = chksum( sum,
&TCP_RX_buf[IPHeaderLength + EthernetHeaderLength],
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);
}
//函数功能:发送"ICMP应答"数据包
//当接收到来自远程设备的ICMP请求,则调用该函数
/*
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
*/
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)(EthernetDataPacketType_IP4>>8);
pTX->type[1] = (u8)(EthernetDataPacketType_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=EthernetDataPacket_TTL;//设置"IP数据包生存时间值"
pTX->protocol=ProtocolType_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_In_ICMP_Program(&TCP_TX_buf[EthernetHeaderLength]));
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)(EthernetDataPacketType_IP4>>8);
pTX->type[1] = (u8)(EthernetDataPacketType_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-EthernetHeaderLength;
//计算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=EthernetDataPacket_TTL;//设置"生存时间值"
pTX->protocol=ProtocolType_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_In_ICMP_Program(&TCP_TX_buf[EthernetHeaderLength]));
pTX->ipchksum[0] = (u8)(chksum>>8);
pTX->ipchksum[1] = (u8)(chksum);
//设置"IPV4检验和"
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
}
/*
向计算机发送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
*/
//函数功能:接收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;
printf("\r\nICMP opcode=0x%02X\r\n",opcode);
if( opcode==ICMP_ECHO )//接收到ICMP请求
{
Update_ARPTable_In_Received_IPDataPacket();
//如果ARP表里没有接收到的MAC地址,则更新ARP表,返回"ARP表"的指针
//ICMP和TCP数据包均含有"以太网头部和IP头部"
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=EthernetDataPacket_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=EthernetDataPacket_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=EthernetDataPacket_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;
}
}
}
}
}
6.3、ping命令测试结果

七、TCP数据包
TCP数据包主要有以下几种类型:
1)、SYN同步数据包;
通常由客户端发送"SYN同步数据包",然后接收来自服务端的"SYN+ACK数据包",和建立连接有关。
2)、SYN+ACK数据包;
3)、PSH+ACK数据包;
对于发送来说,用来指示发送用户数据;对接收方来说,用来提示接收用户数据。
4)、FIN+ACK终止数据包;
注意:SYN和FIN不能同时为1,因为它们分别代表建立连接和关闭连接,是互斥的操作。
5)、RST数据包;
6)、ACK数据包;
ACK数据包是用来应答的,也可以和RST,SYN,PSH搭配使用。比如:
ACK和SYN搭配使用:这是"TCP三次握手"的第二步。服务器收到客户端的SYN请求后,会回复一个SYN+ACK数据包,表示既确认了客户端的连接请求,也同步了自己的连接请求。
ACK和FIN搭配使用:这是"TCP四次挥手"的标准流程。当一方发送FIN表示要关闭连接时,另一方会回复一个ACK来确认收到关闭请求。
ACK和PSH搭配使用:当接收方确认收到数据的同时,希望发送方立即推送数据给应用程序时,可以使用PSH+ACK标志位。这表示"我收到了你之前的数据,并且请立即发送新的数据"。
ACK和RST搭配使用:虽然不常见,但在某些错误处理场景下,ACK与RST可以同时出现,用于在确认收到"一个非法或错误数据包"的同时,强制重置连接。
7.1、TCP数据用到的函数
TCP数据用到的函数大体如下:
u16 TCPv4_header_checksume(void);
u8 Check_Receive_TCPv4_header_checksume(void);
void Add_Receive_next(struct ConnectTableType *p,u16 n);
void Update_SequenceNumber_AcknowledgeNumber_And_Calculate_UserDataLength(
struct ConnectTableType *pConnect,
struct TCP_IPv4_Packet_Type *pRX);
struct ConnectTableType *Get_Pointer_Of_MyConnectTable( u8 *pRemoteIP,
u8 *pRemote_Port,
u8 *pLocal_Port);
void Parse_the_TCP_MSS_option(struct ConnectTableType *p);
void Protect_MyNetworkInformation_Structure_Variable(void);
void Restore_MyNetworkInformation_Structure_Variable(void);
void Create_TCP_Client_Connect_Parameter_Use_FixedPort( u8 *pRemoteIP,
u8 *pRemote_Port,
u8 *pLocal_Port);
void ENC28J60_Send_ACK_data_packet(struct ConnectTableType *pConnect);
void ENC28J60_Send_FIN_ACK_data_packet(struct ConnectTableType *pConnect);
u8 ENC28J60_Disconnect(u8 *pRemoteIP, u8 *pRemote_Port,u8 *pLocal_Port);
u8 Remote_Device_Close_Connection_Enable( struct ConnectTableType *pConnect,
struct TCP_IPv4_Packet_Type *pRX);
void ENC28J60_Send_RST_data_packet(u8 *pRemoteIP, u8 *pRemote_Port,u8 *pLocal_Port);
void ENC28J60_Send_TCP_SYN_data_packet(void);
void TCP_Client_Connect_To_Server_Use_FixedPort( u8 *pRemoteIP,
u8 *pRemote_Port,
u8 *pLocal_Port);
void ENC28J60_Send_PSH_data_packet( struct ConnectTableType *pConnect,
u8 *pData,
u16 len,u8 *urgData);
u8 TCP_Client_Send_Data_To_Server( u8 *pRemoteIP,
u8 *pRemote_Port,
u8 *pLocal_Port,
u8 *pData,
u16 len,
u8 *urgData);
//////ENC28J60作为服务器///////
void TCP_Server_Send_TCP_SYN_ACK_data_packet(struct ConnectTableType *pConnect);
void TCP_Client_Connect_To_ENC28J60_Server(struct ConnectTableType *pConnect);
u8 TCPIP4_Work(void);
7.2、TCP数据包的公共函数
//函数功能:计算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
upper_layer_len = (((u16)(pTX->len[0]) << 8) + pTX->len[1]) - IPHeaderLength;
//上层长度upper_layer_len = 总长度 - IP头的大小
#endif
sum = upper_layer_len + ProtocolType_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[IPHeaderLength + EthernetHeaderLength],
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])
- IPHeaderLength;
//上层长度upper_layer_len = IP报文总长度 - IP头的大小
#endif
sum = upper_layer_len + ProtocolType_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[IPHeaderLength + EthernetHeaderLength],
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);
}
//函数功能:修改"下一个期望接收到的序列号"
//p->Receive_next[]"大端存储方式"的数据和n"小端存储方式"的数据,相加,结果保存在p->Receive_next[]
void uip_add_Receive_next(struct ConnectTableType *p,u16 n)
{
u8 uip_acc32[4];//uip_acc32[]是以"大端存储方式"工作的
/////将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];
}
}
}
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]
}
/*
TCP紧急数据称为"带外数据"。
用户数据称为"带内数据"。
以太网上的TCP紧急数据(也称为带外数据)在协议层面被设计为仅支持传输1字节的数据。
紧急数据本质上是用户数据流中的"一个特殊字节",其位置由紧急指针定义。
如果发送"多个紧急数据",则只有最后一个字节会被保留,之前的紧急数据会被覆盖。
接收端通过读取"紧急指针",分析它指向的字节,从而得到"紧急数据",而用户数据,
则按顺序从接收缓冲区读取。
如果紧急标志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 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=EthernetHeaderLength + 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-1);
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
}
}
}
//根据所给的连接参数,获取"连接表"的指针;如果没有,则创建一个新的连接参数
struct ConnectTableType *Get_Pointer_Of_MyConnectTable( u8 *pRemoteIP,
u8 *pRemote_Port,
u8 *pLocal_Port)
{
struct ConnectTableType *p1,*p2;
u8 i,ch1,ch2;
ch2=0;
for(i=0; i<My_ConnectTable_SIZE;i++)
{//查找以前是否保存过这个连接参数
ch1=0;
p1 = &MyConnectTable[i];
if( memcmp(p1->Remote_IP,pRemoteIP,4)==0 ) ch1++;
if( memcmp(p1->Remote_Port,pRemote_Port,2)==0 ) ch1++;
if( memcmp(p1->Local_Port,pLocal_Port,2)==0 ) ch1++;
if(ch1==3)//连接表里已经有这个连接
{
ch2=1;
break;
}
}
if(ch2==0)
{//如果连接表里没有这个连接,则查找IP地址为0的空位
for(i=0; i<My_ConnectTable_SIZE;i++)
{//查找"IP地址为0"的存储位置
p1 = &MyConnectTable[i];
ch1=0;
if( p1->Remote_IP[0]==0 ) ch1++;
if( p1->Remote_IP[1]==0 ) ch1++;
if( p1->Remote_IP[2]==0 ) ch1++;
if( p1->Remote_IP[3]==0 ) ch1++;
if( ch1==4 )
{
ch2=2;
break;
}
}
}
if(ch2==0)
{//如果连接表里没有这个连接,也没有IP地址为0的空位
//则查找保存时间最久的位置
p1 = &MyConnectTable[0];
for(i=1; i<My_ConnectTable_SIZE;i++)
{//查找建立时间最长IP地址位置
p2 = &MyConnectTable[i];
if(p2->Time>p1->Time) p1=p2;
}
ch2=3;
}
memcpy(p1->Remote_IP,pRemoteIP, 4);//保存远程IP
memcpy(p1->Remote_Port,pRemote_Port, 2);//保存远程端口
memcpy(p1->Local_Port,pLocal_Port, 2);//保存本地端口
p1->Time=0;//因为准备使用这个连接,则将连接表更新计数器清零。
return (p1);
}
//函数功能:解析MSS选项,获取发送方接收缓存有大;
//Parse the TCP MSS option, if present.
#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
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[TCP_OptdataIndex + c];
//TCP_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[TCP_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[TCP_OptdataIndex + 2 + c] << 8) | TCP_RX_buf[TCP_OptdataIndex + 3 + c];
//计算"接收到的最大段大小"
t=tmp16 > TCP_MSS? 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[TCP_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[TCP_OptdataIndex + 1 + c];
}
}
}
}
//保护现场
void Protect_MyNetworkInformation_Structure_Variable(void)
{
memcpy(ConnectBackup.Remote_IP, MyNetworkInformation.Remote_IP, 4);
memcpy(ConnectBackup.Remote_MAC, MyNetworkInformation.Remote_MAC, 6);
memcpy(ConnectBackup.Remote_Port, MyNetworkInformation.Remote_Port, 2);
memcpy(ConnectBackup.Local_Port, MyNetworkInformation.Local_Port, 2);
ConnectBackup.pConnect=MyNetworkInformation.pConnect;
}
//恢复现场
void Restore_MyNetworkInformation_Structure_Variable(void)
{
memcpy(MyNetworkInformation.Remote_IP, ConnectBackup.Remote_IP, 4);
memcpy(MyNetworkInformation.Remote_MAC, ConnectBackup.Remote_MAC, 6);
memcpy(MyNetworkInformation.Remote_Port, ConnectBackup.Remote_Port, 2);
memcpy(MyNetworkInformation.Local_Port, ConnectBackup.Local_Port, 2);
MyNetworkInformation.pConnect=ConnectBackup.pConnect;
}
//函数功能:创建"连接参数",并保护MyNetworkInformation结构变量。
void Create_TCP_Client_Connect_Parameter_Use_FixedPort(u8 *pRemoteIP, u8 *pRemote_Port,u8 *pLocal_Port)
{
struct ConnectTableType *pConnect;
u8 i;
struct ARPTableType *pARPTable;
u8 loop,cnt,ret;
ret=Check_Remote_MAC_In_ARPTable_Array(pRemoteIP);
//如果"ARP表"里没有pRemoteIP指向的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=EthernetDataPacket_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;
}
}
}
for(i=0; i<ARPTable_SIZE;i++)
{//设置合适的本地端口
pARPTable = &ARPTable[i];
if( memcmp(pARPTable->Remote_IP,pRemoteIP,4)==0 )
{//发现远程IP地址
memcpy(MyNetworkInformation.Remote_MAC, pARPTable->Remote_MAC, 6);
//更新"接收方的MAC地址"
pARPTable->Time=0;
//因为准备使用这个远程MAC地址,则将ARP表更新计数器清零。
ret=1;//ARPTable[]有所给的IP地址
break;
}
}
pConnect=Get_Pointer_Of_MyConnectTable(pRemoteIP,pRemote_Port,pLocal_Port);
//根据所给的连接参数,获取"连接表"的指针;如果没有,则创建一个新的连接参数
memcpy(MyNetworkInformation.Remote_IP, pRemoteIP, 4);
memcpy(MyNetworkInformation.Remote_Port, pRemote_Port,2);
memcpy(MyNetworkInformation.Local_Port, pLocal_Port,2);
MyNetworkInformation.pConnect=pConnect;
Protect_MyNetworkInformation_Structure_Variable();
//保护现场
}
7.3、发送"ACK数据包"
void ENC28J60_Send_ACK_data_packet(struct ConnectTableType *pConnect)
{
u8 ret;
struct TCP_IPv4_Packet_Type *pTX;
u16 tmp;
u16 chksum;
int t;
u8 i;
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;
}
}
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)(EthernetDataPacketType_IP4>>8);
pTX->type[1] = (u8)(EthernetDataPacketType_IP4);
//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;
pTX->vhl = 0x45;//设置"IP版本和头部长度"
pTX->tos = 0;//设置"//服务类型"
tmp = IPHeaderLength + TCPHeaderLength;
//"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 - IPHeaderLength) >> 8);
pTX->len[1] = ((tmp - IPHeaderLength) & 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 = EthernetDataPacket_TTL;//修改IP头部中的"生存时间值"
pTX->protocol = ProtocolType_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_In_TCP_Program(&TCP_TX_buf[EthernetHeaderLength]));
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头部中的确认号"
Add_Receive_next(pConnect,pConnect->len);
//应答远程设备,需要将"接收到的序列号+用户数据长度"作为确认号
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-TCPHeaderLength - IPHeaderLength;
if(t<=0)t=1;
pConnect->len = t;
pTX->tcpoffset = (TCPHeaderLength / 4) << 4;
//TCP数据包无optdata[],BUF->tcpoffset=0x50
//TCPHeaderLength=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] = ((TCP_MSS) >> 8);//修改"TCP头部中的窗口大小"
pTX->wnd[1] = ((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=EthernetHeaderLength+IPHeaderLength + TCPHeaderLength;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20",得到len=54
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
}
}
7.4、主动断开"连接"和被动断开"连接"
//函数功能:发送"FIN+ACK数据包"
void ENC28J60_Send_FIN_ACK_data_packet(struct ConnectTableType *pConnect)
{
struct TCP_IPv4_Packet_Type *pTX;
u16 tmp,chksum;
int t;
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)(EthernetDataPacketType_IP4>>8);
pTX->type[1] = (u8)(EthernetDataPacketType_IP4);
//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;
pTX->vhl = 0x45;//设置"IP版本和头部长度"
pTX->tos = 0;//设置"服务类型"
tmp = IPHeaderLength + TCPHeaderLength;
//"IP头部的大小20"+"TCP头部的大小20" ,得到tmp
#if UIP_CONF_IPV6
//For IPv6, the IP length field does not include the IPv6 IP header length.
pTX->len[0] = ((tmp - IPHeaderLength) >> 8);
pTX->len[1] = ((tmp - IPHeaderLength) & 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 = EthernetDataPacket_TTL;//修改IP头部中的"生存时间值"
pTX->protocol = ProtocolType_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_In_TCP_Program(&TCP_TX_buf[EthernetHeaderLength]));
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-TCPHeaderLength - IPHeaderLength;
if(t<=0)t=1;
pConnect->len = t;
pTX->tcpoffset = (TCPHeaderLength / 4) << 4;
//TCP数据包无optdata[],BUF->tcpoffset=0x50
//TCPHeaderLength=20,设置"TCP头部的长度"为5*4=20
pTX->flags = (TCP_FIN | TCP_ACK);
//TCP头部的标志位为"FIN+ACK"
pTX->wnd[0] = ((TCP_MSS) >> 8);//修改"TCP头部中的窗口大小"
pTX->wnd[1] = ((TCP_MSS) & 0xff);//修改"TCP头部中的窗口大小"
pTX->urgp[0] = 0;
pTX->urgp[1] = 0;//修改TCP头部中的"紧急指针"
TCP_TX_Length = EthernetHeaderLength + IPHeaderLength + TCPHeaderLength;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20",得到TCP_TX_Length
//计算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);
#if TCP_Debug == 1
Print_MyConnectTable();
Print_ARP_Table();
#endif
}
//函数功能:主动断开连接
/*
断开连接需要执行4次挥手:
"四次挥手"第1步,发送"FIN+ACK数据包";
"四次挥手"第2步,接收到"ACK数据包";
"四次挥手"第3步,接收到"FIN+ACK数据包";
"四次挥手"第4步,发送"ACK数据包"
*/
u8 ENC28J60_Disconnect(u8 *pRemoteIP, u8 *pRemote_Port,u8 *pLocal_Port)
{
u8 loop,tmp,ret;
u16 cnt;
loop=0;ret=0;
Create_TCP_Client_Connect_Parameter_Use_FixedPort( pRemoteIP,
pRemote_Port,
pLocal_Port);
//创建"连接参数",并保护MyNetworkInformation结构变量
MyNetworkInformation.pConnect->TCPStateFlag=TCP_CLOSING;
if(ConnectBackup.pConnect!=NULL) loop=10;
while(loop)
{
Restore_MyNetworkInformation_Structure_Variable();
//恢复现场
ENC28J60_Send_FIN_ACK_data_packet(MyNetworkInformation.pConnect);
//"四次挥手"第1步,发送"FIN+ACK数据包"
#if TCP_Debug == 1
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
#endif
loop--;
cnt=EthernetDataPacket_TTL*4;
while(cnt)
{
delay_ms(1);
cnt--;
tmp=ENC28J60_Receive_Data_From_Ethernet_Network();
if(tmp==TCP_ACK_RX_Flag)
{//"四次挥手"第2步,接收到"ACK数据包"
ret=1;
cnt=0;
loop=0;
#if TCP_Debug == 1
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
#endif
}
}
}
cnt=0;ret=0;
if(tmp==TCP_ACK_RX_Flag) cnt=EthernetDataPacket_TTL*4;
while(cnt)
{
Restore_MyNetworkInformation_Structure_Variable();
//恢复现场
cnt--;
delay_ms(1);
tmp=ENC28J60_Receive_Data_From_Ethernet_Network();
if(tmp==TCP_FIN_ACK_RX_Flag)
{//"四次挥手"第3步,接收到"FIN+ACK数据包"
ENC28J60_Send_ACK_data_packet(MyNetworkInformation.pConnect);
//发送ACK应答,表示自己进入了关闭等待(CLOSE-WAIT)状态
//"四次挥手"第4步,发送"ACK数据包"
#if TCP_Debug == 1
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
#endif
ret=1;
cnt=0;
loop=0;
#if TCP_Debug == 1
Print_MyConnectTable();
Print_ARP_Table();
#endif
MyNetworkInformation.pConnect->TCPStateFlag=TCP_CLOSED;
MyNetworkInformation.pConnect->ConnectFlag=0;
ConnectBackup.pConnect=NULL;//完成了"发送数据"
}
}
return(ret);
}
//函数功能:被动关闭连接
/*
被动断开连接也要执行4次挥手:
"四次挥手"第1步,接收"FIN+ACK数据包";
"四次挥手"第2步,发送"ACK应答数据包";
"四次挥手"第3步,发送"FIN+ACK数据包";
"四次挥手"第4步,接收"ACK数据包";
该函数的调用条件是接收到"FIN+ACK数据包",然后执行"挥手2、3和4";
*/
u8 Remote_Device_Close_Connection_Enable( struct ConnectTableType *pConnect,
struct TCP_IPv4_Packet_Type *pRX)
{
u8 loop,cnt,tmp,ret;
Update_SequenceNumber_AcknowledgeNumber_And_Calculate_UserDataLength(pConnect,pRX);
ENC28J60_Send_ACK_data_packet(pConnect);
//"四次挥手"第2步,发送"ACK应答数据包",表示自己进入了关闭等待(CLOSE-WAIT)状态
#if TCP_Debug == 1
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
#endif
loop=2;ret=0;
while(loop)
{
ENC28J60_Send_FIN_ACK_data_packet(pConnect);
//"四次挥手"第3步,发送"FIN+ACK数据包",同意断开
#if TCP_Debug == 1
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
#endif
loop--;
cnt=EthernetDataPacket_TTL*2;
while(cnt)
{
delay_ms(1);
cnt--;
tmp=ENC28J60_Receive_Data_From_Ethernet_Network();
//"四次挥手"第4步,接收"ACK数据包"
if(tmp==TCP_ACK_RX_Flag)
{
ret=1;
cnt=0;
loop=0;
#if TCP_Debug == 1
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
Print_MyConnectTable();
Print_ARP_Table();
#endif
pConnect->TCPStateFlag=TCP_CLOSING;
}
}
}
return(ret);
}
7.5、发送"RST数据包"
//函数功能:发送"RST数据包",用来断开与TCP服务器的连接
/*
发送"RST数据包"后,发送方会立即关闭连接,而不需要等待对方的确认。接收方收到RST包后,也会立即关闭连接。
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(u8 *pRemoteIP, u8 *pRemote_Port,u8 *pLocal_Port)
{
struct TCP_IPv4_Packet_Type *pTX;
u16 tmp,chksum;
int t;
Create_TCP_Client_Connect_Parameter_Use_FixedPort(pRemoteIP,pRemote_Port,pLocal_Port);
//创建"连接参数",并保护MyNetworkInformation结构变量
MyNetworkInformation.pConnect->TCPStateFlag=TCP_CLOSING;
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)(EthernetDataPacketType_IP4>>8);
pTX->type[1] = (u8)(EthernetDataPacketType_IP4);
//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;
pTX->vhl = 0x45;//设置"IP版本和头部长度"
pTX->tos = 0;//设置"服务类型"
tmp = IPHeaderLength + TCPHeaderLength;
//"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 - IPHeaderLength) >> 8);
pTX->len[1] = ((tmp - IPHeaderLength) & 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 = EthernetDataPacket_TTL;//修改IP头部中的"生存时间值"
pTX->protocol = ProtocolType_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_In_TCP_Program(&TCP_TX_buf[EthernetHeaderLength]));
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] = MyNetworkInformation.pConnect->Send_next[0];
pTX->seqno[1] = MyNetworkInformation.pConnect->Send_next[1];
pTX->seqno[2] = MyNetworkInformation.pConnect->Send_next[2];
pTX->seqno[3] = MyNetworkInformation.pConnect->Send_next[3];
//修改"TCP头部中的确认号"
//因为这里不是应答远程设备,所以不用将"接收到的序列号+用户数据长度"作为确认号
pTX->ackno[0] = MyNetworkInformation.pConnect->Receive_next[0];
pTX->ackno[1] = MyNetworkInformation.pConnect->Receive_next[1];
pTX->ackno[2] = MyNetworkInformation.pConnect->Receive_next[2];
pTX->ackno[3] = MyNetworkInformation.pConnect->Receive_next[3];
t=pTX->len[0];t=(u16)(t<<8);
t=t|pTX->len[1];
t=t-TCPHeaderLength - IPHeaderLength;
if(t<=0)t=1;
MyNetworkInformation.pConnect->len = t;
pTX->tcpoffset = (TCPHeaderLength / 4) << 4;
//TCP数据包无optdata[],BUF->tcpoffset=0x50
//TCPHeaderLength=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=EthernetHeaderLength+IPHeaderLength + TCPHeaderLength;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20",得到len=54
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
MyNetworkInformation.pConnect->TCPStateFlag=TCP_CLOSED;
MyNetworkInformation.pConnect->ConnectFlag=0;
ConnectBackup.pConnect=NULL;//完成了"发送数据"
#if TCP_Debug == 1
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
Print_MyConnectTable();
Print_ARP_Table();
#endif
}
7.6、客户端主动建立连接
客户端需要连接到服务端,必须"SYN同步数据包"。
//函数功能:发送"SYN同步数据包",为的是连接到TCP服务器
/*
发送"SYN同步数据包":
以太网头部:
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
IP头部:
0x45 0x00 0x00 0x2C 0x00 0x01 0x00 0x00 0x40 0x06 0xF6 0xAB
0xC0 0xA8 0x01 0x11 0xC0 0xA8 0x01 0xBE
TCP头部:
0x03 0xE8 0x13 0x88 0x00 0x00 0xA0 0xB1 0x00 0x00 0x00 0x00
0x60 0x02 0x05 0xB4 0x58 0x31 0x00 0x00 0x02 0x04 0x05 0xB4
分析得到序列号为"0x00 0x00 0xA0 0xB1"
分析得到确认号为"0x00 0x00 0x00 0x00"
*/
void ENC28J60_Send_TCP_SYN_data_packet(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)(EthernetDataPacketType_IP4>>8);
pTX->type[1] = (u8)(EthernetDataPacketType_IP4);
//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;
pTX->vhl = 0x45;//设置"IP版本和头部长度"
pTX->tos = 0;//设置"//服务类型"
tmp = IPHeaderLength + TCPHeaderLength + 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 - IPHeaderLength) >> 8);
pTX->len[1] = ((tmp - IPHeaderLength) & 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 = EthernetDataPacket_TTL;//修改IP头部中的"生存时间值"
pTX->protocol = ProtocolType_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_In_TCP_Program(&TCP_TX_buf[EthernetHeaderLength]));
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 = ( (TCPHeaderLength + 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] = ((TCP_MSS) >> 8);//修改"TCP头部中的窗口大小"
pTX->wnd[1] = ((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] = (TCP_MSS) / 256;//最大段高8数值,TCP最大段大小为(1500-14-40)
pTX->optdata[3] = (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= EthernetHeaderLength+IPHeaderLength
-
TCPHeaderLength
-
TCP_OPT_MSS_LEN;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20"+"可选项大小4",得到len=58
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
}
//函数功能:根据所给的连接参数,主动连接到服务端
/*
建立连接时,需要执行"三次握手":
"三次握手"第1步,发送"SYN数据包";
"三次握手"第2步,接收"SYN+ACK数据包";
"三次握手"第3步,发送"ACK应答数据包";
*/
void TCP_Client_Connect_To_Server_Use_FixedPort( u8 *pRemoteIP,
u8 *pRemote_Port,
u8 *pLocal_Port)
{
u8 cnt,ret;
u8 SendResetConter;
Create_TCP_Client_Connect_Parameter_Use_FixedPort(pRemoteIP,pRemote_Port,pLocal_Port);
//创建"连接参数",并保护MyNetworkInformation结构变量
while(ConnectBackup.pConnect!=NULL)
{
SendResetConter=0;
Restore_MyNetworkInformation_Structure_Variable();
//恢复现场
ENC28J60_Disconnect(pRemoteIP,pRemote_Port,pLocal_Port);
//告诉对方断开连接
SendResetConter++;
if(SendResetConter>=2)
{
Restore_MyNetworkInformation_Structure_Variable();
//恢复现场
ENC28J60_Send_RST_data_packet(pRemoteIP,pRemote_Port,pLocal_Port);
}
Restore_MyNetworkInformation_Structure_Variable();
//恢复现场
ENC28J60_Send_TCP_SYN_data_packet();//"三次握手"第1步,发送"SYN数据包"
MyNetworkInformation.pConnect->TCPStateFlag=TCP_SYN_SENT;
cnt=EthernetDataPacket_TTL;
while(cnt)
{
delay_ms(1);
cnt--;
ret=ENC28J60_Receive_Data_From_Ethernet_Network();
"三次握手"第2步,接收"SYN+ACK数据包"
if(ret==TCP_SYN_ACK_RX_Flag)
{
cnt=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();
Print_ARP_Table();
#endif
Restore_MyNetworkInformation_Structure_Variable();
//恢复现场
ENC28J60_Send_ACK_data_packet(MyNetworkInformation.pConnect);
//"三次握手"第3步,发送"ACK应答数据包"
MyNetworkInformation.pConnect->TCPStateFlag=TCP_ESTABLISHED;
MyNetworkInformation.pConnect->ConnectFlag=1;
ConnectBackup.pConnect=NULL;//完成了"三次握手"
#if TCP_Debug == 1
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
#endif
}
}
}
7.7、发送数据
//函数功能:发送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)
{
struct TCP_IPv4_Packet_Type *pTX;
u16 tmp,chksum;
int t;
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)(EthernetDataPacketType_IP4>>8);
pTX->type[1] = (u8)(EthernetDataPacketType_IP4);
//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;
pTX->vhl = 0x45;//设置"IP版本和头部长度"
pTX->tos = 0;//设置"服务类型"
tmp = IPHeaderLength + TCPHeaderLength + len;
//"IP头部的大小20"+"TCP头部的大小20"+"用户数据长度len" ,得到tmp
if(urgData)
{
tmp=tmp+1;//紧急数据占1个字节
//以太网上的TCP紧急数据仅支持传输1字节的数据。
}
#if UIP_CONF_IPV6
//For IPv6, the IP length field does not include the IPv6 IP header length.
pTX->len[0] = ((tmp - IPHeaderLength) >> 8);
pTX->len[1] = ((tmp - IPHeaderLength) & 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 = EthernetDataPacket_TTL;//修改IP头部中的"生存时间值"
pTX->protocol = ProtocolType_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_In_TCP_Program(&TCP_TX_buf[EthernetHeaderLength]));
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-TCPHeaderLength - IPHeaderLength;
if(t<=0)t=1;
pConnect->len = t;
pTX->tcpoffset = (TCPHeaderLength / 4) << 4;
//TCP数据包无optdata[],BUF->tcpoffset=0x50
//TCPHeaderLength=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] = ((TCP_MSS) >> 8);//修改"TCP头部中的窗口大小"
pTX->wnd[1] = ((TCP_MSS) & 0xff);//修改"TCP头部中的窗口大小"
if(urgData)
{
TCP_TX_Length = EthernetHeaderLength + IPHeaderLength + TCPHeaderLength + len + 1;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20"+"用户数据"+"紧急数据",得到TCP_TX_Length
//以太网上的TCP紧急数据仅支持传输1字节的数据。
tmp=len+1;
pTX->urgp[0] = (u8)(tmp>>8);
pTX->urgp[1] = (u8)(tmp);//修改TCP头部中的"紧急指针"
//添加用户数据
tmp = EthernetHeaderLength + IPHeaderLength + TCPHeaderLength;
for(;tmp<TCP_TX_Length-1;tmp++)
{
TCP_TX_buf[tmp]=*pData;
pData++;
}
if(urgData)
{
TCP_TX_buf[tmp]=*urgData;//紧急数据
}
}
else
{
tmp = EthernetHeaderLength + IPHeaderLength + TCPHeaderLength;
TCP_TX_Length = EthernetHeaderLength + IPHeaderLength + TCPHeaderLength + 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);
}
/*
发送用户数据
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00
0x45 0x00 0x00 0x2B 0x00 0x03 0x00 0x00 0x40 0x06 0xF6 0xAA
0xC0 0xA8 0x01 0x11
0xC0 0xA8 0x01 0xBE
0x03 0xE8 0x13 0x88
0x00 0x00 0xA0 0xB2 0xB1 0xD2 0x47 0xC9
0x50 0x18 0x05 0xB4 0xCF 0xF4 0x00 0x00 ABC
接收来自计算机的应答
0x00 0x08 0xDC 0x11 0x11 0x02 0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x08 0x00
0x45 0x00 0x00 0x28 0xCE 0xA8 0x40 0x00 0x80 0x06 0xA8 0x07
0xC0 0xA8 0x01 0xBE
0xC0 0xA8 0x01 0x11
0x13 0x88 0x03 0xE8
0xB1 0xD2 0x67 0xC9 0x00 0x00 0xA0 0xB5
0x50 0x10 0xFA 0xED 0x5F 0x05 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
*/
//函数功能:将首地址为pData的前len个字节发送给指定连接的设备
u8 TCP_Client_Send_Data_To_Server(u8 *pRemoteIP,
u8 *pRemote_Port,
u8 *pLocal_Port,
u8 *pData,
u16 len,
u8 *urgData)
{
u8 cnt,tmp,ret,repeat;
ret=0;repeat=0;
Create_TCP_Client_Connect_Parameter_Use_FixedPort(pRemoteIP,pRemote_Port,pLocal_Port);
//创建"连接参数",并保护MyNetworkInformation结构变量
if(MyNetworkInformation.pConnect->ConnectFlag!=1)
{
ConnectBackup.pConnect=NULL;
TCP_Client_Connect_To_Server_Use_FixedPort(pRemoteIP,pRemote_Port,pLocal_Port);
Protect_MyNetworkInformation_Structure_Variable();
//保护现场
}
while(ConnectBackup.pConnect!=NULL)
{
Restore_MyNetworkInformation_Structure_Variable();
//恢复现场
ENC28J60_Send_PSH_data_packet(MyNetworkInformation.pConnect,pData,len,urgData);
#if TCP_Debug == 1
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
#endif
repeat++;
cnt=EthernetDataPacket_TTL;
while(cnt)
{
delay_ms(1);
cnt--;
tmp=ENC28J60_Receive_Data_From_Ethernet_Network();
if(tmp==TCP_ACK_RX_Flag)
{
ret=1;
cnt=0;
ConnectBackup.pConnect=NULL;//完成了"发送数据"
#if TCP_Debug == 1
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
Print_MyConnectTable();
Print_ARP_Table();
#endif
}
}
}
return(ret);
}
7.8、服务端被动连接
//函数功能:当收到"SYN请求连接数据包"时,才可以调用该函数回应"同意连接"
void TCP_Server_Send_TCP_SYN_ACK_data_packet(struct ConnectTableType *pConnect)
{
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)(EthernetDataPacketType_IP4>>8);
pTX->type[1] = (u8)(EthernetDataPacketType_IP4);
//设置太网帧的类型为0x0800,表示后面跟着的是IPV4数据包;
pTX->vhl = 0x45;//设置"IP版本和头部长度"
pTX->tos = 0;//设置"//服务类型"
tmp = IPHeaderLength + TCPHeaderLength + 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 - IPHeaderLength) >> 8);
pTX->len[1] = ((tmp - IPHeaderLength) & 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 = EthernetDataPacket_TTL;//修改IP头部中的"生存时间值"
pTX->protocol = ProtocolType_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_In_TCP_Program(&TCP_TX_buf[EthernetHeaderLength]));
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头部中的确认号"
Add_Receive_next(pConnect,pConnect->len);
//应答远程设备,需要将"接收到的序列号+用户数据长度"作为确认号
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 = ( (TCPHeaderLength + TCP_OPT_MSS_LEN) / 4 ) << 4;
//计算"TCP头部的长度",TCP数据包有optdata[],pTX->tcpoffset=0x60
pTX->flags = TCP_SYN|TCP_ACK;
//TCP服务端发送"SYN+ACK"同意请求时,TCP头部的标志位为"TCP_SYN|TCP_ACK"
//设置我的接收窗口大小
pTX->wnd[0] = ((TCP_MSS) >> 8);//修改"TCP头部中的窗口大小"
pTX->wnd[1] = ((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] = (TCP_MSS) / 256;//最大段高8数值,TCP最大段大小为(1500-14-40)
pTX->optdata[3] = (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=EthernetHeaderLength+IPHeaderLength + TCPHeaderLength + TCP_OPT_MSS_LEN;
//"以太网头部的大小14"+"IP头部的大小20"+"TCP头部的大小20"+"可选项大小4",得到len=58
ENC28J60_Packet_Send(TCP_TX_Length,TCP_TX_buf);
}
//函数功能:连接ENC28J60服务器使能
void TCP_Client_Connect_To_ENC28J60_Server(struct ConnectTableType *pConnect)
{
u8 loop,cnt,ret;
loop=1;
while(loop)
{
TCP_Server_Send_TCP_SYN_ACK_data_packet(pConnect);
pConnect->TCPStateFlag=TCP_SYN_SENT;
cnt=EthernetDataPacket_TTL;
while(cnt)
{
delay_ms(1);
cnt--;
ret=ENC28J60_Receive_Data_From_Ethernet_Network();
if(ret==TCP_ACK_RX_Flag)
{
cnt=0;
loop=0;
}
}
}
if(ret==TCP_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();
Print_ARP_Table();
#endif
}
}
7.9、TCP接收程序
//函数功能:接收来自计算机的TCP请求/TCP应答
u8 TCPIP4_Work(void)
{
struct TCP_IPv4_Packet_Type *pRX;
u16 opcode;
u8 ch,ret;
struct ConnectTableType *pConnect;
ret=0;ch=0;
pRX=(struct TCP_IPv4_Packet_Type *)&TCP_RX_buf[0];
if(memcmp(pRX->Receive_Port,Local_Port,2)==0) ch=1; //外设应答时,我的接收的端口
if(memcmp(pRX->Receive_Port,Server_Port,2)==0) ch=1;//外设请求时,我的接收的端口
if( Check_Receive_TCPv4_header_checksume() ) ch++;
if(ch==2)
{//"接收TCPIP数据包之TCP头部校验和"正确,返回1
Update_ARPTable_In_Received_IPDataPacket();
//如果ARP表里没有接收到的MAC地址,则更新ARP表,返回"ARP表"的指针
pConnect=Get_Pointer_Of_MyConnectTable( pRX->Send_IP,
pRX->Send_Port,
pRX->Receive_Port);
//根据所给的连接参数,获取"连接表"的指针;如果没有,则创建一个新的连接参数
Update_MyNetworkInformation_In_Received_TCPDataPacket(pConnect);
//如果接收到正确的TCPIP数据包,则强制更新MyNetworkInformation结构变量
opcode=(u16)(pRX->flags & TCP_CTL);
printf("\r\nTCP opcode=0x%02X\r\n",opcode);
// Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
if( opcode== (TCP_SYN | TCP_ACK) )//远程设备同意"让我连"
{//接收到"syn+ack应答"数据包
Parse_the_TCP_MSS_option(pConnect);
//解析TCP MSS选项,获取对方的接收缓存有大。Parse the TCP MSS option, if present.
pConnect->TCPStateFlag = TCP_SYN_RECEIVED;//系统进入SYN_RECEIVED状态
Update_SequenceNumber_AcknowledgeNumber_And_Calculate_UserDataLength(pConnect,pRX);
pConnect->Time=0;
pConnect->ConnectFlag=1;//客户端建立连接
ret=1;
}
if( opcode== TCP_SYN)//远程设备想要"连接我"
{
#if TCP_Debug == 1
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
#endif
Parse_the_TCP_MSS_option(pConnect);
//解析TCP MSS选项,获取对方的接收缓存有大。Parse the TCP MSS option, if present.
pConnect->TCPStateFlag = TCP_SYN_RECEIVED;//系统进入SYN_RECEIVED状态
Update_SequenceNumber_AcknowledgeNumber_And_Calculate_UserDataLength(pConnect,pRX);
TCP_Client_Connect_To_ENC28J60_Server(pConnect);
//连接ENC28J60服务器使能
pConnect->TCPStateFlag=TCP_ESTABLISHED;
pConnect->Time=0;
pConnect->ConnectFlag=1;//同意建立连接
}
if( opcode == TCP_ACK )//接收到"ACK应答"
{
Update_SequenceNumber_AcknowledgeNumber_And_Calculate_UserDataLength(pConnect,pRX);
pConnect->Time=0;
ret=2;
}
if( ( opcode & (TCP_PSH | TCP_ACK) )==(TCP_PSH | TCP_ACK) )
{//接收到"PSH+ACK应答",说明对方发送用户数据给我
Update_SequenceNumber_AcknowledgeNumber_And_Calculate_UserDataLength(pConnect,pRX);
ENC28J60_Send_ACK_data_packet(pConnect);//发送ACK应答
#if TCP_Debug == 1
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
Print_Send_Package(TCP_TX_buf,TCP_TX_Length);
Print_MyConnectTable();
Print_ARP_Table();
#endif
pConnect->Time=0;
ret=3;
}
if( opcode== (TCP_FIN | TCP_ACK) )//远程设备要求关闭"连接"
{
//获取存放"连接参数"的位置
if(pConnect->TCPStateFlag==TCP_CLOSING)
{//本地主动关闭
ret=4;
}
else
{//远程关闭
//"四次挥手"第1步,接收"FIN+ACK数据包"
pConnect->TCPStateFlag=TCP_CLOSING; //接收到"关闭连接"
Remote_Device_Close_Connection_Enable(pConnect,pRX);
//允许远程关闭连接
pConnect->TCPStateFlag=TCP_CLOSED;
pConnect->Time=0;
ret=5;
}
}
if( (opcode&TCP_RST)== TCP_RST )//如果收到ACK+RST,远程设备要求复位
{
Update_SequenceNumber_AcknowledgeNumber_And_Calculate_UserDataLength(pConnect,pRX);
#if TCP_Debug == 1
Print_Receive_Package(TCP_RX_buf,TCP_RX_Length);
#endif
pConnect->TCPStateFlag=TCP_CLOSED;
pConnect->Time=0;
pConnect->ConnectFlag=0;
}
}
return(ret);
}
main.c
#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"
//#include "Ethernet_Network_Receive.h"
//PLL配置: PLLCLK = HSE * 6 = 72 MHz,2025年11月8日修改
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';//紧急数据
}
const char CPU_Reset_REG[]="\r\nCPU reset!\r\n";
const char Remote_ip_REG[]={192,168,1,190};//远程IP地址为"192.168.1.190"
const char Remote_Port[]={0x13,0x88};//远程端口为"0x1388=5000"
int main(void)
{
// u8 Remote_ip[4];
// u8 Remote_Port[2];
// u8 Local_Port[2];
u8 urgData;
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();
// ENC28J60_Send_PING_Command((u8*)Remote_ip_REG);
// //ENC28J60发送PING命令
TCP_Client_Connect_To_Server_Use_FixedPort((u8*)Remote_ip_REG,(u8*)Remote_Port,(u8*)Local_Port);
// Print_MyConnectTable();
TCP_Client_Send_Data_To_Server((u8*)Remote_ip_REG,(u8*)Remote_Port,(u8*)Local_Port,"ABC",3,NULL);
// Print_MyConnectTable();
delay_ms(5000);
ENC28J60_Disconnect((u8*)Remote_ip_REG,(u8*)Remote_Port,(u8*)Local_Port);
// ENC28J60_Send_RST_data_packet((u8*)Remote_ip_REG,(u8*)Remote_Port,(u8*)Local_Port);
urgData='H';
TCP_Client_Send_Data_To_Server((u8*)Remote_ip_REG,(u8*)Remote_Port,(u8*)Local_Port,"EFG",3,&urgData);
Print_MyConnectTable();
while(1)
{
ENC28J60_Receive_Data_From_Ethernet_Network();
if(timer_expired(&TCP_Timer))
{
timer_reset(&TCP_Timer);//50*10=500毫秒
MyConnectTable_Update_Timer();
//0.5秒执行一次,如果"远程设备IP地址不为0,且建立时间超过2分钟",则将其IP地址设置为0
}
if(timer_expired(&ARP_Timer))
{
timer_reset(&ARP_Timer);
ARP_Table_Update_Timer();
//10秒执行一次,如果"远程设备IP地址不为0,且建立时间超过20分钟",则将其IP地址设置为0
}
}
}
八、测试结果
