一、数据结构
首先根据IP数据包格式(图下图)定义IP数据包头的数据结构

typedef struct tagIPHDR // IP数据包头部 { u_char VIHL; // 版本号(4)+头长度(4) u_char TOS; // 服务类型(8) short TotLen; // 总长度(16) short ID; // 标识(16) short FlagOff; // 标志(3)+片偏移(13) u_char TTL; // 生存时间TTL(8) u_short CheckSum; // 头部校验和(16) in_addr iaSrc; // 源IP地址(32) in_addr iaDst; // 目标IP地址(32) } IPHDR, *PIPHDR;
然后根据ICMP回送请求与应答报文格式定义ICMP的数据结构

typedef struct tagICMPHDR // ICMP回送请求与应带ICMP报文 { u_char Type; // 类型(8) u_char Code; // 代码(8) u_short Checksum; // 校验和(16) u_short ID; // 标识符(16) u_short Seq; // 序号(16) char Data; // 任选数据 } ICMPHDR, *PICMPHDR;
然后分别定义请求回送的数据长度
#define REQ_DATASIZE 32
请求回送的数据结构
typedef struct tagECHOREQUEST { ICMPHDR icmpHdr; DWORD dwTime; char cData[REQ_DATASIZE]; } ECHOREQUEST, *PECHOREQUEST;
ICMP回送应答的数据结构
typedef struct tagECHOREPLY { IPHDR ipHdr; ECHOREQUEST echoRequest; char cFiller[256]; } ECHOREPLY, *PECHOREPLY;
二、函数实现
(1)SendEchoRequest
函数功能是发送回送请求数据包,首先定义三个静态变量
static ECHOREQUEST echoReq; // 回送请求数据结构
static nId = 1; // 标识符
static nSeq = 1; // 序号
然后填写回送请求信息
echoReq.icmpHdr.Type = ICMP_ECHOREQ; // 类型
echoReq.icmpHdr.Code = 0; // 代码
echoReq.icmpHdr.Checksum = 0; // 校验和
echoReq.icmpHdr.ID = nId++; // 标识符
echoReq.icmpHdr.Seq = nSeq++; // 序号
填写要发送的数据
for (i = 0; i < REQ_DATASIZE; i++)
{
echoReq.cData[i] = ' ' + i;
}
保存发送时间
echoReq.dwTime = GetTickCount();
数据存入包中并计算校验和
echoReq.icmpHdr.Checksum = in_chsum((u_short*)&echoReq, sizeof(ECHOREQUEST));
发送回送请求
nRet = sendto(s,
(LPSTR)&echoReq,
sizeof(ECHOREQUEST),
0,
(LPSOCKADDR)lpstToAddr,
sizeof(SOCKADDR_IN));
(2)RecvEchoReply
函数功能为接收回送应答数据
DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL)
{
ECHOREPLY echoReply; // 回送应答数据结构
int nRet;
int nAddrLen = sizeof(sockaddr_in);
// 接受回送应答
nRet = recvfrom(s,
(LPSTR)&echoReply,
sizeof(ECHOREPLY),
0,
(LPSOCKADDR)lpsaFrom,
&nAddrLen);
// 检查返回的值
if (nRet == SOCKET_ERROR)
{
ReportError("recvfrom()");
}
*pTTL = echoReply.ipHdr.TTL; // 取得TTL值
return (echoReply.echoRequest.dwTime); // 返回所用时间
}
(3)WaitForEchoReply
函数功能:等待套接子s是否有数据可读
int WaitForEchoReply(SOCKET s)
{
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));
}
(3)in_chsum
函数功能计算校验和
u_short in_chsum(u_short *addr, int len)
{
register int nLeft = len;
register u_short *w = addr;
register u_short answer;
register int sum = 0;
while (nLeft > 1)
{
sum += *w++;
nLeft -= 2;
}
if (nLeft == 1)
{
u_short u = 0;
*(u_char*)(&u) = *(u_char*)w;
sum += u;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
(4)main函数的实现
第一步:定义Winsock数据结构wsaData并新建版本号1.1
第二步:调用WSAStartup初始化wsaData
第三步:调用Ping函数
第四步:调用WSACleanup释放Winsock
void main(int argc, char **argv)
{
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(1, 1); // Winsock1.1
int nRet;
// 命令行参数检查
if (argc != 2)
{
fprintf(stderr, "/nUsage: ping hostname/n");
return;
}
// 初始化Winsock
nRet = WSAStartup(wVersionRequested, &wsaData);
if (nRet)
{
fprintf(stderr, "/nError initializing Winsock/n");
return;
}
if (wsaData.wVersion != wVersionRequested)
{
fprintf(stderr, "/nWinsock version not supported/n");
return;
}
// 调用ping函数
Ping(argv[1]);
//Ping("www.sina.com");
// 释放Winsock
WSACleanup();
}
(5)Ping
函数功能:实现ping功能
定义函数用到的数据
SOCKET rawSocket; // 原始套接字
LPHOSTENT lpHost; // 主机信息
sockaddr_in saDest; // 目的地址
sockaddr_in saSrc; // 源地址
DWORD dwTimeSent; // 发送时间
DWORD dwElapsed; // 延迟时间
然后创建一个原始套接字
创建一个原始套接口,协议为ICMP协议
rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
根据用户输入的目的地址获取
lpHost = gethostbyname(pstrHost);
设置目标套接口地址
saDest.sin_addr.s_addr = *((u_long FAR*)(lpHost->h_addr));
saDest.sin_family = AF_INET;
saDest.sin_port = 0;
输出ping程序的提示信息
printf("/nPinging %s [%s] with %d bytes of data:/n",
pstrHost,
inet_ntoa(saDest.sin_addr),
REQ_DATASIZE);
发送ICMP回送请求
SendEchoRequest(rawSocket, &saDest);
使用select()等待接收回送的数据
WaitForEchoReply(rawSocket);
接收应答
dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL);
计算传输时间,并输出提示信息
dwElapsed = GetTickCount() - dwTimeSent;
答应应答信息
printf("/nReply from: %s: bytes=%d time=%ldms TTL=%d",
inet_ntoa(saSrc.sin_addr),
REQ_DATASIZE,
dwElapsed,
cTTL);
}
// 关闭套接字
nRet = closesocket(rawSocket);
附:程序源代码
// ping.h // 在该头文件中定义了IP和ICMP协议头的结构 #pragma pack(1) #define ICMP_ECHOREPLY 0 #define ICMP_ECHOREQ 8 typedef struct tagIPHDR // IP数据包头部 { u_char VIHL; // 版本号(4)+头长度(4) u_char TOS; // 服务类型(8) short TotLen; // 总长度(16) short ID; // 标识(16) short FlagOff; // 标志(3)+片偏移(13) u_char TTL; // 生存时间TTL(8) u_short CheckSum; // 头部校验和(16) in_addr iaSrc; // 源IP地址(32) in_addr iaDst; // 目标IP地址(32) } IPHDR, *PIPHDR; typedef struct tagICMPHDR // ICMP回送请求与应带ICMP报文 { u_char Type; // 类型(8) u_char Code; // 代码(8) u_short Checksum; // 校验和(16) u_short ID; // 标识符(16) u_short Seq; // 序号(16) char Data; // 任选数据 } ICMPHDR, *PICMPHDR; // 请求回送的数据长度 #define REQ_DATASIZE 32 // ICMP回送请求的数据结构 typedef struct tagECHOREQUEST { ICMPHDR icmpHdr; DWORD dwTime; char cData[REQ_DATASIZE]; } ECHOREQUEST, *PECHOREQUEST; // ICMP回送应答 typedef struct tagECHOREPLY { IPHDR ipHdr; ECHOREQUEST echoRequest; char cFiller[256]; } ECHOREPLY, *PECHOREPLY; #pragma pack()
// ping.cpp // 实现简易ping功能 #include <stdio.h> #include <stdlib.h> #include <winsock.h> #include "ping.h" void Ping(LPCSTR pstrHost); void ReportError(LPCSTR pstrFrom); int WaitForEchoReply(SOCKET s); u_short in_chsum(u_short *addr, int len); // ICMP 回送请求和应答函数声明 int SendEchoRequest(SOCKET, LPSOCKADDR_IN); DWORD RecvEchoReply(SOCKET, LPSOCKADDR_IN, u_char *); // 主程序 void main(int argc, char **argv) { WSADATA wsaData; WORD wVersionRequested = MAKEWORD(1, 1); // Winsock1.1 int nRet; // 命令行参数检查 if (argc != 2) { fprintf(stderr, "/nUsage: ping hostname/n"); return; } // 初始化Winsock nRet = WSAStartup(wVersionRequested, &wsaData); if (nRet) { fprintf(stderr, "/nError initializing Winsock/n"); return; } if (wsaData.wVersion != wVersionRequested) { fprintf(stderr, "/nWinsock version not supported/n"); return; } // 调用ping函数 Ping(argv[1]); //Ping("www.sina.com"); // 释放Winsock WSACleanup(); } void Ping(LPCSTR pstrHost) { SOCKET rawSocket; // 原始套接字 LPHOSTENT lpHost; // 主机信息 sockaddr_in saDest; // 目的地址 sockaddr_in saSrc; // 源地址 DWORD dwTimeSent; // 发送时间 DWORD dwElapsed; // 延迟时间 u_char cTTL; int nLoop; int nRet; // 创建一个原始套接口 rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (rawSocket == SOCKET_ERROR) { ReportError("socket()"); return; } lpHost = gethostbyname(pstrHost); if (lpHost == NULL) { fprintf(stderr, "/nHost not found: %s/n", pstrHost); return; } // 设置目标套接口地址 saDest.sin_addr.s_addr = *((u_long FAR*)(lpHost->h_addr)); saDest.sin_family = AF_INET; saDest.sin_port = 0; // 输出ping程序的提示信息 printf("/nPinging %s [%s] with %d bytes of data:/n", pstrHost, inet_ntoa(saDest.sin_addr), REQ_DATASIZE); // 控制ping执行的次数 for (nLoop = 0; nLoop < 4; nLoop++) { // 发送ICMP回送请求 SendEchoRequest(rawSocket, &saDest); // 使用select()等待接收回送的数据 nRet = WaitForEchoReply(rawSocket); if (nRet == SOCKET_ERROR) { ReportError("select()"); break; } if (!nRet) { printf("/nTimeOut/n"); break; } // 接收应答 dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL); // 计算传输时间,并输出提示信息 dwElapsed = GetTickCount() - dwTimeSent; printf("/nReply from: %s: bytes=%d time=%ldms TTL=%d", inet_ntoa(saSrc.sin_addr), REQ_DATASIZE, dwElapsed, cTTL); } printf("/n"); // 关闭套接字 nRet = closesocket(rawSocket); if (nRet == SOCKET_ERROR) { ReportError("closesocket()"); } } int SendEchoRequest(SOCKET s, LPSOCKADDR_IN lpstToAddr) { static ECHOREQUEST echoReq; // 回送请求数据结构 static nId = 1; // 标识符 static nSeq = 1; // 序号 int nRet; int i; // 填写回送请求信息 echoReq.icmpHdr.Type = ICMP_ECHOREQ; // 类型 echoReq.icmpHdr.Code = 0; // 代码 echoReq.icmpHdr.Checksum = 0; // 校验和 echoReq.icmpHdr.ID = nId++; // 标识符 echoReq.icmpHdr.Seq = nSeq++; // 序号 // 填写要发送的数据 for (i = 0; i < REQ_DATASIZE; i++) { echoReq.cData[i] = ' ' + i; } // 保存发送时间 echoReq.dwTime = GetTickCount(); // 数据存入包中并计算校验和 echoReq.icmpHdr.Checksum = in_chsum((u_short*)&echoReq, sizeof(ECHOREQUEST)); // 发送回送请求 nRet = sendto(s, (LPSTR)&echoReq, sizeof(ECHOREQUEST), 0, (LPSOCKADDR)lpstToAddr, sizeof(SOCKADDR_IN)); if (nRet == SOCKET_ERROR) { ReportError("sendto()"); } return nRet; } DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL) { ECHOREPLY echoReply; // 回送应答数据结构 int nRet; int nAddrLen = sizeof(sockaddr_in); // 接受回送应答 nRet = recvfrom(s, (LPSTR)&echoReply, sizeof(ECHOREPLY), 0, (LPSOCKADDR)lpsaFrom, &nAddrLen); // 检查返回的值 if (nRet == SOCKET_ERROR) { ReportError("recvfrom()"); } *pTTL = echoReply.ipHdr.TTL; // 取得TTL值 return (echoReply.echoRequest.dwTime); // 返回所用时间 } void ReportError(LPCSTR pstrFrom) { fprintf(stderr, "/n %d error: /n", WSAGetLastError()); } // 等待套接子s是否有数据可读 int WaitForEchoReply(SOCKET s) { 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)); } u_short in_chsum(u_short *addr, int len) { register int nLeft = len; register u_short *w = addr; register u_short answer; register int sum = 0; while (nLeft > 1) { sum += *w++; nLeft -= 2; } if (nLeft == 1) { u_short u = 0; *(u_char*)(&u) = *(u_char*)w; sum += u; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return (answer); }
本文介绍了一个简易Ping程序的设计与实现过程,包括数据结构定义、关键函数的实现细节等。该程序能够发送ICMP回送请求并接收应答,计算往返时间。
382

被折叠的 条评论
为什么被折叠?



