一、数据结构
首先根据IP数据包格式(图下图)定义IP数据包头的数据结构
typedef struct tagIPHDR // IP数据包头部
{
    u_char VIHL;        // 版本号(4)+头长度(4)
    u_char TOS;         // 服务类型(8)
    short TotLen;       // 总长度(16)
    short ID;           // 标识(16)
    short FlagOff;      // 标志(3)+片偏移(13)
    u_char TTL;         // 生存时间TTL(8)
    u_short CheckSum;   // 头部校验和(16)
    in_addr iaSrc;      // 源IP地址(32)
    in_addr iaDst;      // 目标IP地址(32)   
} IPHDR, *PIPHDR;
然后根据ICMP回送请求与应答报文格式定义ICMP的数据结构
typedef struct tagICMPHDR   // ICMP回送请求与应带ICMP报文
{
    u_char Type;        // 类型(8)
    u_char Code;        // 代码(8)
    u_short Checksum;   // 校验和(16)
    u_short ID;         // 标识符(16)
    u_short Seq;        // 序号(16)
    char Data;          // 任选数据
} ICMPHDR, *PICMPHDR;
 
然后分别定义请求回送的数据长度
#define REQ_DATASIZE 32
请求回送的数据结构
typedef struct tagECHOREQUEST
{
    ICMPHDR icmpHdr;
    DWORD dwTime;
    char cData[REQ_DATASIZE];
} ECHOREQUEST, *PECHOREQUEST;
ICMP回送应答的数据结构
typedef struct tagECHOREPLY
{
    IPHDR ipHdr;
    ECHOREQUEST echoRequest;
    char cFiller[256];
} ECHOREPLY, *PECHOREPLY;
 
二、函数实现
(1)SendEchoRequest
函数功能是发送回送请求数据包,首先定义三个静态变量
    static ECHOREQUEST echoReq;    // 回送请求数据结构
     static nId = 1;        // 标识符
     static nSeq = 1;    // 序号然后填写回送请求信息
    echoReq.icmpHdr.Type = ICMP_ECHOREQ; // 类型
     echoReq.icmpHdr.Code = 0;             // 代码
     echoReq.icmpHdr.Checksum = 0;         // 校验和
     echoReq.icmpHdr.ID = nId++;             // 标识符    echoReq.icmpHdr.Seq = nSeq++;         // 序号
 填写要发送的数据
     for (i = 0; i < REQ_DATASIZE; i++)
     {
         echoReq.cData[i] = ' ' + i;
     }保存发送时间
     echoReq.dwTime = GetTickCount(); 数据存入包中并计算校验和
     echoReq.icmpHdr.Checksum = in_chsum((u_short*)&echoReq, sizeof(ECHOREQUEST));发送回送请求
     nRet = sendto(s,
         (LPSTR)&echoReq,
         sizeof(ECHOREQUEST),
         0,
         (LPSOCKADDR)lpstToAddr,
         sizeof(SOCKADDR_IN));(2)RecvEchoReply
函数功能为接收回送应答数据
 
 
 DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL)
 {
     ECHOREPLY echoReply;    // 回送应答数据结构
     int nRet;
     int nAddrLen = sizeof(sockaddr_in);
     // 接受回送应答
     nRet = recvfrom(s,
                     (LPSTR)&echoReply,
                     sizeof(ECHOREPLY),
                     0,
                     (LPSOCKADDR)lpsaFrom,
                     &nAddrLen);
     // 检查返回的值
     if (nRet == SOCKET_ERROR)
     {
         ReportError("recvfrom()");
     }
     *pTTL = echoReply.ipHdr.TTL;    // 取得TTL值
     return (echoReply.echoRequest.dwTime);    // 返回所用时间
 }
 (3)WaitForEchoReply函数功能:等待套接子s是否有数据可读
 int WaitForEchoReply(SOCKET s)
 {
     timeval Timeout;
     fd_set readfds;
     readfds.fd_count = 1;
     readfds.fd_array[0] = s;
     Timeout.tv_sec = 5;
     Timeout.tv_usec = 0;
     return (select(1, &readfds, NULL, NULL, &Timeout));
 } 
(3)in_chsum
函数功能计算校验和
u_short in_chsum(u_short *addr, int len)
 {
     register int nLeft = len;
     register u_short *w = addr;
     register u_short answer;
     register int sum = 0;
     while (nLeft > 1)
     {
         sum += *w++;
         nLeft -= 2;
     }
     if (nLeft == 1)
     {
         u_short u = 0;
         *(u_char*)(&u) = *(u_char*)w;
         sum += u;
     }
     sum = (sum >> 16) + (sum & 0xffff);
     sum += (sum >> 16);
     answer = ~sum;
     return (answer);
 }(4)main函数的实现
第一步:定义Winsock数据结构wsaData并新建版本号1.1
第二步:调用WSAStartup初始化wsaData
第三步:调用Ping函数
第四步:调用WSACleanup释放Winsock
void main(int argc, char **argv)
 {
     WSADATA wsaData;
     WORD wVersionRequested = MAKEWORD(1, 1); // Winsock1.1
     int nRet;
     
     // 命令行参数检查
     if (argc != 2)
     {
         fprintf(stderr, "/nUsage: ping hostname/n");
         return;
     }
     // 初始化Winsock
     nRet = WSAStartup(wVersionRequested, &wsaData);
     if (nRet)
     {
         fprintf(stderr, "/nError initializing Winsock/n");
         return;
     }
     if (wsaData.wVersion != wVersionRequested)
     {
         fprintf(stderr, "/nWinsock version not supported/n");
         return;
     }
     // 调用ping函数
     Ping(argv[1]);
     //Ping("www.sina.com");
     // 释放Winsock
     WSACleanup();
 }(5)Ping
函数功能:实现ping功能
定义函数用到的数据
    SOCKET rawSocket;    // 原始套接字
     LPHOSTENT lpHost;    // 主机信息
     sockaddr_in saDest;    // 目的地址
     sockaddr_in saSrc;    // 源地址
     DWORD dwTimeSent;    // 发送时间
     DWORD dwElapsed;    // 延迟时间然后创建一个原始套接字
创建一个原始套接口,协议为ICMP协议
     rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);根据用户输入的目的地址获取
lpHost = gethostbyname(pstrHost);
设置目标套接口地址
     saDest.sin_addr.s_addr = *((u_long FAR*)(lpHost->h_addr));
     saDest.sin_family = AF_INET;
     saDest.sin_port = 0; 输出ping程序的提示信息
     printf("/nPinging %s [%s] with %d bytes of data:/n",
                 pstrHost,
                 inet_ntoa(saDest.sin_addr),
                 REQ_DATASIZE); 发送ICMP回送请求
         SendEchoRequest(rawSocket, &saDest);
 使用select()等待接收回送的数据        WaitForEchoReply(rawSocket);
 接收应答
         dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL);
 计算传输时间,并输出提示信息
         dwElapsed = GetTickCount() - dwTimeSent;答应应答信息
        printf("/nReply from: %s: bytes=%d time=%ldms TTL=%d",
                 inet_ntoa(saSrc.sin_addr),
                 REQ_DATASIZE,
                 dwElapsed,
                 cTTL);
     }
     // 关闭套接字
     nRet = closesocket(rawSocket);附:程序源代码
// ping.h
// 在该头文件中定义了IP和ICMP协议头的结构
#pragma pack(1)
#define ICMP_ECHOREPLY 0
#define ICMP_ECHOREQ 8
typedef struct tagIPHDR // IP数据包头部
{
    u_char VIHL;        // 版本号(4)+头长度(4)
    u_char TOS;         // 服务类型(8)
    short TotLen;       // 总长度(16)
    short ID;           // 标识(16)
    short FlagOff;      // 标志(3)+片偏移(13)
    u_char TTL;         // 生存时间TTL(8)
    u_short CheckSum;   // 头部校验和(16)
    in_addr iaSrc;      // 源IP地址(32)
    in_addr iaDst;      // 目标IP地址(32)   
} IPHDR, *PIPHDR;
typedef struct tagICMPHDR   // ICMP回送请求与应带ICMP报文
{
    u_char Type;        // 类型(8)
    u_char Code;        // 代码(8)
    u_short Checksum;   // 校验和(16)
    u_short ID;         // 标识符(16)
    u_short Seq;        // 序号(16)
    char Data;          // 任选数据
} ICMPHDR, *PICMPHDR;
// 请求回送的数据长度
#define REQ_DATASIZE 32
// ICMP回送请求的数据结构
typedef struct tagECHOREQUEST
{
    ICMPHDR icmpHdr;
    DWORD dwTime;
    char cData[REQ_DATASIZE];
} ECHOREQUEST, *PECHOREQUEST;
// ICMP回送应答
typedef struct tagECHOREPLY
{
    IPHDR ipHdr;
    ECHOREQUEST echoRequest;
    char cFiller[256];
} ECHOREPLY, *PECHOREPLY;
#pragma pack()
 
// ping.cpp
// 实现简易ping功能
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include "ping.h"
void Ping(LPCSTR pstrHost);
void ReportError(LPCSTR pstrFrom);
int WaitForEchoReply(SOCKET s);
u_short in_chsum(u_short *addr, int len);
// ICMP 回送请求和应答函数声明
int SendEchoRequest(SOCKET, LPSOCKADDR_IN);
DWORD RecvEchoReply(SOCKET, LPSOCKADDR_IN, u_char *);
// 主程序
void main(int argc, char **argv)
{
    WSADATA wsaData;
    WORD wVersionRequested = MAKEWORD(1, 1); // Winsock1.1
    int nRet;
    
    // 命令行参数检查
    if (argc != 2)
    {
        fprintf(stderr, "/nUsage: ping hostname/n");
        return;
    }
    // 初始化Winsock
    nRet = WSAStartup(wVersionRequested, &wsaData);
    if (nRet)
    {
        fprintf(stderr, "/nError initializing Winsock/n");
        return;
    }
    if (wsaData.wVersion != wVersionRequested)
    {
        fprintf(stderr, "/nWinsock version not supported/n");
        return;
    }
    // 调用ping函数
    Ping(argv[1]);
    //Ping("www.sina.com");
    // 释放Winsock
    WSACleanup();
}
void Ping(LPCSTR pstrHost)
{
    SOCKET rawSocket;   // 原始套接字
    LPHOSTENT lpHost;   // 主机信息
    sockaddr_in saDest; // 目的地址
    sockaddr_in saSrc;  // 源地址
    DWORD dwTimeSent;   // 发送时间
    DWORD dwElapsed;    // 延迟时间
    u_char cTTL;        
    int nLoop;
    int nRet;
    // 创建一个原始套接口
    rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (rawSocket == SOCKET_ERROR)
    {
        ReportError("socket()");
        return;
    }
    lpHost = gethostbyname(pstrHost);
    if (lpHost == NULL)
    {
        fprintf(stderr, "/nHost not found: %s/n", pstrHost);
        return;
    }
    // 设置目标套接口地址
    saDest.sin_addr.s_addr = *((u_long FAR*)(lpHost->h_addr));
    saDest.sin_family = AF_INET;
    saDest.sin_port = 0;
    // 输出ping程序的提示信息
    printf("/nPinging %s [%s] with %d bytes of data:/n",
                pstrHost,
                inet_ntoa(saDest.sin_addr),
                REQ_DATASIZE);
    // 控制ping执行的次数
    for (nLoop = 0; nLoop < 4; nLoop++)
    {
        // 发送ICMP回送请求
        SendEchoRequest(rawSocket, &saDest);
        // 使用select()等待接收回送的数据
        nRet = WaitForEchoReply(rawSocket);
        if (nRet == SOCKET_ERROR)
        {
            ReportError("select()");
            break;
        }
        if (!nRet)
        {
            printf("/nTimeOut/n");
            break;
        }
        // 接收应答
        dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL);
        // 计算传输时间,并输出提示信息
        dwElapsed = GetTickCount() - dwTimeSent;
        printf("/nReply from: %s: bytes=%d time=%ldms TTL=%d",
                inet_ntoa(saSrc.sin_addr),
                REQ_DATASIZE,
                dwElapsed,
                cTTL);
    }
    printf("/n");
    // 关闭套接字
    nRet = closesocket(rawSocket);
    if (nRet == SOCKET_ERROR)
    {
        ReportError("closesocket()");
    }
}
int SendEchoRequest(SOCKET s, LPSOCKADDR_IN lpstToAddr)
{
    static ECHOREQUEST echoReq; // 回送请求数据结构
    static nId = 1;     // 标识符
    static nSeq = 1;    // 序号
    int nRet;
    int i;
    // 填写回送请求信息
    echoReq.icmpHdr.Type = ICMP_ECHOREQ; // 类型
    echoReq.icmpHdr.Code = 0;            // 代码
    echoReq.icmpHdr.Checksum = 0;        // 校验和
    echoReq.icmpHdr.ID = nId++;          // 标识符
    echoReq.icmpHdr.Seq = nSeq++;        // 序号
    // 填写要发送的数据
    for (i = 0; i < REQ_DATASIZE; i++)
    {
        echoReq.cData[i] = ' ' + i;
    }
    // 保存发送时间
    echoReq.dwTime = GetTickCount();
    // 数据存入包中并计算校验和
    echoReq.icmpHdr.Checksum = in_chsum((u_short*)&echoReq, sizeof(ECHOREQUEST));
    // 发送回送请求
    nRet = sendto(s,
        (LPSTR)&echoReq,
        sizeof(ECHOREQUEST),
        0,
        (LPSOCKADDR)lpstToAddr,
        sizeof(SOCKADDR_IN));
    if (nRet == SOCKET_ERROR)
    {
        ReportError("sendto()");
    }
    return nRet;
}
DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL)
{
    ECHOREPLY echoReply;    // 回送应答数据结构
    int nRet;
    int nAddrLen = sizeof(sockaddr_in);
    // 接受回送应答
    nRet = recvfrom(s,
                    (LPSTR)&echoReply,
                    sizeof(ECHOREPLY),
                    0,
                    (LPSOCKADDR)lpsaFrom,
                    &nAddrLen);
    // 检查返回的值
    if (nRet == SOCKET_ERROR)
    {
        ReportError("recvfrom()");
    }
    *pTTL = echoReply.ipHdr.TTL;    // 取得TTL值
    return (echoReply.echoRequest.dwTime);  // 返回所用时间
}
void ReportError(LPCSTR pstrFrom)
{
    fprintf(stderr, "/n %d error: /n", WSAGetLastError());
}
// 等待套接子s是否有数据可读
int WaitForEchoReply(SOCKET s)
{
    timeval Timeout;
    fd_set readfds;
    readfds.fd_count = 1;
    readfds.fd_array[0] = s;
    Timeout.tv_sec = 5;
    Timeout.tv_usec = 0;
    return (select(1, &readfds, NULL, NULL, &Timeout));
}
u_short in_chsum(u_short *addr, int len)
{
    register int nLeft = len;
    register u_short *w = addr;
    register u_short answer;
    register int sum = 0;
    while (nLeft > 1)
    {
        sum += *w++;
        nLeft -= 2;
    }
    if (nLeft == 1)
    {
        u_short u = 0;
        *(u_char*)(&u) = *(u_char*)w;
        sum += u;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    answer = ~sum;
    return (answer);
}