摘自:https://blog.csdn.net/weibo1230123/article/details/79891018

ping的实现和代码分析
一.介绍     
ping命令是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。ping命令的工作原理是:
向网络上的另一个主机系统发送ICMP报文,如果指定系统得到了报文,它将把报文一模一样地传回给发送者,这有点象潜水艇声纳系统中使用的发声装置。 例如,在Linux终端上执行ping如下:

二.分析

由上面的执行结果可以看到,ping命令执行后显示出被测试系统主机名和相应IP地址、返回给当前主机的ICMP报文顺序号、ttl生存时间和往返时间rtt(单位是毫秒,即千分之一秒)。要写一个模拟ping命令,这些信息有启示作用。要真正了解ping命令实现原理,就要了解ping命令所使用到的TCP/IP协议。 ICMP(Internet Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。ICMP协议是IP层的一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。由于IP层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect()函数,若有使用也只是用于设置IP地址。明白了工作原理,我们就可以写我们自己的ping命令:myping:

代码1:

 /*
* 名称: myping
* 程序应用: ping命令是向目的主机发送ICMP报文,检验本地主机和远程的目的主机是否连接
*
*
*/ /*ICMP必须使用原始套接字进行设计,要手动设置IP的头部和ICMP的头部并行校验*/
/***********主函数*********************************************
myping.c*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/time.h>
#include <stdio.h>
#include <string.h> /* bzero */
#include <netdb.h>
#include <pthread.h>
//保存发送包的状态值
typedef struct pingm_pakcet{
struct timeval tv_begin; //发送时间
struct timeval tv_end; //接收到的时间
short seq; //序列号
int flag; //1,表示已经发送但是没有接收到回应,0,表示接收到回应
}pingm_pakcet;
static pingm_pakcet *icmp_findpacket(int seq);
static unsigned short icmp_cksum(unsigned char *data, int len);
static struct timeval icmp_tvsub(struct timeval end, struct timeval begin);
static void icmp_statistics(void);
static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv,int length);
static int icmp_unpack(char *buf,int len);
static void *icmp_recv(void *argv);
static void icmp_sigint(int signo);
static void icmp_usage();
static pingm_pakcet pingpacket[];
#define K 1024
#define BUFFERSIZE 72 //发送缓冲区的大小
static unsigned char send_buff[BUFFERSIZE];
static unsigned char recv_buff[*K]; //防止接收溢出,设置大一些
static struct sockaddr_in dest; //目的地址
static int rawsock = ; //发送和接收线程需要的socket描述符
static pid_t pid; //进程PID
static int alive = ; //是否接收到退出信号
static short packet_send = ; //已经发送的数据包数量
static short packet_recv = ; //已经接收的数据包数量
static char dest_str[]; //目的主机字符串
static struct timeval tv_begin, tv_end, tv_interval; //2.计算发送和接收的时间
static void icmp_usage()
{
//ping加IP地址或者域名
printf("ping aaa.bbb.ccc.ddd\n");
}
/*终端信号处理函数SIGINT*/
static void icmp_sigint(int signo)
{
alive = ;
gettimeofday(&tv_end,NULL);
tv_interval = icmp_tvsub(tv_end, tv_begin);
return;
} //3.统计数据结果
/*统计数据结果函数******************************************
打印全部ICMP发送的接收统计结果*/
static void icmp_statistics(void)
{
long time = (tv_interval.tv_sec * ) + (tv_interval.tv_usec/);
printf("--- %s ping statistics ---\n", dest_str);
printf("%d packets transmitted, %d received, %d%c packet loss, time %ld ms\n",
packet_send,packet_recv,(packet_send-packet_recv)*/packet_send,'%',time);
}
/*************查找数组中的标识函数***********************
查找合适的包的位置
当seq为1时,表示查找空包
其他值表示查找seq对应的包*/
static pingm_pakcet *icmp_findpacket(int seq)
{
int i;
pingm_pakcet *found = NULL;
//查找包的位置
if(seq == -){
for(i=;i<;i++){
if(pingpacket[i].flag == ){
found = &pingpacket[i];
break;
}
}
}
else if(seq >= ){
for(i = ;i< ;i++){
if(pingpacket[i].seq == seq){
found = &pingpacket[i];
break;
}
}
}
return found;
} //4.校验和函数
/*************校验和函数*****************************
TCP/IP协议栈使用的校验算法是比较经典的,对16位的数据进行累加计算,并返回计算结果, CRC16校验和计算icmp_cksum
参数:
data:数据
len:数据长度
返回值:
计算结果,short类型
*/
static unsigned short icmp_cksum(unsigned char *data, int len)
{
int sum = ; //计算结果
int odd = len & 0x01; //是否为奇数
/*将数据按照2字节为单位累加起来*/
while(len & 0xfffe){
sum += *(unsigned short*)data;
data += ;
len -= ;
}
/*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一个字节*/
if(odd){
unsigned short tmp = ((*data)<<)&0xff00;
sum += tmp;
}
sum = (sum >> ) + (sum & 0xffff); //高地位相加
sum += (sum >> ); //将溢出位加入 return ~sum; //返回取反值
} //5.ICMP头部校验打包和拆包
/**********进行ICMP头部校验********************/
//设置ICMP报头
static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length)
{
unsigned char i = ;
//设置报头
icmph->icmp_type = ICMP_ECHO; //ICMP回显请求
icmph->icmp_code = ; //code的值为0
icmph->icmp_cksum = ; //先将cksum的值填为0,便于以后的cksum计算
icmph->icmp_seq = seq; //本报的序列号
icmph->icmp_id = pid & 0xffff; //填写PID
for(i=; i< length; i++)
icmph->icmp_data[i] = i; //计算校验和
icmph->icmp_cksum = icmp_cksum((unsigned char*)icmph, length);
} /*解压接收到的包,并打印信息*/
static int icmp_unpack(char *buf, int len)
{
int i,iphdrlen;
struct ip *ip = NULL;
struct icmp *icmp = NULL;
int rtt; ip = (struct ip *)buf; //IP报头
iphdrlen = ip->ip_hl * ; //IP头部长度
icmp = (struct icmp *)(buf+iphdrlen); //ICMP段的地址
len -= iphdrlen;
//判断长度是否为ICMP包
if(len < ){
printf("ICMP packets\'s length is less than 8\n");
return -;
}
//ICMP类型为ICMP_ECHOREPLY并且为本进程的PID
if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)){
struct timeval tv_interval,tv_recv,tv_send;
//在发送表格中查找已经发送的包,按照seq
pingm_pakcet *packet = icmp_findpacket(icmp->icmp_seq);
if(packet == NULL)
return -;
packet->flag = ; //取消标志
tv_send = packet->tv_begin; //获取本包的发送时间 gettimeofday(&tv_recv,NULL); //读取此时间,计算时间差
tv_interval = icmp_tvsub(tv_recv,tv_send);
rtt = tv_interval.tv_sec * + tv_interval.tv_usec/;
/*打印结果包含
ICMP段的长度
源IP地址
包的序列号
TTL
时间差
*/
printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n",
len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rtt);
packet_recv ++; //接收包数量加1
}
else {
return -;
}
} //6.计算时间差函数
/************计算时间差time_sub************************
参数:
end:接收到时间
begin:开始发送的时间
返回值:
使用的时间
*/
static struct timeval icmp_tvsub(struct timeval end, struct timeval begin)
{
struct timeval tv;
//计算差值
tv.tv_sec = end.tv_sec - begin.tv_sec;
tv.tv_usec = end.tv_usec - begin.tv_usec;
//如果接收的时间的usec值小于发送时的usec,从uesc域借位
if(tv.tv_usec < ){
tv.tv_sec --;
tv.tv_usec += ;
} return tv;
} //7.发送报文函数
//**********发送报文***************************
static void *icmp_send(void *argv)
{
//保存程序开始发送数据的时间
gettimeofday(&tv_begin, NULL);
while(alive){
int size = ;
struct timeval tv;
gettimeofday(&tv, NULL); //当前包的发送时间
//在发送包状态数组中找到一个空闲位置
pingm_pakcet *packet = icmp_findpacket(-);
if(packet){
packet->seq = packet_send;
packet->flag = ;
gettimeofday(&packet->tv_begin,NULL);
}
icmp_pack((struct icmp *)send_buff,packet_send,&tv, );
//打包数据
size = sendto(rawsock, send_buff,,,(struct sockaddr *)&dest, sizeof(dest));
if(size < ){
perror("sendto error");
continue;
}
packet_send ++;
//每隔1s发送一个ICMP回显请求包
sleep();
}
} //8.接收目的主机的回复函数
/***********接收ping目的主机的回复***********/
static void *icmp_recv(void *argv)
{
//轮询等待时间
struct timeval tv;
tv.tv_usec = ;
tv.tv_sec = ;
fd_set readfd;
//当没有信号发出一直接收数据
while(alive){
int ret = ;
FD_ZERO(&readfd);
FD_SET(rawsock,&readfd);
ret = select(rawsock+,&readfd,NULL,NULL,&tv);
switch(ret)
{
case -:
//错误发生
break;
case :
//超时
break;
default :
{
//收到一个包
int fromlen = ;
struct sockaddr from;
//接收数据
int size = recv(rawsock,recv_buff,sizeof(recv_buff),);
if(errno == EINTR){
perror("recvfrom error");
continue;
}
//解包
ret = icmp_unpack(recv_buff,size);
if(ret == ){
continue;
}
}
break;
}
}
} //9.设置ICMP头部(程序中不需要,这里只是作为了解)
/**********设置ICMP发送报文的头部*********************************
回显请求的ICMP报文
*/
/*struct icmp
{
u_int8_t icmp_type; //消息类型
u_int8_t icmp_code; //消息类型的子码
u_int16_t icmp_cksum; //校验和
union
{
struct ih_idseq //显示数据报
{
u_int16_t icd_id; //数据报ID
u_int16_t icd_seq; //数据报的序号
}ih_idseq;
}icmp_hun;
#define icmp_id icmp_hun.ih_idseq.icd_id;
#define icmp_seq icmp_hun.ih_idseq.icd_seq;
union
{
u_int8_t id_data[1]; //数据
}icmp_dun;
#define icmp_data icmp_dun.id_data;
}; */ //10.主函数
//主程序
int main(int argc, char const *argv[])
{
struct hostent *host = NULL;
struct protoent *protocol = NULL;
char protoname[] = "icmp";
unsigned long inaddr = ;
int size = *K; if(argc < ) //参数是否数量正确
{
icmp_usage();
return -;
}
//获取协议类型
protocol = getprotobyname(protoname);
if(protocol == NULL)
{
perror("getprotobyname()");
return -;
}
//复制目的地址字符串
memcpy(dest_str, argv[],strlen(argv[])+);
memset(pingpacket, , sizeof(pingm_pakcet) * );
//socket初始化
rawsock = socket(AF_INET, SOCK_RAW, protocol->p_proto);
if(rawsock < ){
perror("socket");
return -;
} pid = getuid(); //为与其他线程区别,加入pid
//增大接收缓冲区,防止接收包被覆盖
setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
bzero(&dest, sizeof(dest));
//获取目的地址的IP地址
dest.sin_family = AF_INET;
//输入的目的地址为字符串IP地址
inaddr = inet_addr(argv[]);
if(inaddr == INADDR_NONE){ //输入的是DNS地址
host = gethostbyname(argv[]);
if(host == NULL){
perror("gethostbyname");
return -;
}
//将地址复制到dest
memcpy((char *)&dest.sin_addr, host->h_addr, host->h_length);
} //IP地址字符串
else {
memcpy((char *)&dest.sin_addr, &inaddr,sizeof(inaddr));
}
//打印提示
inaddr = dest.sin_addr.s_addr;
printf("PING %s (%ld.%ld.%ld.%ld) 56(84) bytes of data.\n",
dest_str,(inaddr&0x000000ff)>>,(inaddr&0x0000ff00)>>,(inaddr&0x00ff0000)>>,(inaddr&0xff000000)>>);
//截取信号SIGINT,将icmp_sigint挂接上
signal(SIGINT,icmp_sigint); /*发送数据并接收回应
建立两个线程,一个用于发数据,另一个用于接收响应数据,主程序等待两个线程运行完毕后再进行
下一步,最后对结果进行统计并打印
*/
alive = ; //初始化可运行
pthread_t send_id, recv_id; //建立两个线程,用于发送和接收
int err = ;
err = pthread_create(&send_id, NULL, icmp_send, NULL); //发送
if(err < ){
return -;
}
err = pthread_create(&recv_id, NULL, icmp_recv, NULL); //接收
if(err < ){
return -;
}
//等待线程结束
pthread_join(send_id, NULL);
pthread_join(recv_id, NULL);
//清理并打印统计结果
close(rawsock);
icmp_statistics();
return ;
}
三.程序运行方法
由于在程序中用到了<pthread.h>多线程,所以在编译时要加上-lpthread,并且想要运行的话要在root权限下,一般的用户是没有权限的此程序编译方法为:
gcc -o myping myping.c -lpthread 
./myping www.baidu.com
 
 
代码2:
 #include "stdafx.h"
#include<stdio.h>
#include<windows.h>
#include<process.h> #pragma comment( lib, "ws2_32.lib" ) #define SEND_SIZE 32
#define PACKET_SIZE 4096
#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0 struct icmp
{
unsigned char icmp_type;
unsigned char icmp_code;
unsigned short icmp_cksum;
unsigned short icmp_id;
unsigned short icmp_seq;
unsigned long icmp_data;
}; struct ip
{
unsigned char ip_hl:;
unsigned char ip_v:;
unsigned char ip_tos;
unsigned short ip_len;
unsigned short ip_id;
unsigned short ip_off;
unsigned char ip_ttl;
unsigned char ip_p;
unsigned short ip_sum;
unsigned long ip_src;
unsigned long ip_dst;
}; /*unsigned */char sendpacket[PACKET_SIZE];
/*unsigned */char recvpacket[PACKET_SIZE];
struct sockaddr_in dest_addr;
struct sockaddr_in from_addr;
int sockfd;
int pid; unsigned short cal_chksum(unsigned short *addr,int len);
int pack(int pack_no);
int unpack(/*unsigned*/ char *buf,int len);
void send_packet(void);
void recv_packet(void); void main(int argc,char *argv[])
{
struct hostent *host;
struct protoent *protocol;
WSADATA wsaData;
int timeout=;
int SEND_COUNT=;
int i;
char* par_host = "www.baidu.com"; /*
par_host=argv[argc-1];
switch(argc)
{
case 2: break;
case 3: if(strcmp(argv[1],"-t")==0)
{
SEND_COUNT=10000;
break;
}
//fall through
default:
printf("usage: %s [-t] Host name or IP address\n",argv[0]);
exit(1);
}*/ if(WSAStartup(0x1010,&wsaData)!=)
{
printf("wsastartup error\n");
exit();
}
if( (protocol=getprotobyname("icmp") )==NULL)
{
printf("getprotobyname error\n");
exit();
}
if( (sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto) )<)
{
printf("socket error\n");
exit();
}
if(setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout))<)
fprintf(stderr,"failed to set recv timeout: %d\n",WSAGetLastError());
if(setsockopt(sockfd,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout))<)
fprintf(stderr,"failed to set send timeout: %d\n",WSAGetLastError()); memset(&dest_addr,,sizeof(dest_addr));
dest_addr.sin_family=AF_INET;
if(host=gethostbyname(par_host) )
{
memcpy( (char *)&dest_addr.sin_addr,host->h_addr,host->h_length);
//resolve address to hostname
if(host=gethostbyaddr(host->h_addr,,PF_INET))
par_host=host->h_name;
}
else if( dest_addr.sin_addr.s_addr=inet_addr(par_host)==INADDR_NONE)
{
printf("Unkown host %s\n",par_host);
exit();
} pid = _getpid();
printf("Pinging %s [%s]: with %d bytes of data:\n\n",par_host,inet_ntoa(dest_addr.sin_addr),SEND_SIZE);
for(i=;i<SEND_COUNT;i++)
{
send_packet();
recv_packet();
Sleep();
}
} //this algorithm is referenced from other's
unsigned short cal_chksum(unsigned short *addr,int len)d
//打包
int pack(int pack_no)
{
int packsize;
struct icmp *icmp; packsize=+SEND_SIZE;
icmp=(struct icmp*)sendpacket;
icmp->icmp_type=ICMP_ECHO;
icmp->icmp_code=;
icmp->icmp_cksum=;
icmp->icmp_seq=pack_no;
icmp->icmp_id=pid;
icmp->icmp_data=GetTickCount();
icmp->icmp_cksum=cal_chksum( (unsigned short *)icmp,packsize); /*校验算法*/
return packsize;
} //解包
int unpack(/*unsigned*/ char *buf,int len)
{
struct ip *ip;
struct icmp *icmp;
double rtt;
int iphdrlen; ip=(struct ip *)buf;
iphdrlen=ip->ip_hl*;
icmp=(struct icmp *)(buf+iphdrlen);
if( (icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id==pid) )
{
len=len-iphdrlen-;
rtt=GetTickCount()-icmp->icmp_data;
printf("Reply from %s: bytes=%d time=%.0fms TTL=%d icmp_seq=%u\n",
inet_ntoa(from_addr.sin_addr),
len,
rtt,
ip->ip_ttl,
icmp->icmp_seq);
return ;
}
return ;
} //发送
void send_packet()
{
int packetsize;
static int pack_no=; packetsize=pack(pack_no++);
if( sendto(sockfd,sendpacket,packetsize,,(struct sockaddr *)&dest_addr,sizeof(dest_addr) )< )
printf("Destination host unreachable.\n");
// printf("send NO %d\n",pack_no-1);
} //接收
void recv_packet()
{
int n,fromlen;
int success; fromlen=sizeof(from_addr);
do
{
if( (n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),,(struct sockaddr *)&from_addr,&fromlen)) >=)
success=unpack(recvpacket,n);
else if (WSAGetLastError() == WSAETIMEDOUT)
{
printf("Request timed out.\n");
return;
}
}while(!success); }
 
gcc -o myping myping.c -lpthread 
./myping www.baidu.com
 
gcc -o myping myping.c -lpthread 
./myping www.baidu.com
 
--------------------- 
作者:魏波- 
来源:CSDN 
原文:https://blog.csdn.net/weibo1230123/article/details/79891018 
版权声明:本文为博主原创文章,转载请附上博文链接!

c linux ping 实现的更多相关文章

  1. 转载:解决linux ping: unknown host www.baidu.com

    解决linux ping: unknown host www.baidu.com 转载网址:http://www.kankanews.com/ICkengine/archives/48417.shtm ...

  2. 解决linux ping: unknown host www.baidu.com(转)

    解决方案:    如果某台Linux服务器ping不通域名, 如下提示: [root@localhost ~]# ping www.baidu.comping: unknown host www.ba ...

  3. windows与linux ping 显示的ip不一样

    DNS修改了一下域名对应的IP后,域名不能访问了,我在windows下ping一下域名,IP没有变过来,还是老的IP.我在linux下又ping了一下域名,是换过了的.这个问题是由windows下的本 ...

  4. linux ping命令

    Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性,我们经常会说“ping一下某机器,看是不是开着”.不能打开网页时会说“你先ping网关地址192.168.1.1试试”. ...

  5. Linux ping 命令

    ping命令用来测试与目标主机的连通性,常见用法如下: [root@localhost ~]$ ping www.baidu.com # 对目标主机域名进行连通性测试 [root@localhost ...

  6. linux ping 命令解析

    不管在windows平台,还是在linux平台,ping都是非常常用的网络命令:ping命令通过ICMP(Internet控制消息协议)工作:ping可以用来测试本机与目标主机是否联通.联通速度如何. ...

  7. Red hat linux ping: unknown host www.baidu.com

    "ping: unknown host www.baidu.com" 解决方案: 如果某台Linux服务器ping不通域名, 如下提示: [root@localhost ~]# p ...

  8. Linux ping命令详解

    Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性 基于IMCP协议 常见命令参数 -q 不显示任何传送封包的信息,只显示最后的结果 -n 只输出数值 -R 记录路由过程 ...

  9. linux ping命令实践

          ping 解析       Linux系统的ping命令是常用的网络命令,它通常用来检测与目标主机的连通性,经常说"ping以下机器,看是否开着,不能打开网页时候,可以ping ...

随机推荐

  1. OkHttp使用方法

    1.在app/build.gradle中添加依赖 compile 'com.squareup.okhttp3:okhttp:4.0.1' 2.创建OkHttpClient实例 OkHttpClient ...

  2. click 模块使用方法说明

    !/usr/bin/env python -- coding: utf-8 -- import click @click.command() @click.option('--count', defa ...

  3. 在centos7上搭建mongodb副本集

    1.安装副本集介绍 副本集(Replica Set)是一组MongoDB实例组成的集群,由一个主(Primary)服务器和多个备份(Secondary)服务器构成.通过Replication,将数据的 ...

  4. TOTAL COMMAND自定义快捷键

  5. 表格字段常用注解@NotBlank @NotEmpty @NotNul @Pattern

    在Hibernate Validator(org.hibernate.validator.constraints)中: @NotEmpty://CharSequence, Collection, Ma ...

  6. ubuntu下安装和配置最新版JDK8傻瓜教程

    ubuntu下安装和配置最新版JDK8傻瓜教程 听语音 | 浏览:18940 | 更新:2014-07-14 22:13 | 标签:ubuntu 1 2 3 4 5 6 分步阅读 ubuntu系统通常 ...

  7. awk查看接口耗时情况

    +1:LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Ag ...

  8. python开发_function annotations

    在看python的API的时候,发现了一个有趣的东东,即:python的方法(函数)注解(Function Annotation) 原文: 4.7.7. Function Annotations Fu ...

  9. Python小知识点(3)--装饰器

    (1)装饰器含参数,被装饰函数不含(含)参数 实例代码如下: import time # 装饰器函数 def wrapper(func): def done(*args,**kwargs): star ...

  10. scrapy框架的日志等级和请求参数

    一 . Scrapy的日志等级 - 在使用 scrapy crawl xxx 允许程序时,在终端里打印输出的就是scrapy的日志信息 - 日志信息的种类 : ERROR : 错误信息 WARNING ...