Ping程序的实现(计网课设)

写在前面

本题目为Ping程序的实现,为计算机网络课程设计,采用VS运行,忽略了一些报错,如C4996、C4233,程序能正常运行,可供同样需要完成课设的小伙伴们参考使用(ps:俺也是多方参考的,谢谢巨人的肩膀,😀)


一、课设要求

利用c语言实现的Ping命令,能用于测试一个主机到另一个主机间的联通情况,程序还提供了几个选项以实现不同的功能。
(1)实现Ping功能。程序能实现基本的Ping操作,发送ICMP回显请求报文,接收显应答报文。
(2)能输出指定条数的记录。程序提供了“-n”选项,用以输出指定条数的记录。
(3)能按照指定大小输出每条记录。程序提供了“datasize”选项,用以指定输出的数据报的大小。
(4)能输出用户帮助。程序提供了用户帮助,显示程序提供的选项以及选项格式等。

二、实现原理(俺认为是这样)

利用socket原始套接字(可自定义IP)实现发送/监听ICMP报文,实现基本的Ping功能,向目标主机发送ICMP回应请求,然后等待目标主机的ICMP回应答复。通过这种方式,它可以判断目标主机是否在线,以及网络延迟(即ping值)。

1.运行截图

功能1为默认Ping4次,数据报大小为32字节。其余功能均可指定次数或数据报大小,不做赘述。此处截图展示为功能4,指定次数和数据报大小。(也可直接Ping IP地址,俺懒得截图,可自己测试)

2.完整代码

#include <stdio.h>                  //标准输入输出函数 
#include <stdlib.h>                 //实用程序库函数
#include <winsock.h>                //网络编程socket相关部分API 
#include<conio.h>                   //通用输入输出库
#include<stdbool.h>                 //bool:true\false
#pragma warning(disable:4996)
#pragma warning(disable:4233)
#pragma comment(lib, "ws2_32.lib")  //导入库文件:Windows Sockets应用程序接口, 用于支持Internet和网络应用程序
#define ICMP_ECHOREPLY 0            //ICMP 回应答复类型码为0 
#define ICMP_ECHOREQ 8              //ICMP 回应请求类型码为8 
#define REQ_DATASIZE 32             //默认请求数据报大小
#define sent 4                      //默认已发送条数

int sentnum = sent;
int PagSize = REQ_DATASIZE;

//定义 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 REQUEST
{
    ICMPHDR icmpHdr;
    DWORD dwTime;
    char cData[REQ_DATASIZE];
}REQUEST, * PECHOREQUEST;

//定义 ICMP 回应答复
typedef struct REPLY
{
    IPHDR ipHdr;
    REQUEST echoRequest;
    char cFiller[256];
}REPLY, * 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;//每16位相加,因此字节数-2
    }
    //补全奇数位
    if (nleft == 1)
    {
        u_short u = 0;
        *(u_char*)(&u) = *(u_char*)w;
        sum += u;
    }
    //将反馈的 16 位从高位移到低位
    sum = (sum >> 16) + (sum & 0xffff);     //高16位和低16位相加,sum & 0xffff将高16位置0
    sum += (sum >> 16);                     //将低16位与溢出值相加
    answer = ~sum;                          //取反码
    return (answer);
}

//发送回应请求函数
int SendRequest(SOCKET s, struct sockaddr_in* lpstToAddr)
{
    static REQUEST 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 < PagSize; nRet++)
    {
        echoReq.cData[nRet] = '1' + nRet;
    }
    //存储发送的时间
    echoReq.dwTime = GetTickCount();
    //计算回应请求的校验和
    echoReq.icmpHdr.Checksum = checksum((u_short*)&echoReq, sizeof(REQUEST));
    //发送回应请求
    nRet = sendto(s, (LPSTR)&echoReq, sizeof(REQUEST), 0, (struct sockaddr*)lpstToAddr, sizeof(SOCKADDR_IN));
    if (nRet == SOCKET_ERROR)
    {
        printf("send to() error:%d\n", WSAGetLastError());
    }
    return (nRet);
}

//接收应答回复并进行解析
DWORD ReceiveReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char* pTTL)
{
    REPLY echoReply;
    int nRet;
    int nAddrLen = sizeof(struct sockaddr_in);
    //接收应答回复
    nRet = recvfrom(s, (LPSTR)&echoReply, sizeof(REPLY), 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 WaitReply(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;    //设计时间为1s,超过1s为超时
    //调用select函数可以确定一个或多个套接字的状态,判断套接字上是否有数据,或者能否向一个套接字写入数据。
    return(select(1, &readfds, NULL, NULL, &timeout));//0:超时;大于0的值时,表示SOCKET数;失败时返回SOCKET_ERROR
}

//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;
    int nRet, mintime = 100000, maxtime = 0, averagetime = 0;
    int reveived = 0, lost = 0;
    rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);            //创建原始套接字 ,ICMP 类型
    // 第二个注释函数 socket 
    if (rawSocket == SOCKET_ERROR)
    {
        printf("socket() error:%d\n", WSAGetLastError());
        return;
    }
    //检测目标主机
    lpHost = gethostbyname(pstrHost);       //返回对应于给定主机名的包含主机名字和地址信息的hostent结构的指针
    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), PagSize);
    //发起多次 PING 测试
    for (nLoop = 0; nLoop < sentnum; nLoop++) {
        if (logic) sentnum = sentnum + 1;
        //发送 ICMP 回应请求 
        SendRequest(rawSocket, &destIP);
        //等待回复的数据
        nRet = WaitReply(rawSocket);
        if (nRet == SOCKET_ERROR)
        {
            printf("select() error:%d\n", WSAGetLastError());
            break;
        }
        if (!nRet)
        {
            lost++;
            printf("\nRequest time out.");
            continue;
        }
        //接收回复
        dwTimeSent = ReceiveReply(rawSocket, &srcIP, &cTTL);
        reveived++;
        //计算花费的时间
        dwElapsed = GetTickCount() - dwTimeSent;
        if (dwElapsed > maxtime) maxtime = dwElapsed;
        if (dwElapsed < mintime) mintime = dwElapsed;
        averagetime += dwElapsed;
        printf("\nReply from %s: bytes = %d time = %ldms TTL = %d",
            inet_ntoa(srcIP.sin_addr), PagSize, 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",
        sentnum, reveived, lost, (float)(lost * 1.0 / sentnum) * 100);
    if (lost == 0)
    {
        printf("Approximate round trip times in milli-seconds:\n");
        printf(" Mintime = %dms, Maxtime = %dms, Averagetime = %dms\n", mintime, maxtime, averagetime / sentnum);
    }
    printf("\n\n");
    nRet = closesocket(rawSocket);
    if (nRet == SOCKET_ERROR)
    {
        printf("closesocket() error:%d\n", WSAGetLastError());
    }
}

/*显示信息函数*/
void UserHelp()
{
    printf("\t\t\t使用说明: ping 域名或ip地址\n");
    printf("\t\t\t功能1  Ping\n");
    printf("\t\t\t功能2  输出指定条数的记录\n");
    printf("\t\t\t功能3  按指定大小输出每条记录(最小32,最大1024)\n");
    printf("\t\t\t功能4  按指定大小输出指定条数的记录\n");
    printf("\t\t\t功能0  退出程序\n");
    printf("\n\n");
}


//主程序
void main()
{
    while (1)
    {
        WSADATA wsd;                    // 检测输入的参数 
        //初始化 Winsock 
        if (WSAStartup(MAKEWORD(1, 1), &wsd) != 0) {// 第一个函数说明 WSAStartup() 
            printf(" 加载 Winsock 失败 !\n");
        }
        char opt[200];
        char* ptr = opt;
        bool log = false;
        int i;                          //获取用户输入的功能数字
        int n,size;
        printf("欢迎使用Ping程序\n");
        UserHelp();
        printf("请输入你所需要使用的功能:\n	功能");
        scanf("%d", &i);                //i为获取到的用户输入的功能数字
        switch (i) {
        case 1:                         //Ping功能
        {
            printf("Ping  ");
            scanf("%s", opt);          //输入ping 的地址 字符串
            break; }
        case 2:                         //输出指定条数
        {
            printf("Ping  ");
            scanf("%s", opt);
            printf("请输入想要Ping该服务器的次数:");
            scanf("%d", &n);
            sentnum = n;
            break;
        }
        case 3:                         //输出指定大小
        {
            printf("Ping  ");
            scanf("%s", opt);
            printf("请输入想要传送的数据包大小:");
            scanf("%d", &size);
            PagSize = size;
            break;
        }
        case 4:                         //输出指定大小指定条数
        {
            printf("Ping  ");
            scanf("%s", opt);
            printf("请输入想要Ping该服务器的次数:");
            scanf("%d", &n);
            printf("请输入想要传送的数据包大小:");
            scanf("%d", &size);
            sentnum = n;
            PagSize = size;
            break;
        }
        case 0:                         //退出程序
        {
            printf("正在退出程序...");    //1s后退出程序
            Sleep(1000);
            exit(0);
            break;
        }
        default:
        {
            UserHelp();
        }
        }
        //开始 PING 
        Ping(ptr, log);
        //程序释放 Winsock 资源
        WSACleanup();
    }
}

  • 18
    点赞
  • 158
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值