PING是基於ICMP(Internet Control Message Protocol)協定工作的。ICMP報文是封裝在IP包中,工作在網路層。
ICMP包頭的型別欄位有2類,一類是查詢報文,用於檢測通訊鏈路是否正常;一類是差錯報文,通知出錯原因。
回送訊息是用於進行通訊的主機間,判斷所發的封包是否已經成功到達對端的一種訊息,ping命令就是利用這個訊息實現的:可以向對端傳送回送請求的訊息(request,8),也可以接收對端主機傳送回來的應答(reply,0)
Type(8位元) | Code(8位元) |
Checksum(16位元) | |
Identifier(16位元) | |
Sequence Number(16位元) | |
選項資料 |
Type(型別):0代表回送應答,8代表回送請求
Code(程式碼):值為0
Checksum(校驗和)
Identifier(識別符號):Unix系統在實現ping程式時,把ICMP報文中的識別符號欄位設定成傳送程序的PID號
Sequence Number(序號):從0開始,每傳送一次新的請求就加1
抓包:
請求包(Response frame:40 表示應答此請求的reply包是N0.40這一包)
應答包(Request frame:37 表示此包是對N0.37這一請求包的應答)
①執行ping 192.168.x.x命令後,源主機會先構建一個ICMP請求訊息封包(型別為8)
②由ICMP協定將這個封包連同192.168.x.x這一地址一起交給IP層。IP層將以192.168.x.x作為目的地址,本地ip地址作為源地址,協定欄位置為1表示是ICMP協定,再加上一些其他的資訊構建成一個IP封包。
③之後,需要加入MAC頭,如果在本地ARP對映表中能直接查到192.168.x.x這一地址所對應的MAC地址,則可以直接使用。在獲取MAC地址後,由資料鏈路層構建一個資料框,其中,目的地址是剛剛找到的MAC地址,源地址是本機的MAC地址。
④對端裝置收到這個資料框後,將其中的目的MAC地址和自己的本地MAC地址對比,如果符合則接收,不符合就丟棄。如果MAC地址符合,那麼提取出IP封包,經IP層檢查後,再提取出有用的相關資訊給ICMP協定。
⑤識別出這是一個ping請求幀後,將構建一個ICMP應答包(型別為0),經過同樣的流程傳給傳送請求包的主機。
1.設定395相關網路引數及對端,將ip包協定欄位置為1,表示為Internet控制訊息 (ICMP)協定型別。
1 /* CH395相關定義 */ 2 const UINT8 CH395IPAddr[4] = {192,168,1,126}; /* CH395IP地址 */ 3 const UINT8 CH395GWIPAddr[4] = {192,168,1,1}; /* CH395閘道器 */ 4 const UINT8 CH395IPMask[4] = {255,255,255,0}; /* CH395子網掩碼 */ 5 const UINT8 DestIPAddr[4] = {192,168,1,23}; /* 目的IP */ 6 const UINT8 IPRawProto = 1; /* IP包協定型別 */
2.初始化395和socket的引數,並將socket設定為ipraw模式。(關於ipraw模式的介紹和使用可以查閱CH395的官方手冊)
1 /********************************************************************************** 2 * Function Name : InitCH395InfParam 3 * Description : 初始化CH395Inf引數 4 * Input : None 5 * Output : None 6 * Return : None 7 **********************************************************************************/ 8 void InitCH395InfParam(void) 9 { 10 memset(&CH395Inf,0,sizeof(CH395Inf)); /* 將CH395Inf全部清零*/ 11 memcpy(CH395Inf.IPAddr,CH395IPAddr,sizeof(CH395IPAddr)); /* 將IP地址寫入CH395Inf中 */ 12 memcpy(CH395Inf.GWIPAddr,CH395GWIPAddr,sizeof(CH395GWIPAddr)); /* 將閘道器IP地址寫入CH395Inf中 */ 13 memcpy(CH395Inf.MASKAddr,CH395IPMask,sizeof(CH395IPMask)); /* 將子網掩碼寫入CH395Inf中 */ 14 } 15 16 /********************************************************************************** 17 * Function Name : InitSocketParam 18 * Description : 初始化socket 19 * Input : None 20 * Output : None 21 * Return : None 22 **********************************************************************************/ 23 void InitSocketParam(void) 24 { 25 memset(&SockInf,0,sizeof(SockInf)); /* 將SockInf[0]全部清零*/ 26 memcpy(SockInf.IPAddr,DestIPAddr,sizeof(DestIPAddr)); /* 將目的IP地址寫入 */ 27 SockInf.ProtoType = PROTO_TYPE_IP_RAW; /* IP RAW模式 */ 28 SockInf.IPRAWProtoType = IPRawProto; 29 } 30 31 /********************************************************************************** 32 * Function Name : CH395SocketInitOpen 33 * Description : 設定CH395 socket 引數,初始化並開啟socket 34 * Input : None 35 * Output : None 36 * Return : None 37 **********************************************************************************/ 38 void CH395SocketInitOpen(void) 39 { 40 UINT8 i; 41 /* socket 0為IP RAW模式 */ 42 CH395SetSocketDesIP(0,SockInf.IPAddr); /* 設定socket 0目標IP地址 */ 43 CH395SetSocketProtType(0,SockInf.ProtoType); /* 設定socket 0協定型別 */ 44 CH395SetSocketIPRAWProto(0,SockInf.IPRAWProtoType); /* 設定協定欄位 */ 45 46 i = CH395OpenSocket(0); /* 開啟socket 0 */ 47 mStopIfError(i); /* 檢查是否成功 */ /* 檢查是否成功 */ 48 }
3.在socket的接收中斷中,呼叫 CH395GetRecvLength()函數獲取當前緩衝區中的資料長度,在呼叫CH395GetRecvData()函數讀取資料,最後呼叫 CH395IcmpRecvData()函數對收到的ping包處理和分析。
1 /********************************************************************************** 2 * Function Name : CH395SocketInterrupt 3 * Description : CH395 socket 中斷,在全域性中斷中被呼叫 4 * Input : sockindex 5 * Output : None 6 * Return : None 7 **********************************************************************************/ 8 void CH395SocketInterrupt(UINT8 sockindex) 9 { 10 UINT8 sock_int_socket; 11 12 UINT16 len; 13 14 sock_int_socket = CH395GetSocketInt(sockindex); /* 獲取socket 的中斷狀態 */ 15 // printf("SOCK status : %02x\n",sock_int_socket); 16 if(sock_int_socket & SINT_STAT_SENBUF_FREE) /* 傳送緩衝區空閒,可以繼續寫入要傳送的資料 */ 17 { 18 } 19 if(sock_int_socket & SINT_STAT_SEND_OK) /* 傳送完成中斷 */ 20 { 21 IcmpSuc++; 22 } 23 if(sock_int_socket & SINT_STAT_RECV) /* 接收中斷 */ 24 { 25 printf("recv back!\r\n"); 26 len = CH395GetRecvLength(sockindex); /* 獲取當前緩衝區內資料長度 */ 27 if(len == 0)return; 28 if(len > 512) len = 512; 29 CH395GetRecvData(sockindex,len,MyBuffer); /* 讀取資料 */ 30 CH395IcmpRecvData(len,MyBuffer); 31 } 32 if(sock_int_socket & SINT_STAT_CONNECT) /* 連線中斷,僅在TCP模式下有效*/ 33 { 34 35 } 36 if(sock_int_socket & SINT_STAT_DISCONNECT) /* 斷開中斷,僅在TCP模式下有效 */ 37 { 38 } 39 if(sock_int_socket & SINT_STAT_TIM_OUT) /* 超時中斷 */ 40 { 41 } 42 }
4.在CH395PINGInit()函數中,進行引腳初始化、復位395、初始化395、初始化socket、開啟socket等操作。
呼叫 CH395EnablePing(1)函數開啟ping功能(預設開啟)
InitParameter()--- PING引數變數初始化
InitPing()--- PING初始化,生成ping查詢報文
1 /********************************************************************************** 2 * Function Name : main 3 * Description : main主函數 4 * Input : None 5 * Output : None 6 * Return : None 7 **********************************************************************************/ 8 void CH395PINGInit(void) 9 { 10 UINT8 i; 11 Delay_Ms(100); 12 printf("CH395EVT Test Demo\n"); 13 CH395_PORT_INIT(); 14 CH395CMDReset(); /* 復位CH395晶片 */ 15 Delay_Ms(1000); /* 延時1000毫秒,要分開寫,否則無效 */ 16 17 i = CH395CMDGetVer(); /*獲取晶片以及韌體版本號 */ 18 printf("韌體版本號:%02x\n",i); 19 InitCH395InfParam(); /* 初始化CH395相關變數 */ 20 i = CH395Init(); /* 初始化CH395晶片 */ 21 printf("CH395Init:%02x\n",i); 22 mStopIfError(i); 23 24 while(1) 25 { /* 等待乙太網連線成功*/ 26 if(CH395CMDGetPHYStatus() == PHY_DISCONN) /* 查詢CH395是否連線 */ 27 { 28 printf("CH395 DISCONN\n"); 29 Delay_Ms(200); /* 未連線則等待200MS後再次查詢 */ 30 } 31 else 32 { 33 printf("CH395 Connect Ethernet\n"); /* CH395晶片連線到乙太網,此時會產生中斷 */ 34 break; 35 } 36 } 37 InitSocketParam(); // /* 初始化socket相關變數 */ 38 CH395SocketInitOpen();// 39 40 //CH395EnablePing(1);//預設開啟 41 InitParameter(); 42 InitPing(); 43 printf("!!\r\n"); 44 // TIM2_Init(); 45 // Intervalometer_4ms(); 46 }
1 /********************************************************************************** 2 * Function Name : InitParameter 3 * Description : Ping引數初始化 4 * Input : None 5 * Output : None 6 * Return : None 7 **********************************************************************************/ 8 void InitParameter( void ) 9 { 10 UNREACH_COUNT=0; 11 TIMOUT_COUNT=0; 12 SUCRECV_COUNT=0; 13 IcmpCont=0; 14 IcmpSeq=0; 15 IcmpSuc=0;/// 16 icmp_tmp=0; 17 CH395GetIPInf(CH395INF_BUF); 18 } 19 20 /********************************************************************************** 21 * Function Name : InitPing 22 * Description : Ping初始化 23 * Input : None 24 * Output : None 25 * Return : None 26 **********************************************************************************/ 27 void InitPing( void ) 28 { 29 IcmpHeader head; 30 UINT32 check_sum=0; 31 UINT8 i; 32 33 IcmpCont++; 34 IcmpSeq += 1; 35 head.i_type = ICMP_HEAD_TYPE;//8 36 head.i_code = ICMP_HEAD_CODE;//0 37 head.i_id = ICMP_HEAD_ID;//512 38 head.i_seq = ICMP_HEAD_SEQ+IcmpSeq;//100+ 39 memset(head.i_data,0,sizeof(head.i_data)); 40 41 for( i=0;i<ICMP_DATA_BYTES;i++ ){//32 42 if(i<26) 43 head.i_data[i] = i + 'a'; 44 else 45 head.i_data[i] = i + 'a' - 26; 46 if(i%2==0) 47 check_sum += head.i_data[i]<<8; 48 else 49 check_sum += head.i_data[i]; 50 } 51 check_sum += head.i_type<<8; 52 check_sum += head.i_code; 53 check_sum += head.i_id; 54 check_sum += head.i_seq; 55 head.i_cksum = check_sum>>16; 56 head.i_cksum += check_sum&0xffff; 57 head.i_cksum = 0xffff - head.i_cksum; 58 memset(SEND_BUF,0,sizeof(SEND_BUF)); 59 memcpy(SEND_BUF,&head,sizeof(head)); 60 61 SEND_BUF[2] = head.i_cksum >> 8; 62 SEND_BUF[3] = head.i_cksum & 0xff; 63 SEND_BUF[4] = head.i_id >> 8; 64 SEND_BUF[5] = head.i_id & 0xff; 65 SEND_BUF[6] = head.i_seq >> 8; 66 SEND_BUF[7] = head.i_seq & 0xff; 67 }
5.在main函數中,通過 CH395SendData()函數傳送 InitPing()函數生成的40位元組的資料,簡單實現按鍵按一下傳送一次ping請求包。
1 /********************************************************************************** 2 * Function Name : main 3 * Description : main主函數 4 * Input : None 5 * Output : None 6 * Return : None 7 **********************************************************************************/ 8 int main(void) 9 { 10 11 Delay_Init(); 12 USART_Printf_Init(115200); 13 Delay_Ms(100); 14 /* 延時100毫秒 */ 15 GPIO_Toggle_INIT_C(); 16 CH395PINGInit(); 17 // 18 //Delay_Ms(2000); 19 //CH395SendData(0,MyBuffer1,2); 20 //Delay_Ms(20); 21 // CH395SendData( 0,SEND_BUF,40 ); 22 // Delay_Ms(20); 23 // CH395SendData( 0,SEND_BUF,40 ); 24 // 25 // Delay_Ms(20); 26 // CH395SendData( 0,SEND_BUF,40 ); 27 // Delay_Ms(20); 28 // CH395SendData( 0,SEND_BUF,40 ); 29 while(1) 30 { 31 if(CH395_INT_WIRE == 0) 32 { 33 CH395GlobalInterrupt(); 34 } 35 if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == RESET) 36 { 37 Delay_Ms(2); 38 if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == RESET) 39 { 40 printf("PING %d times\r\n",++t); 41 CH395SendData( 0,SEND_BUF,40 ); 42 while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == RESET); 43 } 44 } 45 // CH395SendData( 0,SEND_BUF,40 ); 46 // Delay_Ms(1500); 47 /*查詢狀態執行相應命令*/ 48 // CH395_PINGCmd(); 49 // InitPing(); 50 } 51 }
效果如下,按下按鍵傳送一次ping請求包,對端reply一包資料。(一共發了3次ping_request,成功進入socket接收中斷接收處理回包)
工程程式碼及抓包:https://files.cnblogs.com/files/blogs/808422/CH395_%E4%B8%BB%E5%8A%A8ping.zip?t=1702869711&download=true