现在的位置: 首页 > 综合 > 正文

基于C语言实现的Ping程序

2014年01月29日 ⁄ 综合 ⁄ 共 7638字 ⁄ 字号 评论关闭

基于C语言实现的Ping程序

转自:http://blog.chinaunix.net/u3/104677/showart.php?id=2410971

【前言】Ping 是用于测试网络连接量的程序,利用它可以检查网络是否能够连通。Ping程序的实现原理非常简单,即发送一个ICMP回声请求消息给目的地并报告是否收到所希望的ICMP回声应答。本文采用C语言编写一个ing功能的程序。
 
0.ICMP协议简介

ICMP是(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。

ICMP协议是一种面向连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,它对于网络安全具有极其重要的意义。

它是TCP/IP协议族的一个子协议,属于网络层协议,主要用于在主机与路由器之间传递控制信息,包括报告错误、交换受限控制和状态信息等。当遇到IP数据无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。   

ICMP提供一致易懂的出错报告信息。发送的出错报文返回到发送原数据的设备,因为只有发送设备才是出错报文的逻辑接受者。发送设备随后可根据ICMP报文确定发生错误的类型,并确定如何才能更好地重发失败的数据报。但是ICMP唯一的功能是报告问题而不是纠正错误,纠正错误的任务由发送方完成。

我们在网络中经常会使用到ICMP协议,比如我们经常使用的用于检查网络通不通的Ping命令(Linux和Windows中均有),这个“Ping”的过程实际上就是ICMP协议工作的过程。还有其他的网络命令如跟踪路由的Tracert命令也是基于ICMP协议的。

ICMP报文分为两种,一是错误报告报文,二是查询报文。每个ICMP报头均包含类型、编码和校验和这三项内容,长度为8位,8位和16位,其余选项则随ICMP的功能不同而不同。这两种ICMP类型报头格式如下:

 
1.程序源代码
/********************************************************
 * 功能:实现PING功能
 * 环境: GCC-4.2.4
 * 作者:YSQ-NJUST,yushengqiangyu@163.com
 * 备注:自由软件,主要用于学习、交流、共享。
 *******************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip_icmp.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#define E_FAILD_FD -1
#define ICMP_DATA_LEN 20  /* ICMP负载长度 */
#define ICMP_ECHO_MAX 4  /* ECHO-REQUEST报文发送次数 */
#define ICMP_REQUEST_TIMEOUT 2  /* 超时时间间隔 */
 
/* ICMP报文发送与接收缓存 */
static unsigned char aucSendBuf[1024 * 1024] = {0};
static unsigned char aucRecvBuf[1024 * 1024] = {0};
 
/* 结构体定义 */
typedef struct tagIcmpStatic
{
 unsigned int uiSendPktNum;
 unsigned int uiRcvPktNum;
 float fMinTime;
 float fMaxTime;
 float fArgTime;
}ICMP_STATIC_S;
 
/* 全局数据结构 */
ICMP_STATIC_S g_stPktStatic;      /* ICMP报文统计 */
struct timeval stSendTime = {0};  /* ECHO-REQUEST报文发送时间 */
struct timeval stRcvTime ={0};    /* ECHO-REPLY报文接收时间 */
 
/* 输出报文统计信息 */
void showStatic(const ICMP_STATIC_S *pstStInfo)
{
 unsigned int uiSend, uiRecv;
 
 uiSend = pstStInfo->uiSendPktNum;
 uiRecv = pstStInfo->uiRcvPktNum;
 printf("/n***PING Statistics***");
 printf("/nPackets:Send = %u,Recveived = %u,Lost = %u", uiSend, uiRecv, uiSend - uiRecv);
 printf("/nTime:Minimum = %.1fms,Maximum = %.1fms,Average=%.2fms/n", pstStInfo->fMinTime, pstStInfo->fMaxTime, pstStInfo->fArgTime);
}
 
/* 计算时间差,返回时间以毫秒为单位 */
unsigned int timeSub(const struct timeval *pstOut, const struct timeval *pstIn)
{
 unsigned int uiSec = 0;
 int iUsec = 0;
 
 uiSec = pstOut->tv_sec - pstIn->tv_sec;
 iUsec = pstOut->tv_usec - pstIn->tv_usec;
 if (0 > iUsec)
 {
  iUsec += 1000000;
  uiSec--;
 }
 return uiSec * 1000 + (unsigned int)(iUsec / 1000);
}
 
/* 校验和计算 */
unsigned short calcIcmpChkSum(const void *pPacket, int iPktLen)
{
 unsigned short usChkSum = 0;
 unsigned short *pusOffset = NULL;
 
 pusOffset = (unsigned short *)pPacket;
 while(1 < iPktLen)
 {
  usChkSum += *pusOffset++;
  iPktLen -= sizeof(unsigned short);
 }
 if (1 == iPktLen)
 {
  usChkSum += *((char *)pusOffset);
 }
 usChkSum = (usChkSum >> 16) + (usChkSum & 0xffff);
 usChkSum += (usChkSum >>16);
 
 return ~usChkSum;
}
 
/* ICMP报文填充 */
int newIcmpEcho(const int iPacketNum, const int iDataLen)
{
 struct icmp *pstIcmp = NULL;
 
 memset(aucSendBuf, 0, sizeof(aucSendBuf));
 pstIcmp = (struct icmp *)aucSendBuf;
 pstIcmp->icmp_type = ICMP_ECHO;
 pstIcmp->icmp_code = 0;
 pstIcmp->icmp_seq = htons((unsigned short)iPacketNum);
 pstIcmp->icmp_id = htons((unsigned short)getpid());
 pstIcmp->icmp_cksum = 0;
 pstIcmp->icmp_cksum = calcIcmpChkSum(pstIcmp, iDataLen + 8);
 return iDataLen + 8;
}
 
/* 解析ECHO-REPLY响应报文 */
int parseIcmp(const struct sockaddr_in *pstFromAddr, char *pRecvBuf, const int iLen)
{
 int iIpHeadLen = 0;
 int iIcmpLen = 0;
 struct ip *pstIp = NULL;
 struct icmp *pstIcmpReply = NULL;
 
 pstIp = (struct ip *)pRecvBuf;
 iIpHeadLen = pstIp->ip_hl << 2;
 pstIcmpReply = (struct icmp *)(pRecvBuf + iIpHeadLen);
 /* 报文长度非法 */
 iIcmpLen = iLen - iIpHeadLen;
 if (8 > iIcmpLen)
 {
  printf("[Error]Bad ICMP Echo-reply/n");
  return -1;
 }
 /* 报文类型非法 */
 if ((pstIcmpReply->icmp_type != ICMP_ECHOREPLY) ||
     (pstIcmpReply->icmp_id != htons((unsigned short)getpid())))
 {
  return -1;
 }
 sleep(1);
 printf("%d bytes reply from %s: icmp_seq=%u Time=%dms TTL=%d/n", iIcmpLen, inet_ntoa(pstFromAddr->sin_addr), ntohs(pstIcmpReply->icmp_seq), timeSub(&stRcvTime, &stSendTime), pstIp->ip_ttl);
 
 return 1;
}
 
/* Echo响应报文接收 */
void recvIcmp(const int fd)
{
 int iRet = 0;
 int iRecvLen = 0;
 unsigned int uiInterval = 0;
 socklen_t fromLen = sizeof(struct sockaddr_in);
 struct sockaddr_in stFromAddr = {0};
 
 /* 清空接收缓存,并准备接收响应报文 */
 memset(aucRecvBuf, 0, 1024 * 1024);
 iRecvLen = recvfrom(fd, (void *)aucRecvBuf, sizeof(aucRecvBuf), 0, (struct sockaddr *)&stFromAddr,&fromLen);
 gettimeofday(&stRcvTime, NULL);
 if (0 > iRecvLen)
 {
  if (EAGAIN == errno)
  {
   /* 请求超时 */
   printf("Request time out./n");
   g_stPktStatic.fMaxTime = ~0;
  }
  else
  {
   /* 接收数据包出错 */
   perror("[Error]ICMP Receive");
  }
  return;
 }
 /* 获取统计参数 */
 g_stPktStatic.uiRcvPktNum++;
 uiInterval = timeSub(&stRcvTime, &stSendTime);
 g_stPktStatic.fArgTime = (g_stPktStatic.fArgTime * (g_stPktStatic.uiSendPktNum - 1) + uiInterval)/g_stPktStatic.uiSendPktNum;
 if (uiInterval < g_stPktStatic.fMinTime)
 {
  g_stPktStatic.fMinTime = uiInterval;
 }
 if (uiInterval > g_stPktStatic.fMaxTime)
 {
  g_stPktStatic.fMaxTime = uiInterval;
 }
 /* 解析ICMP响应报文 */
 iRet = parseIcmp(&stFromAddr, (char *)aucRecvBuf, iRecvLen);
 if (0 > iRet)
 {
  return;
 }
}
 
/* 发送ICMP报文 */
void sendIcmp(const int fd, const struct sockaddr_in *pstDestAddr)
{
 unsigned char ucEchoNum = 0;
 int iPktLen = 0;
 int iRet = 0;
 
 while(ICMP_ECHO_MAX > ucEchoNum)
 {
  iPktLen = newIcmpEcho(ucEchoNum, ICMP_DATA_LEN); 
  /* 记录发送起始时间 */
  g_stPktStatic.uiSendPktNum++;
  gettimeofday(&stSendTime, NULL);
  /* 发送ICMP-ECHO报文 */
  iRet = sendto(fd, aucSendBuf, iPktLen, 0, (struct sockaddr *)pstDestAddr, sizeof(struct sockaddr_in));
  if(0 > iRet)
  {
   perror("Send ICMP Error");
   continue;
  }
  /* 等待接收响应报文 */
  recvIcmp(fd);
  ucEchoNum++;
 }
}
/*主函数*/
int main(int argc, char *argv[])
{
 int iRet = 0;
 int iRcvBufSize = 1024 * 1024;
 int fd = E_FAILD_FD;
 in_addr_t stHostAddr;
 
 struct timeval stRcvTimeOut = {0};
 struct hostent  *pHost = NULL;
 struct sockaddr_in stDestAddr = {0};
 struct protoent *pProtoIcmp = NULL;
 g_stPktStatic.uiSendPktNum = 0;
 g_stPktStatic.uiRcvPktNum =0;
 g_stPktStatic.fMinTime = 1000000.0;
 g_stPktStatic.fMaxTime = -1.0;
 g_stPktStatic.fArgTime = 0.0;
 
 /* 参数判断 */
 if (2 > argc)
 {
  printf("/nUsage:%s hostname/IP address/n", argv[0]);
  return -1;
 }
 /* 获取ICMP协议类型  */
 pProtoIcmp = getprotobyname("icmp");
 if (NULL == pProtoIcmp)
 {
  perror("[Error]Get ICMP Protoent Structrue");
  return -1;
 }
 /* 创建ICMP使用的SOCKET */
 fd = socket(PF_INET, SOCK_RAW, pProtoIcmp->p_proto);
 if (0 > fd)
 {
  perror("[Error]Init Socket");
  return -1;
 }
 /* ROOT权限回收 */
 iRet = setuid(getuid());
 if (0 > iRet)
 {
  perror("[Error]Setuid");
  close(fd);
  return -1;
 }
 /* 设置SOCKET选项 */
 stRcvTimeOut.tv_sec = ICMP_REQUEST_TIMEOUT;
 setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &iRcvBufSize, sizeof(iRcvBufSize));
 setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &stRcvTimeOut, sizeof(struct timeval));
 
 /* 地址解析 */
 stHostAddr = inet_addr(argv[1]);
 if (INADDR_NONE == stHostAddr)
 {
  /* 根据主机名称解析IP地址 */
  pHost = gethostbyname(argv[1]);
  if (NULL == pHost)
  {
   perror("[Error]Host Name Error");
   close(fd);
   return -1;
  }
  memcpy((char *)&stDestAddr.sin_addr, (char *)(pHost->h_addr), pHost->h_length);
 }
 else
 {
  memcpy((char *)&stDestAddr.sin_addr, (char *)&stHostAddr, sizeof(stHostAddr));
 }
 printf("/nPING %s(%s): %d bytes in ICMP packets/n", argv[1], inet_ntoa(stDestAddr.sin_addr), ICMP_DATA_LEN);
 /* 发送ICMP报文 */
 sendIcmp(fd, &stDestAddr);
 
 /* 输出统计信息 */
 showStatic(&g_stPktStatic);
 /* 关闭FD */
 close(fd);
 return 0;
}
 
2.编译运行

sudo gcc -g -Wall -o myping ping.c

chmod u+x

myping ./myping www.baidu.com

 
3.运行结果示意

PING www.baidu.com(119.75.217.56): 20 bytes in ICMP packets

28 bytes reply from 119.75.217.56: icmp_seq=0 Time=27ms TTL=128

28 bytes reply from 119.75.217.56: icmp_seq=1 Time=23ms TTL=128

28 bytes reply from 119.75.217.56: icmp_seq=2 Time=30ms TTL=128

28 bytes reply from 119.75.217.56: icmp_seq=3 Time=31ms TTL=128

 

***PING Statistics***

Packets:Send = 4,Recveived = 4,Lost = 0

Time:Minimum = 23.0ms,Maximum = 31.0ms,Average=27.75ms

抱歉!评论已关闭.