摘自: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. 雅虎CSS初始化代码

    body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,b ...

  2. librtmp接收flv流中提取h264码流:根据多个资料汇总

    rtmpdump可以下载rtmp流并保存成flv文件.如果要对流中的音频或视频单独处理,需要根据flv协议分别提取.简单修改rtmpdump代码,增加相应功能.1 提取音频:rtmpdump程序在Do ...

  3. mysql 数据库 自动截取数据的问题---mysql的sql_model的四种模式:宽松模式、严格模式

    mysql支持的sql_mode模式:ANSI.TRADITIONAL.STRICT_ALL_TABLES和STRICT_TRANS_TABLES. ANSI模式:宽松模式,对插入数据进行校验,如果不 ...

  4. mysql分区表之二:MySQL的表的四种分区类型介绍

    一.什么是表分区 通俗地讲表分区是将一大表,根据条件分割成若干个小表.mysql5.1开始支持数据表分区了.如:某用户表的记录超过了600万条,那么就可以根据入库日期将表分区,也可以根据所在地将表分区 ...

  5. centos开机提示CPU无法识别的问题

    centos版本和对应cpu兼容性,官方网址: https://access.redhat.com/support/policy/intel Red Hat Enterprise Linux Vers ...

  6. Vim编辑器基本操作学习(一)

      最近在服务端编辑文件总不可避免要使用vim编辑器,下面就对学习到的常用命令进行总结,以便自己以后查看.   基本编辑命令   删除字符:x 删除一行:dd 删除换行符:J,同时将两行合并成一行 撤 ...

  7. Joker的运维开发之路

    python 1--数据类型,流程控制 2--数据类型详细操作,文件操作,字符编码 https://mp.weixin.qq.com/s/i3lcIP82HdsSr9LzPgkqww 点开更精彩 目前 ...

  8. 修改win7远程桌面端口号

    Windows 7/Vista/XP/2003等系统中的远程终端服务是一项功能非常强大的服务,同时也成了入侵者长驻主机的通道,入侵者可以利用一些手段得到管理员账号和密码并入侵主机.下面,我们来看看如何 ...

  9. canvas绘制路径

    canvas绘制路径 方法 beginPath() 创建一个新的路径 lineTo() 描绘路径 closePath() 沿着路径画直线,并且画点移动到路径开头 stroke() 绘制形状 fill( ...

  10. django网页的分页功能,大家如果有疑问请留言

    url文件 from django.contrib import admin from django.conf.urls import url from app01 import views urlp ...