#include "stdio.h"
#include "fcntl.h"
#include "errno.h"
#include "signal.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "sys/time.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "netdb.h"
#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
#define ICMP_HEADSIZE 8
#define IP_HEADSIZE 20
#define MAX(a, b) ((a) > (b)) ? (a) : (b)
#define MIN(a, b) ((a) > (b)) ? (b) : (a)
char *host;
char *prog;
extern errno;
long lSendTime;
u_short seq;
int iTimeOut;
int sock, sent, recvd, max, min, total;
u_long lHostIp;
struct sockaddr_in it; //套接字地址信息
struct timeval now; //用于计算时间,timeval为系统内定义结构体
int ping();
void stat();
typedef struct tagIpHead //定义IP报头结构
{
u_char ip_verlen; //header length
u_char ip_tos; //type of service
u_char ip_len; //total length
u_short ip_id; //identification
u_short ip_fragoff; //fragment offset field
u_char ip_ttl; //time to live
u_char ip_proto; //protocol
u_short ip_chksum; //IP checksum
u_long ip_src_addr; //source address
u_long ip_dst_addr; //dest address
}IPHEAD;
typedef struct tagIcmpHead //定义Icmp报头结构
{
u_char icmp_type; //type of message
u_char icmp_code; //type sub code
u_short icmp_chksum; //ones complement checksum of struct
u_short icmp_id; //标示符
u_short icmp_seq; //顺序号
u_char icmp_data[1];
}ICMPHEAD;
/*校验和算法
原理:把被校验的数据16位进行累加,然后取反码,若数据字节长度为奇数,则数据尾部补一个字节的0以凑成偶数.
接收方在计算过程中包含了发送方存在首部中的校验和,因此如果在传输过程中没发生任何差错,那么接收方计算的校验和结果应全为1。如果不全为1(即校验和错误),那么IP就丢弃收到的数据报。
*/
u_short ChkSum(u_short *pIcmpData, int iDataLen)
{
u_short iSum;
u_short iOddByte;
iSum = 0;
/*把ICMP报头二进制数据以2字节为单位累加起来*/
while(iDataLen > 1)
{
iSum ^= *(pIcmpData++); //^表示异或运算
iDataLen -= 2;
}
/*若ICMP报头为奇数个字节,会剩下最后一字节.把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加*/
if(iDataLen == 1)
{
iOddByte = 0;
*((u_char *) & iOddByte) = *(u_char *)pIcmpData;
iSum ^= iOddByte;
}
iSum^0xffff; //继续累加
return(iSum);
}
/*获取当前时间*/
long time_now()
{
struct timeval now;
long lPassed;
gettimeofday(&now, 0);
/* tv_sec 秒; tv_usec 微秒 */
lPassed = now.tv_sec * 1000000 + now.tv_usec; //采用微秒精度
return lPassed;
}
main(int argc, char **argv)
{
struct hostent *h;
char buf[200];
char dst_host[32];
int i, namelen;
IPHEAD *pIpHead;
ICMPHEAD *pIcmpHead;
int normalSize = 0;
int times = 0;
if(argc < 2) //输入参数不合要求
{
printf("usage: %s [-timeout] host | IP\n", argv[0]);
exit(0);
}
//printf("argc: %d argv: %s", argc, *(argv + 1));
prog = argv[0];
host = argc == 2 ? argv[1] : argv[2];
iTimeOut = argc == 2 ? 1 : atoi(argv[1]);
/*数据发送之前要建立一个套接字(socket),系统调用socket()返回一个套接口描述符,如果出错,则返回-1。*/
if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
{
perror("socket");
exit(2);
}
/*初始化套接字it*/
bzero(&it, sizeof(it)); //置字节字符串it的前sizeof(it)个字节为零
it.sin_family = AF_INET; //地址族
/*inet_addr(host)
每个ip对应一个32位的数,inet_addr把他们倒序过来,再转换为十进制数即为结果.
*/
if((lHostIp = inet_addr(host)) != INADDR_NONE) //host为32位IP地址,lHostIp为十进制数表示
{
it.sin_addr.s_addr = lHostIp; //获取主机IP地址
strcpy(dst_host, host); //拷贝二进制四段IP
}
else if(h = gethostbyname(host))
{
bcopy(h -> h_addr, &it.sin_addr, h -> h_length);
//bcopy()函数,将h->h_addr的前h->h_length个字节复制到&it.sin_addr中
sprintf(dst_host, "%s(%s)", host, inet_ntoa(it.sin_addr));
}
else //IP或主机名错误
{
fprintf(stderr, "bad IP or host\n");
exit(3);
}
namelen = sizeof(it);
i = IP_HEADSIZE + ICMP_HEADSIZE + sizeof(long);
printf("\npinging %s, send %d bytes\n\n", dst_host, i);
seq = 0;
sigset(SIGINT, stat); //sigset用于修改信号定位
sigset(SIGALRM, ping);
alarm(iTimeOut); //alarm()用来设置信号在经过iTimeOut后传送给目前的进程
ping();
for(;;)
{
register size;
register u_char ttl;
register delta;
register iIpHeadLen;
size = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&it, &namelen); //recvfrom()从(已连接)套接口上接收数据,并捕获数据发送源的地址,it为装有源地址的缓冲区.若无错误发生,recvfrom()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误
if(size == -1)
{
//printf("recvrrom failed, size is %d\n", size);
continue;
}
delta = (int)((time_now() - lSendTime)); //计算时间差,即PING所需时间
pIpHead = (IPHEAD *)buf;
iIpHeadLen = (int)((pIpHead -> ip_verlen&0xf) << 2);
normalSize = iIpHeadLen + ICMP_HEADSIZE;
if(size < normalSize)
{
//printf("sizeof iIpHeadLen + ICMP_HEADSIZE error:size is %\n", size);
continue;
}
ttl = pIpHead -> ip_ttl;
pIcmpHead = (ICMPHEAD *)(buf + iIpHeadLen);
if(pIcmpHead -> icmp_type != ICMP_ECHOREPLY)
{
//printf("ICMP_ECHOREPLY error\n");
continue;
}
if(pIcmpHead -> icmp_id != seq || pIcmpHead -> icmp_seq != seq)
{
//printf("icmp_id != seq\n");
continue;
}
sprintf(buf, "icmp_seq = %u bytes = %d ttl = %d",pIcmpHead -> icmp_seq, seq, size, ttl);
printf("myping reply from %s: %s time=%.3f ms\n",host ,buf, (float)delta / 1000);
max = MAX(delta,max);
min = min < delta ? MAX(delta, min) : delta;
total += delta;
++recvd;
++seq;
if(times == 3) //设置ping的次数
{
stat();
break;
}
times++;
}
}
ping()
{
char buf[200];
int iPacketSize;
int tag = -1;
ICMPHEAD *pIcmpHead = (ICMPHEAD *)buf;
pIcmpHead -> icmp_type = ICMP_ECHO;
pIcmpHead -> icmp_code = 0;
pIcmpHead -> icmp_id = seq;
pIcmpHead -> icmp_seq = seq;
pIcmpHead -> icmp_chksum = 0;
*((long *)pIcmpHead -> icmp_data) = time_now();
iPacketSize = ICMP_HEADSIZE + 4;
pIcmpHead -> icmp_chksum = ChkSum((u_short *)pIcmpHead, iPacketSize); //校验和算法
lSendTime = time_now();
tag = sendto(sock, buf, iPacketSize, 0, (struct sockaddr *) & it, sizeof(it)); //发送数据
if(tag < 0) //发送失败
{
printf("\nmy_ping send failed\n");
exit(6);
}
++sent;
alarm(iTimeOut);
}
/*显示工作状态*/
void stat()
{
if(sent)
{
printf("\n%s ping statistics summerized by 网络四班-彭华^-^\n", host);
printf("%d packets sent, %d packets received, %.2f%% lost\n", sent, recvd, (float)(sent-recvd) / (float)sent * 100);
}
if(recvd)
{
printf("round_trip min/avg/max: %d/%d/%d ms\n\n", min, total/recvd, max);
}
exit(0);
}