学习TCP/IP的第10步:TCP访问外网

TCP访问外网?前面所写的都是在内网下工作的,现在要访问外网,TCP数据包如何实现。

内网 : 也就是内部私有的网络。也就是我们常说的局域网。

文件下载连接:

​​​​​​​https://download.csdn.net/download/weixin_42550185/92647267?spm=1001.2014.3001.5501

内网IP又分为3种:

第1种10.0.0.0~10.255.255.255

第2种172.16.0.0~172.31.255.255

第3种192.168.0.0~192.168.255.255

其它的IP你就认为是外网IP

1、TCP/IP数据包,双方的IP地址,在同一局域网内:

以太网帧头:

dest_MAC[6]用来存放目的MAC,也就是局域网中的"非本机和路由器"的其它设备的MAC地址

src_MAC[6]用来存放源MAC,也就是本地的MAC地址

IP头部:

Send_IP[4]用来存放发送方的IP地址,假定为"192.168.1.17" 。

Receive_IP[4]用来存放接收方的IP地址,假定为"192.168.1.190" 。

2、TCP/IP数据包,双方的IP地址,不在同一局域网内,就需要进行跨网络通信。

以太网帧头:

dest_MAC[6]用来存放目的MAC,也就是局域网中的"路由器"的MAC地址

src_MAC[6]用来存放源MAC,也就是本地的MAC地址

IP头部:

Send_IP[4]用来存放发送方的IP地址,假定为"192.168.1.17" 。

Receive_IP[4]用来存放接收方的IP地址,假定为"112.26.166.6"。这是域名"httpbin.org"绑定的IP地址。

3、发送"ICMP和TCP数据包"时,路由器判断的程过程

当路由器收到"ICMP和TCP数据包"时,先检查目标IP地址是否在同一子网内。路由器先查询自己的"路由表",

查看是否有"接收方IP主机的MAC地址"。如果没有,则用ARP查询"接收方IP主机的MAC",然后保存"接收方的MAC

和与之对应的IP地址"。修改以太网头部的目的MAC地址就可以了。

if (接收方IP在同一子网内)

{

dest_MAC[6] = ARP查询到的目标主机的MAC

}

else

{

dest_MAC[6] = 该路由器的"路由器的MAC"

}

将"修改好的ICMP和TCP数据包"发送给目的主机。

4、获取本地路由器的MAC地址

u8 Check_RemoteIP_Address_In_SameNetwork(u8 *pRemote_IP)

{

u8 ch2,ret;

u8 loop,cnt,t;

struct ARPTableType *p1;

ch2=0;ret=0;

if( (pRemote_IP[0] & MyNetworkInformation.Subnet_Mask[0]) ==

(MyNetworkInformation.Local_IP[0] & MyNetworkInformation.Subnet_Mask[0]) )

ch2++;

if( (pRemote_IP[1] & MyNetworkInformation.Subnet_Mask[1]) ==

(MyNetworkInformation.Local_IP[1] & MyNetworkInformation.Subnet_Mask[1]) )

ch2++;

if( (pRemote_IP[2] & MyNetworkInformation.Subnet_Mask[2]) ==

(MyNetworkInformation.Local_IP[2] & MyNetworkInformation.Subnet_Mask[2]) )

ch2++;

if( (pRemote_IP[3] & MyNetworkInformation.Subnet_Mask[3]) ==

(MyNetworkInformation.Local_IP[3] & MyNetworkInformation.Subnet_Mask[3]) )

ch2++;

//IP地址前3个字节相同,只是最后一个字节不同,通常它们位于"同一网络(子网)"内

if(ch2!=0x04)//不位于"同一网络(子网)"内

{

ret=1;

}

else//位于"同一网络(子网)"内

{

ret=0;

}

if(ret==1)//不位于"同一网络(子网)"内

{

t=0;

for(cnt=0; cnt<ARPTable_SIZE;cnt++)

{

p1 = &ARPTable[cnt];

if( memcmp(p1->Remote_IP,MyNetworkInformation.Router_IP,4)==0 )

{//发现发现路由器IP地址

memcpy(MyNetworkInformation.Remote_MAC, p1->Remote_MAC, 6);

//更新"接收方的MAC地址"

p1->Time=0;

//因为准备使用这个远程MAC地址,则将ARP表更新计数器清零。

t=1;//ARPTable[]有所给的IP地址,则设置t=1

break;

}

}

if(t==0)//没有发现路由器IP地址和MAC地址

{

memset(MyNetworkInformation.Remote_MAC, 0xFF, 6);

//"接收方的MAC地址"为"0xFF,0xFF,0xFF,0xFF,0xFF,0xFF"

memcpy(MyNetworkInformation.Remote_IP, MyNetworkInformation.Router_IP, 4);

//将路由器IP地址设置为远程IP地址,准备读取路由器的MAC地址

loop=2;ch2=0;

while(loop)

{//ARP表里没有"路由器IP地址"

ENC28J60_Send_ARP_REQUEST();

loop--;

cnt=EthernetDataPacket_TTL;

while(cnt)

{

delay_ms(1);

cnt--;

ch2=ENC28J60_Receive_Data_From_Ethernet_Network();

if(ch2==ARP_REPLY_RX_Flag)

{

cnt=0;

loop=0;

break;

}

}

}

}

memcpy(MyNetworkInformation.Remote_IP, pRemote_IP, 4);

}

else//位于"同一网络(子网)"内

{

t=0;

for(cnt=0; cnt<ARPTable_SIZE;cnt++)

{

p1 = &ARPTable[cnt];

if( memcmp(p1->Remote_IP,pRemote_IP,4)==0 )

{//发现远程IP地址

memcpy(MyNetworkInformation.Remote_MAC, p1->Remote_MAC, 6);

//更新"接收方的MAC地址"

p1->Time=0;//因为准备使用这个远程MAC地址,则将ARP表更新计数器清零。

t=1;//ARPTable[]有所给的IP地址,则设置t=1

break;

}

}

if(t==0)//没有发现远程IP地址和MAC地址

{

memset(MyNetworkInformation.Remote_MAC, 0xFF, 6);

//"接收方的MAC地址"为"0xFF,0xFF,0xFF,0xFF,0xFF,0xFF"

memcpy(MyNetworkInformation.Remote_IP, pRemote_IP, 4);

//将路由器IP地址设置为远程IP地址,准备读取路由器的MAC地址

loop=2;ch2=0;

while(loop)

{//ARP表里没有"pRemote_IP指向的IP地址"

ENC28J60_Send_ARP_REQUEST();

loop--;

cnt=EthernetDataPacket_TTL;

while(cnt)

{

delay_ms(1);

cnt--;

ch2=ENC28J60_Receive_Data_From_Ethernet_Network();

if(ch2==ARP_REPLY_RX_Flag)

{

cnt=0;

loop=0;

break;

}

}

}

}

}

return(ret);

}

5、ENC28J60_Variable.h

复制代码
#ifndef _ENC28J60_Variable_H
#define _ENC28J60_Variable_H

#include "stm32f10x.h"//使能uint8_t,uint16_t,uint32_t,uint64_t,int8_t,int16_t,int32_t,int64_t

//以太网帧的类型:
#define EthernetDataPacketType_ARP  0x0806  //ARP请求包类型
#define EthernetDataPacketType_IP4  0x0800  //IPV4数据包类型
#define EthernetDataPacketType_IP6  0x86dd  //IPv6包类型,本程序不支持IPv6

//在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

#define EthernetDataPacket_TTL  64  //以太网数据包的生存时间(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 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


extern u8 TCP_TX_buf[EthernetBufferSize];//ENC28JI60发送数据的缓存
extern u16 TCP_TX_Length;             //记录TCP_TX_buf[]的有效字节数
extern u8 TCP_RX_buf[EthernetBufferSize];//ENC28JI60接收数据的缓存
extern 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];   //记录"远程接收窗口"大小
	//如果"接收到的最大段大小"大于"本地的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个连接
extern 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;//记录连接表指针
};
extern struct NetworkInformationType MyNetworkInformation;
extern const char Local_Port[];//本地端口为"1000"
extern const char Server_Port[];//用作服务器时的端口为"1000"

//在接收时,程序会自动更新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;//记录连接表指针
};
extern struct ConnectType ConnectBackup;

//定义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地址
extern struct ARPTableType ARPTable[ARPTable_SIZE];

extern 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版本
//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;         //数据包生存时间
  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数值
};

#endif

6、ICMP.c

复制代码
#include "ICMP.h"
#include "string.h" //使能strcpy(),strlen(),memset(),NULL
#include "stdio.h"  //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()
#include "delay.h"

#include "ENC28J60_Variable.h"
#include "enc28j60.h"
#include "ENC28J60_Interface.h"
#include "ARP.h"

/*
ICMP是"Internet Control Message Protocol"的缩写,它是一种网络层协议,
主要用于发送错误消息和操作信息。
Ping命令是通过ICMP回显请求(Echo Request)和响应(Echo Reply)来测试主机连通性。
*/

u16 ICMP_SequenceNumber;

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;
#if ICMP_Debug == 1
		printf("\r\nICMP opcode=0x%02X\r\n",opcode);
#endif

		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;
		    }
	    }
	  }
	}
}

7、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"

//PLL配置: PLLCLK = HSE * 6 = 72 MHz,2025年11月8日修改


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"

//const char httpbin_org_server_name[] = "httpbin.org";//默认域名为"httpbin.org"
//const char httpbin_org_IP_REG[]="112.26.166.6";
const char Remote_ip_REG[]={112,26,166,6};//远程IP地址为"112.26.166.6"
const char Remote_Port[]={0x00,0x50};//用作服务器时的端口为"0x0050=80"

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();
//	Test_ICMP();


	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
		}
	}
}

8、测试结果

相关推荐
安科士andxe8 小时前
深入解析|安科士1.25G CWDM SFP光模块核心技术,破解中长距离传输痛点
服务器·网络·5g
YJlio11 小时前
1.7 通过 Sysinternals Live 在线运行工具:不下载也能用的“云端工具箱”
c语言·网络·python·数码相机·ios·django·iphone
CTRA王大大11 小时前
【网络】FRP实战之frpc全套配置 - fnos飞牛os内网穿透(全网最通俗易懂)
网络
今天只学一颗糖11 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
testpassportcn12 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
通信大师12 小时前
深度解析PCC策略计费控制:核心网产品与应用价值
运维·服务器·网络·5g
Tony Bai14 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
消失的旧时光-194314 小时前
从 0 开始理解 RPC —— 后端工程师扫盲版
网络·网络协议·rpc
游乐码14 小时前
c#变长关键字和参数默认值
学习·c#
叫我龙翔15 小时前
【计网】从零开始掌握序列化 --- JSON实现协议 + 设计 传输\会话\应用 三层结构
服务器·网络·c++·json