Ping程序实现

/*************************************************************************
    > File Name: Ping.c
    > Author: ICKelin
    > Mail: [email protected]
    > Created Time: 2014年12月17日 星期三 04时53分50秒
 ************************************************************************/

#include <stdio.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

#include <cmath>
#include <vector>
#include <numeric>
#include <algorithm>

const size_t MXLEN = 1500;
const size_t DATALEN = 56;
const size_t REQLEN = ICMP_MINLEN + DATALEN;
const size_t NAMELEN = 50;

char dstIP[NAMELEN], dstName[NAMELEN]; //目标IP,目标主机名
char sendBuf[REQLEN], recvBuf[MXLEN];  //发送缓存,接收缓冲

sockaddr_in dstAddr;        //目标地址
uint32_t nrSend, nrRecv;    //发送包数量,接收包数量
std::vector<double> rtts;   //记录每次的rtt

timeval startTime, endTime; //开始时间,结束时间

//计算校验和
uint16_t calChecksum(uint16_t *buf, size_t len)
{
    uint32_t sum = 0;
    while (len > 1) sum += *buf++, len -= 2;
    if (len == 1)
        sum += (*(uint8_t *)buf) << 4 ;

    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += sum >> 16;
    return (uint16_t)~sum;
}

//输出统计信息
void calStat(int signo)
{
    //计时结束,计算花费的时间
    gettimeofday(&endTime, NULL);
    int elapse = 1000 * (endTime.tv_sec - startTime.tv_sec) + (endTime.tv_usec - startTime.tv_usec) / 1000;

    printf("\n--- %s ping statistics ---\n"
           "%u packets transmitted, %u received, %.lf%% packet loss, time %dms\n",
           dstName, nrSend, nrRecv, 1 - (double)nrRecv / nrSend, elapse);

    //统计rtt信息
    std::sort(rtts.begin(), rtts.end());
    double mn = *rtts.begin(), mx = *rtts.rbegin();
    double avg = accumulate(rtts.begin(), rtts.end(), 0.0) / rtts.size();
    double mdev = 0;
    for (std::vector<double>::iterator p = rtts.begin(); p != rtts.end(); p++)
        mdev += (*p - avg) * (*p - avg);
    mdev = pow(mdev / rtts.size(), 0.5);

    printf("rtt min/avg/max/mdev = %.3lf/%.3lf/%.3lf/%.3lf ms\n", mn, avg, mx, mdev);

    exit(0);
}

//发送ICMP包
void sendRequest(int sockfd)
{
    //包计数
    static uint32_t no = 1;

    //填充ICMP包内容
    icmp *icmp = (struct icmp *)sendBuf;
    icmp->icmp_type = ICMP_ECHO;
    icmp->icmp_code = 0;
    icmp->icmp_cksum = 0;
    icmp->icmp_seq = no++;
    icmp->icmp_id = getpid();

    timeval *tv = (timeval *)icmp->icmp_data;
    gettimeofday(tv, NULL);
    icmp->icmp_cksum = calChecksum((uint16_t *)icmp, REQLEN);

    //发送
    int n = sendto(sockfd, sendBuf, REQLEN, 0, (sockaddr *)&dstAddr, sizeof(dstAddr));
    if (n <= 0)
    {
        if (errno == EWOULDBLOCK)
            printf("Send timed out.\n");
        else
            perror("sendto error");
    }
    else
        nrSend++;
}

//接收ICMP包
void recvReply(int sockfd)
{
    socklen_t addrLen = sizeof(dstAddr);
    while (true) //接收ICMP应答,无限循环直到成功接收或超时出错。
    {
        int n = recvfrom(sockfd, recvBuf, sizeof(recvBuf), 0, (sockaddr *)&dstAddr, &addrLen);
        if (n <= 0)
        {
            if (errno == EWOULDBLOCK)
                printf("Receive timed out.\n");
            else
                perror("recvfrom error");
            return;
        }

        ip *ip = (struct ip *)recvBuf;
        int iphdrLen = ip->ip_hl << 2;
        if ((n -= iphdrLen) < 8)
        {
            printf("ICMP packets too short.\n");
            return;
        }

        icmp *icmp = (struct icmp *)(recvBuf + iphdrLen);
        if (icmp->icmp_type == ICMP_ECHOREPLY && icmp->icmp_id == getpid()) //确实是之前发送包的应答
        {
            nrRecv++;
            timeval *tvSend = (timeval *)icmp->icmp_data, tvRecv;
            gettimeofday(&tvRecv, NULL);
            double rtt = (tvRecv.tv_sec - tvSend->tv_sec) * 1000 + (tvRecv.tv_usec - tvSend->tv_usec) / 1000.0; //时间差
            rtts.push_back(rtt);
            printf("%d byte from %s: icmp_req=%u, ttl=%d, time=%.1lf ms\n", n, dstIP, icmp->icmp_seq,  ip->ip_ttl, rtt);
            return;
        }
    }
}

//无限ping
void ping(int sockfd)
{
    //扩大缓冲区
    int size = 1024 * 50;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));

    //设置超时值
    timeval tv = {5, 0};
    setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    //无限ping只能靠ctrl-c结束,设置该信号的响应
    signal(SIGINT, calStat);

    nrSend = nrRecv = 0;
    while (1) //无限发送ICMP请求和接收应答
    {
        sendRequest(sockfd);
        recvReply(sockfd);
        sleep(1); //1秒1次
    }
}

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("Usage: Ping IP/host.\n");
        return 0;
    }

    gettimeofday(&startTime, NULL); //计时开始

    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
    {
        perror("Creating socket error");
        exit(1);
    }

    memset(&dstAddr, 0, sizeof(dstAddr));
    dstAddr.sin_family = AF_INET;
    if (inet_pton(AF_INET, argv[1], &dstAddr.sin_addr) <= 0)
    {
        //输入的是主机名
        hostent *host;
        if ((host = gethostbyname(argv[1])) == NULL)
        {
            perror("Invalid parameter");
            exit(1);
        }
        memcpy((char *)&dstAddr.sin_addr, host->h_addr, sizeof(dstAddr.sin_addr));
        inet_ntop(AF_INET, (void *)&dstAddr.sin_addr, dstIP, sizeof(dstIP));
        strcpy(dstName, host->h_name);
    }
    else
    {
        //输入的是IP地址
        memcpy(dstIP, argv[1], sizeof(argv[1]));
        memcpy(dstName, argv[1], sizeof(argv[1]));
    }

    printf("PING %s (%s) %d(%d) bytes of data.\n", dstName, dstIP, DATALEN, REQLEN + 20);

    ping(sockfd);

    return 0;
}

参考链接:http://noalgo.info/810.html

时间: 2024-10-24 20:01:40

Ping程序实现的相关文章

计算机网络(5)-----ICMP协议和PING程序

控制报文协议(Internet Control Message Protocol) 定义 它是TCP/IP协议族的一个子协议,用于在IP主机.路由器之间传递控制消息.控制消息是指网络通不通.主机是否可达.路由是否可用等网络本身的消息. 简介 ICMP报文就像是IP报文的小弟,总顶着IP报文的名头出来混.因为ICMP报文是在IP报文内部的,如图: IP协议并不是一个可靠的协议,它不保证数据被送达,那么,自然的,保证数据送达的工作应该由其他的模块来完成.其中一个重要的模块就是ICMP(网络控制报文)

Ping程序

一.概述 Ping程序是对两个TCP/IP系统连通性进行测试的基本工具.该程序发送一份ICMP回显请求报文给主机,并等待返回ICMP回显应答. 二.格式 大多数TCP/IP实现都在内核中直接支持Ping服务器--这种服务器不是一个用户进程. ? 在Unix中,把ICMP的标识符字段设置为发送进程的ID号,这样子即使在同一台主机上同时运行了多个Ping程序实例,也能正确识别出返回的信息.

《网络编程》原始套接字 ---ping程序实现

概述 基于字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)不可以访问传输层协议,只是对应用层的报文进行操作,传输层的数据报格式都是由系统提供的协议栈实现,用户只需要填充相应的应用层报文,由系统完成底层报文首部的填充并发送.原始套接字(SOCK_RAW)可以访问位于基层的传输层协议,原始套接字没有端口号. 原始套接字(SOCK_RAW)是一种不同于 SOCK_STREAM.SOCK_DGRAM 的套接字,它实现于系统核心.原始套接字使进程可以读与写 ICMP.IGMP

ping程序的实现

构造icmp包,发送给自己在同一网段的主机,使用select函数,非阻塞方式接收回包.还包括反码算术求和求首部校验和的函数. 转载请注明出处. 可能的情况 1.在线 目的主机直接回复icmp包. 2.终点不可达(发送不到目的主机) 接收到路由器或本机的icmp的终点不可达回包. 3.接受不到回包(能发送到目的主机) 能发送到目的主机,但是被目的主机的防火墙拦截了,不做回复,所以收不到回包. 代码绝大部分是老师上课的内容,自己整理了一遍,加上了一些注释: 1 // SetAvailableIP.c

Ping程序与Traceroute程序的原理(五)

(参考文献)TCP/IP详解,卷1:协议 "ping"这个名字来源于声呐定位操作.目的是为了测试另外一台主机是否可以到达.该程序发送一份ICMP回显请求报文给主机,并等待返回ICMP回显应答. 一般来说,如果不能ping到某台主机那么就不能Telnet或者FTP到那台主机.反过来,如果不能Telnet到某台主机,那么通常可以用ping程序来确定问题出在哪里.Ping程序还能测出到这台主机的往返时间,以表明该主机离我们有"多远". Ping程序 我们称发送回显请求的p

ping程序剖析

   在剖析ping之前我们先补充一点知识...     (1)结构体addinfo      头文件:#include<netdb.h>     struct addrinfo     {            int  ai_flags;            int  ai_family;      //AF_INET,AF_INET6,UNIX etc            int  ai_socktype;   //STREAW,DATAGRAM,RAW            int

Linux下实现ping程序

今天参照大神的代码实现了一个ping程序. 总体是先发送一个ping请求,然后循环四次监听返回. send_ping函數 將icmp_hdr(icmp消息的header)的指針指向一片內存空間,然後定義各個屬性.通過memcpy函數將要發送的數據複製到data屬性中. 再通過sendto函數將icmp數據包發送到指定地址 sendto函數 #include <sys/types.h> #include <sys/socket.h> int sendto(int socketfd,

003.同时Ping多个IP(select实现IO复用,信号计时),ping程序升级版

写这个的目的主要是为了以后的方便: 1.信号计时函数的使用 2.ip头的构建和icmp头的构建 3.selec函数t的用法 代码实现: /src/ping.h 1 /* 2 * ping.h 3 * 4 * Created on: 2015年11月6日 5 * Author: root 6 */ 7 8 #ifndef PING_H_ 9 #define PING_H_ 10 11 #endif /* PING_H_ */ 12 13 #include <sys/types.h> 14 #in

TCP协议学习记录 (三) Ping程序 RR选项 记录路由hop

一开始想直接在上个程序改,自己构造IP包头,但后来发现不行,微软不让干了,所有后来选用libcap库来收发包 代码写的很乱.. 1 #pragma pack(4) 2 3 #define ECHO_REQUEST 8 4 #define DATASIZE 65500 5 #define PACKETSIZE 65535 6 #define IPCOUNT 9 7 #define MACSIZE 6 8 #define OPTION_RR 7 9 10 struct ethhdr 11 { 12