欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

c语言实现ping程序

程序员文章站 2022-05-10 13:08:00
...

基础知识

1、ICMP协议
互联网控制信息协议(Internet CONTROLM essage Protocol),用于错误报告和调试。该协议是TCP/IP 协议集中的一个子协议,属于网络层协议,主要用于在主机与路由器之间传递控制信息,包括报告错误、交换受限控制和状态信息等。当遇到IP 数据无法访问目标、IP 路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。我们可以通过Ping 命令发送ICMP回应请求消息并记录收到ICMP回应回复消息,通过这些消息来对网络或主机的故障提供参考依据。常见ICMP报文有响应请求报文,目标不可到达、源抑制和超时报文,此外还有时间戳报文。

2、Ping 工作原理
Ping的原理就是首先建立通道,然后发送包,对方接受后返回信息,这个包至少包括以下内容:发送的时候,包的内容包括对方的ip地址和自己的地址,还有序列数;回送的时候包括双方地址,还有时间等,主要是接受方在都是在操作系统内核里做好的,时刻在监听。Ping程序生成一个icmp“回送请求”,将其发送给目的主机。通过检测是否可以收到目标主机的应答,便可以知道网络的连通性。

3.ICMP报文格式

4.IP报文格式

5、WinSock原始套接字的使用方法与API
函数Winsock 原始套接字编程过程中,服务器端/ 客户端的编程都按照以下步骤:
初始化套接字(WSAStartup)
创建套接字(socket 或WSASocket)
向服务器通信(sendto/recvfrom)
关闭套接字(closesocket)
结束使用套接字(WSACleanup)

6、三种WinSock地址结构
① 用的Winsock 地址结构sockaddr ,针对各种通信域的套接字,存储它们的地址信息。
② 专门针对Internet 通信域的Winsock 地址结构sockaddr_in
③ 专用于存储IP 地址的结构in_addr

7、编程时,需要用到一些windows 函数,说明如下:
(1)int WSAStartup(WORD wVersionRequested,LPWSADA TA lpWSAData);
函数说明:为了在应用程序当中调用任何一个Winsock API 函数,首先第一件事情就是必须通过WSAStartup 函数完成对Winsock 服务的初始化, 因此需要调用WSAStartup 函数。使用Socket的程序在使用Socket 之前必须调用WSAStartup 函数。该函数的第一个参数指明程序请求使用的Socket 版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket 的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket 版本来搜索相应的Socket 库,然后绑定找到的Socket 库到该应用程序中。以后应用程序就可以调用所请求的Socket 库中的其它Socket 函数了。
(2)SOCKET socket( int af, int type, int protocol );
函数说明:应用程序调用socket 函数来创建一个能够进行网络通信的套接字。
第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP 协议族,该参数置AF_INET;第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM 、数据报套接字类型为SOCK_DGRAM 、原始套接字SOCK_RAW接口并不适用某种特定的协议去封装它,而是由程序自行处理数据包以及协议首部);第三个参数指定应用程序所使用的通信协议。
(3).int sendto( SOCKET s, const char FAR *buf, int len,int flags, const struct sockaddr FAR *to, int tolen);
函数说明:
返回值:实际发送数据的长度。
parameter s 套接字
buff 待发送数据的缓冲区size 缓冲区长度
Flags 调用方式标志位, 一般为0, 改变Flags,将会改变Sendto 发送的形式
Addr 指针,指向目的套接字的地址
Len addr 所指地址的长度
(4)int recvfrom(SOCKET s, char FAR* buf, int len, int flags,struct sockaddr FAR *from, int FAR *fromlen );
函数说明:recvfrom( )用来接收远程主机经指定的socket 传来的数据,并把数据传到由参数buf 指向的内存空间,参数len 为可接收数据的最大长度.参flags 一般设0,其他数值定义参考recv(). 参数from 用来指定欲传送的网络地址,结构sockaddr 请参考bind() 函数.参数fromlen 为sockaddr 的结构长度.

算法设计

要实现ping 程序,需要实现以下步骤:
(1) 创建协议类型为IPPROTO_ICMP 的原始套接字,设置套接字属性。
(2) 创建并初始化ICMP 封包。
(3) 调用sendto 函数向远程主机发送ICMP 请求。
(4) 调用recfrom 函数接受ICMP 响应。
Ping程序的设计与实现大致可分为四个模块,分别是:初始化模块、功能控制模块、ping 模块、mian 测试模块。
c语言实现ping程序
1.初始化模块:该模块用于定义及初始化各个全局变量,为winsock加载winsock 体。主要包括定义IP 首部格式、定义ICMP首部格式、定义ICMP回应请求、定义ICMP回应答复。
c语言实现ping程序
2.功能控制模块: 该模块是被其他模块调用,其功能包括计算校验和、发送回应请求函数、接收应答回复并进行解析、等待回应答复。
计算校验和
c语言实现ping程序
发送请求
c语言实现ping程序
接收应答回复并进行解析
c语言实现ping程序
等待回应答复
c语言实现ping程序
3.Ping 模块功能模块 该模块是本程序的核心模块,调用其他模块实现其功
能,进而实现Ping 的功能。
c语言实现ping程序
4.main()函数模块:向指定的域名或IP 地址发送Echo请求报文;根据响应报文显示出Ping 的结果
c语言实现ping程序

代码

#include <stdio.h> 
#include <stdlib.h> 
#include <winsock.h> 
#include<conio.h> 
#pragma comment(lib, "ws2_32.lib")// 导入库文件 
#define ICMP_ECHOREPLY 0 //ICMP 回应答复 
#define ICMP_ECHOREQ 8 //ICMP 回应请求 
#define REQ_DATASIZE 32 // 请求数据报大小
#include <iostream>
using namespace std; 

//定义 IP 首部格式
typedef struct IPHeader 
{ 
    u_char VIHL; // 版本和首部长度 
    u_char ToS; //服务类型 
    u_short TotalLen; // 总长度 
    u_short ID; // 标识号 
    u_short Frag_Flags; //片偏移量 
    u_char TTL; // 生存时间 
    u_char Protocol; // 协议 
    u_short Checksum; //首部校验和
    struct in_addr SrcIP; // 源 IP 地址 
    struct in_addr DestIP; // 目的地址
}IPHDR, *PIPHDR; 

//定义 ICMP 首部格式
typedef struct ICMPHeader 
{ 
    u_char Type; //类型 
    u_char Code; //代码 
    u_short Checksum; //首部校验和 
    u_short ID; // 标识 
    u_short Seq; //***
    char Data; //数据
}ICMPHDR, *PICMPHDR; 

//定义 ICMP 回应请求
typedef struct ECHOREQUEST 
{ 
    ICMPHDR icmpHdr; 
    DWORD dwTime; 
    char cData[REQ_DATASIZE]; 
}ECHOREQUEST, *PECHOREQUEST; 

//定义 ICMP 回应答复
typedef struct ECHOREPLY 
{ 
    IPHDR ipHdr; 
    ECHOREQUEST echoRequest; 
    char cFiller[256]; 
}ECHOREPLY,*PECHOREPLY;

//计算校验和
u_short checksum(u_short *buffer, int len) 
{ 
    register int nleft = len; 
    register u_short *w = buffer; 
    register u_short answer; 
    register int sum = 0; 
    //使用 32 位累加器 ,进行 16 位的反馈计算
    while ( nleft > 1 ) 
    { 
        sum += *w++; 
        nleft -= 2; 
    } 
    //补全奇数位
    if ( nleft == 1 ) 
    { 
        u_short u = 0; 
        *(u_char *)(&u) = *(u_char*)w; 
        sum += u; 
    }
    //将反馈的 16 位从高位移到低位
    sum = (sum >> 16) + (sum & 0xffff); 
    sum += (sum >> 16); 
    answer = ~sum; 
    return (answer); 
} 

//发送回应请求函数
int SendEchoRequest(SOCKET s, struct sockaddr_in *lpstToAddr) 
{ 
    static ECHOREQUEST echoReq; 
    static int nId = 1; 
    static int nSeq = 1; 
    int nRet; 
    //填充回应请求消息
    echoReq.icmpHdr.Type = ICMP_ECHOREQ; 
    echoReq.icmpHdr.Code = 0; 
    echoReq.icmpHdr.Checksum = 0; 
    echoReq.icmpHdr.ID = nId++; 
    echoReq.icmpHdr.Seq = nSeq++; 
    //填充要发送的数据
    for (nRet = 0; nRet < REQ_DATASIZE; nRet++) 
    { 
        echoReq.cData[nRet] = '1' + nRet; 
    } 
    //存储发送的时间
    echoReq.dwTime = GetTickCount(); 
    //计算回应请求的校验和
    echoReq.icmpHdr.Checksum = checksum((u_short*)&echoReq, sizeof(ECHOREQUEST)); 
    //发送回应请求
    nRet = sendto(s,(LPSTR)&echoReq,sizeof(ECHOREQUEST),0,(struct sockaddr*)lpstToAddr,sizeof(SOCKADDR_IN)); 
    if (nRet == SOCKET_ERROR) 
    { 
        printf("send to() error:%d\n", WSAGetLastError()); 
    } 
    return (nRet); 
} 

//接收应答回复并进行解析
DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL) 
{ 
    ECHOREPLY echoReply; 
    int nRet; 
    int nAddrLen = sizeof(struct sockaddr_in); 
    //接收应答回复
    nRet = recvfrom(s,(LPSTR)&echoReply,sizeof(ECHOREPLY),0,(LPSOCKADDR)lpsaFrom,&nAddrLen); 
    //检验接收结果
    if (nRet == SOCKET_ERROR) 
    { 
        printf("recvfrom() error:%d\n",WSAGetLastError()); 
    } 
    //记录返回的 TTL 
    *pTTL = echoReply.ipHdr.TTL; 
    //返回应答时间
    return(echoReply.echoRequest.dwTime); 
} 

//等待回应答复 ,使用 select 模型
int WaitForEchoReply(SOCKET s) 
{
    struct timeval timeout;
    fd_set readfds;
    readfds.fd_count = 1;
    readfds.fd_array[0] = s;
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    return(select(1, &readfds, NULL, NULL, &timeout));
} 

//PING 功能实现
void Ping(char *pstrHost,bool logic) 
{ 
    char c; 
    SOCKET rawSocket; 
    LPHOSTENT lpHost; 
    struct sockaddr_in destIP; 
    struct sockaddr_in srcIP; 
    DWORD dwTimeSent; 
    DWORD dwElapsed; 
    u_char cTTL; 
    int nLoop,k=4; 
    int nRet,minimum=100000,maximum=0,average=0; 
    int sent=4,reveived=0,lost=0; 
    //创建原始套接字 ,ICMP 类型
    rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    // 第二个注释函数 socket 
    if (rawSocket == SOCKET_ERROR) 
    { 
        printf("socket() error:%d\n", WSAGetLastError()); 
        return; 
    } 
    //检测目标主机
    lpHost = gethostbyname(pstrHost); 
    if (lpHost==NULL) 
    { 
        printf("Host not found:%s\n", pstrHost); 
        return; 
    } 
    //设置目标机地址 
    destIP.sin_addr.s_addr = *((u_long FAR*)(lpHost->h_addr)); // 设置目标 IP 
    destIP.sin_family = AF_INET; //地址规格
    destIP.sin_port = 0; 
    //提示开始进行 PING 
    printf("\nPinging %s [%s] with %d bytes of data:\n",pstrHost,inet_ntoa(destIP.sin_addr),REQ_DATASIZE);
    //发起多次 PING 测试
    for (nLoop=0; nLoop<k; nLoop++){ 
        if (logic) k=k+1; 
        //发送 ICMP 回应请求 
        SendEchoRequest(rawSocket, &destIP); 
        //等待回复的数据
        nRet = WaitForEchoReply(rawSocket); 
        if(nRet == SOCKET_ERROR) 
        { 
            printf("select() error:%d\n", WSAGetLastError()); 
            break; 
        } 
        if (!nRet) 
        { 
            lost++; 
            printf("\nRequest time out."); 
            continue; 
        } 
        //接收回复
        dwTimeSent = RecvEchoReply(rawSocket, &srcIP, &cTTL); 
        reveived++; 
        //计算花费的时间
        dwElapsed = GetTickCount() - dwTimeSent; 
        if(dwElapsed>maximum) maximum=dwElapsed; 
        if(dwElapsed<minimum) minimum=dwElapsed; 
        average+=dwElapsed; 
        printf("\nReply from %s: bytes = %d time = %ldms TTL = %d", 
            inet_ntoa(srcIP.sin_addr),REQ_DATASIZE,dwElapsed,cTTL); 
        if(_kbhit()) /* Use _getch to throw key away. */ 
        { 
            if ((c=_getch())==0x2) //crrl -b 
            break; 
        } else 
        Sleep(1000); 
    } 
    printf("\n\n"); 
    printf("Ping statistics for %s:\n",inet_ntoa(srcIP.sin_addr)); 
    printf(" Packets: Sent = %d, Received = %d, Lost = %d (%.f%% loss),\n", 
        sent,reveived,lost,(float)(lost*1.0/sent)*100); 
    if(lost==0) 
    { 
        printf("Approximate round trip times in milli-seconds:\n"); 
        printf(" Minimum = %dms, Maximum = %dms, Average = %dms\n",minimum,maximum,average/sent); 
    } 
    printf("\n\n"); 
    nRet = closesocket(rawSocket); 
    if (nRet == SOCKET_ERROR) 
    { 
        printf("closesocket() error:%d\n", WSAGetLastError()); 
    } 
} 

//主程序
void main() 
{ 
    printf("Welcome to the Ping Test\n"); 
    while(1) 
    { 
        WSADATA wsd;// 检测输入的参数 
        //初始化 Winsock 
        if(WSAStartup(MAKEWORD(1, 1), &wsd) != 0){// 第一个函数说明 WSAStartup() 
            printf(" 加载 Winsock 失败 !\n"); 
        } 
        char opt1[100]; 
        char *ptr=opt1; 
        bool log=false; 
        printf("Ping "); 
        cin.getline(opt1,100,'\n');//ping 的地址 字符串
        if(strstr(opt1, "-t")!=NULL) 
        { 
            log=true; 
            strncpy(ptr,opt1+0,strlen(opt1)-3);// 把原字符串的最后三位截取 
            ptr[strlen(opt1)-2]=0; 
            //printf("%s", ptr); 
        } 
        //开始 PING 
        Ping(ptr,log); 
        //程序释放 Winsock 资源
        WSACleanup(); 
    } 
} 

c语言实现ping程序

相关标签: c ping