版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36249516/article/details/78467009
前后创建了三次的MFC工程,就为了实现一个简单的ping 程序,第一天晚上因为头文件的包含关系,调了数个小时;第二次因为返回的数据有无也一直没有成功,经过两次十余小时的尝试,对整个流程有了更为深刻的了解后,再一次尝试后,过程就来的轻松地多。下面是具体流程:
首先先介绍一下简单的ping原理:
部分转载于:http://www.cnblogs.com/ranjiewen/p/5704627.html
C++实现ping功能:
基础知识
ping的过程是向目的IP发送一个type=8的ICMP响应请求报文,目标主机收到这个报文之后,会向源IP(发送方,我)回复一个type=0的ICMP响应应答报文。
那上面的字节、往访时间、TTL之类的信息又是从哪来的呢?这取决于IP和ICMP的头部。
IP头部:
头部内容有点多,我们关心的只有以下几个:
IHL:首部长度。因为IP的头部不是定长的,所以需要这个信息进行IP包的解析,从而找到Data字段的起始点。
另外注意这个IHL是以4个字节为单位的,所以首部实际长度是IHL*4字节。
Time to Live:生存时间,这个就是TTL了。
Data:这部分是IP包的数据,也就是ICMP的报文内容。
ICMP响应请求/应答报文头部:
Type:类型,type=8表示响应请求报文,type=0表示响应应答报文。
Code:代码,与type组合,表示具体的信息,参考这里。
Checksum:检验和,这个是整个ICMP报文的检验和,包括Type、Code、...、Data。
Identifier:标识符,这个一般填入本进程的标识符。
Sequence Number:序号
Data:数据部分
上面是标准的ICMP报文,一般而言,统计ping的往返时间的做法是,在ICMP报文的Data区域写入4个字节的时间戳。
在收到应答报文时,取出这个时间戳与当前的时间对比即可。
Ping程序实现步骤
- 创建类型为SOCK_RAW的一个套接字,同时设定协议IPPROTO_ICMP。
- 创建并初始化ICMP头。
- 调用sendto或WSASendto,将ICMP请求发给远程主机。
- 调用recvfrom或WSARecvfrom,以接收任何ICMP响应。
参考代码:
#include
#include
#include
#pragma comment (lib, "ws2_32.lib")
#define ICMP_ECHOREPLY 0 // ICMP回复应答
#define ICMP_ECHOREQ 8 // ICMP回应请求
#define REQ_DATASIZE 32 // 请求数据报大小
// 定义IP首部格式
typedef struct _IPHeader
{
u_char VIHL; // 版本和首部长度
u_char ToS; // 服务类型
u_short TotalLen; // 总长度
u_short ID; // 标识号
u_short Frag_Flags; // 段偏移量
u_char TTL; // 生存时间
u_char Protocol; // 协议
u_short Checksum; // 首部校验和
struct in_addr SrcIP; // 源IP地址
struct in_addr DestIP; // 目的地址
}IPHDR, *PIPHDR;
// 定义ICMP首部格式
typedef struct _ICMPHeader
{
u_char Type; // 类型
u_char Code; // 代码
u_short Checksum; // 首部校验和
u_short ID; // 标识
u_short Seq; // 序列号
char Data; // 数据
}ICMPHDR, *PICMPHDR;
// 定义ICMP回应请求
typedef struct _ECHOREQUEST
{
ICMPHDR icmpHdr;
DWORD dwTime;
char cData[REQ_DATASIZE];
}ECHOREQUEST, *PECHOREQUEST;
// 定义ICMP回应答复
typedef struct _ECHOREPLY
{
IPHDR ipHdr;
ECHOREQUEST echoRequest;
char cFiller[256];
}ECHOREPLY, *PECHOREPLY;
// 计算校验和
u_short checksum(u_short *buffer, int len)
{
register int nleft = len;
register u_short *w = buffer;
register u_short answer;
register int sum = 0;
// 使用32bit的累加器,进行16bit的反馈计算
while( nleft > 1 ) {
sum += *w++;
nleft -= 2;
}
// 补全奇数位
if( nleft == 1 ) {
u_short u = 0;
*(u_char *)(&u) = *(u_char *)w ;
sum += u;
}
// 将反馈的16bit从高位移至地位
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = ~sum; /* truncate to 16 bits */
return (answer);
}
// 发送回应请求函数
int SendEchoRequest(SOCKET s,struct sockaddr_in *lpstToAddr)
{
static ECHOREQUEST echoReq;
static nId = 1;
static nSeq = 1;
int nRet;
// 填充回应请求消息
echoReq.icmpHdr.Type = ICMP_ECHOREQ;
echoReq.icmpHdr.Code = 0;
echoReq.icmpHdr.Checksum = 0;
echoReq.icmpHdr.ID = nId++;
echoReq.icmpHdr.Seq = nSeq++;
// 填充要发送的数据(随便填写)
for (nRet = 0; nRet < REQ_DATASIZE; nRet++)
echoReq.cData[nRet] = ' '+nRet;
// 储存发送的时间
echoReq.dwTime = GetTickCount();
// 计算回应请求的校验和
echoReq.icmpHdr.Checksum = checksum((u_short *)&echoReq, sizeof(ECHOREQUEST));
// 发送回应请求
nRet = sendto(s, // 建立起的套接字
(LPSTR)&echoReq, // 发送的缓冲区内容
sizeof(ECHOREQUEST),
0, // 标志位
(struct sockaddr*)lpstToAddr, // 发送的目标地址
sizeof(SOCKADDR_IN)); // 地址结构长度
if (nRet == SOCKET_ERROR)
{
printf("sendto() error:%d\n",WSAGetLastError());
}
return (nRet);
}
// 接收应答回复并进行解析
DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL)
{
ECHOREPLY echoReply;
int nRet;
int nAddrLen = sizeof(struct sockaddr_in);
//接收应答回复
nRet = recvfrom(s, // 接收的套接字
(LPSTR)&echoReply, // 接收的缓冲区
sizeof(ECHOREPLY), // 缓冲区长度
0, // 标识
(LPSOCKADDR)lpsaFrom, // 接收的地址
&nAddrLen); // 地址结构长度
// 检验接收结果
if (nRet == SOCKET_ERROR)
{
printf("recvfrom() error:%d\n",WSAGetLastError());
}
// 记录返回的TTL
*pTTL = echoReply.ipHdr.TTL;
//返回应答时间
return(echoReply.echoRequest.dwTime);
}
// 等待回应答复,使用select机制
int WaitForEchoReply(SOCKET s)
{
struct timeval timeout;
fd_set readfds;
readfds.fd_count = 1;
readfds.fd_array[0] = s;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
return(select(1, &readfds, NULL, NULL, &timeout));
}
// Ping功能实现
void Ping(char *pstrHost)
{
SOCKET rawSocket;
LPHOSTENT lpHost;
struct sockaddr_in destIP;
struct sockaddr_in srcIP;
DWORD dwTimeSent;
DWORD dwElapsed;
u_char cTTL;
int nLoop;
int nRet;
// 创建原始套接字,ICMP类型
rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (rawSocket == SOCKET_ERROR)
{
printf("socket() error:%d\n",WSAGetLastError());
return;
}
// 检测目标主机
lpHost = gethostbyname(pstrHost);
if (lpHost == NULL)
{
printf("Host not found: %s\n", pstrHost);
return;
}
// 设置目标机地址
destIP.sin_addr.s_addr = *((u_long FAR *) (lpHost->h_addr));
destIP.sin_family = AF_INET;
destIP.sin_port = 0;
// 提示开始进行Ping
printf("\nPinging %s with %d bytes of data:\n",
inet_ntoa(destIP.sin_addr),
REQ_DATASIZE);
// 发起多次Ping测试
for (nLoop = 0; nLoop < 4; nLoop++)
{
//发送ICMP回应请求
SendEchoRequest(rawSocket, &destIP);
// 使用select()等待回复的数据
nRet = WaitForEchoReply(rawSocket);
if (nRet == SOCKET_ERROR)
{
printf("select() error:%d\n",WSAGetLastError());
break;
}
if (!nRet)
{
printf("\nRequest time out");
break;
}
//接收回复
dwTimeSent = RecvEchoReply(rawSocket, &srcIP, &cTTL);
// 计算花费的时间
dwElapsed = GetTickCount() - dwTimeSent;
printf("\nReply from %s: bytes=%d time=%ldms TTL=%d",
inet_ntoa(srcIP.sin_addr),
REQ_DATASIZE,
dwElapsed,
cTTL);
}
printf("\n");
nRet = closesocket(rawSocket); // 关闭套接字,释放资源
if (nRet == SOCKET_ERROR)
{
printf("closesocket() error:%d\n",WSAGetLastError());
}
}
void main(int argc, char **argv)
{
WSADATA wsd;
int nRet;
// 检测输入的参数
if (argc != 2)
{
printf("\nUsage: ping hostname\n");
return;
}
// 初始化Winsock
if (WSAStartup(MAKEWORD(1,1), &wsd) != 0)
{
printf("加载Winsock失败!\n");
}
//开始Ping
Ping(argv[1]);
// 释放Winsock资源
WSACleanup();
}
参考博文有:http://blog.sina.com.cn/s/blog_47120f8f0101fjul.html
http://blog.csdn.net/ivy8966/article/details/54612601?locationNum=6&fps=1
全部工程资源下载地址:
http://download.csdn.net/download/qq_36249516/10107387