一、資料結構
首先根據IP資料包格式(圖)定義IP資料包頭的資料結構
typedef struct tagIPHDR// IP資料包頭部<br />{<br />u_char VIHL;// 版本號碼(4)+頭長度(4)<br />u_char TOS;// 服務類型(8)<br />short TotLen;// 總長度(16)<br />short ID;// 標識(16)<br />short FlagOff;// 標誌(3)+片位移(13)<br />u_char TTL;// 存留時間TTL(8)<br />u_short CheckSum;// 頭部校正和(16)<br />in_addr iaSrc;// 源IP地址(32)<br />in_addr iaDst;// 目標IP地址(32)<br />} IPHDR, *PIPHDR;
然後根據ICMP回送請求與應答報文格式定義ICMP的資料結構
typedef struct tagICMPHDR// ICMP回送請求與應帶ICMP報文<br />{<br />u_char Type;// 類型(8)<br />u_char Code;// 代碼(8)<br />u_short Checksum;// 校正和(16)<br />u_short ID;// 標識符(16)<br />u_short Seq;// 序號(16)<br />char Data;// 任選資料<br />} ICMPHDR, *PICMPHDR;
然後分別定義請求回送的資料長度
#define REQ_DATASIZE 32
請求回送的資料結構
typedef struct tagECHOREQUEST<br />{<br />ICMPHDR icmpHdr;<br />DWORD dwTime;<br />char cData[REQ_DATASIZE];<br />} ECHOREQUEST, *PECHOREQUEST;
ICMP回送應答的資料結構
typedef struct tagECHOREPLY<br />{<br />IPHDR ipHdr;<br />ECHOREQUEST echoRequest;<br />char cFiller[256];<br />} 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<br />// 在該標頭檔中定義了IP和ICMP協議頭的結構<br />#pragma pack(1)<br />#define ICMP_ECHOREPLY 0<br />#define ICMP_ECHOREQ 8<br />typedef struct tagIPHDR// IP資料包頭部<br />{<br />u_char VIHL;// 版本號碼(4)+頭長度(4)<br />u_char TOS;// 服務類型(8)<br />short TotLen;// 總長度(16)<br />short ID;// 標識(16)<br />short FlagOff;// 標誌(3)+片位移(13)<br />u_char TTL;// 存留時間TTL(8)<br />u_short CheckSum;// 頭部校正和(16)<br />in_addr iaSrc;// 源IP地址(32)<br />in_addr iaDst;// 目標IP地址(32)<br />} IPHDR, *PIPHDR;<br />typedef struct tagICMPHDR// ICMP回送請求與應帶ICMP報文<br />{<br />u_char Type;// 類型(8)<br />u_char Code;// 代碼(8)<br />u_short Checksum;// 校正和(16)<br />u_short ID;// 標識符(16)<br />u_short Seq;// 序號(16)<br />char Data;// 任選資料<br />} ICMPHDR, *PICMPHDR;<br />// 請求回送的資料長度<br />#define REQ_DATASIZE 32<br />// ICMP回送請求的資料結構<br />typedef struct tagECHOREQUEST<br />{<br />ICMPHDR icmpHdr;<br />DWORD dwTime;<br />char cData[REQ_DATASIZE];<br />} ECHOREQUEST, *PECHOREQUEST;<br />// ICMP回送應答<br />typedef struct tagECHOREPLY<br />{<br />IPHDR ipHdr;<br />ECHOREQUEST echoRequest;<br />char cFiller[256];<br />} ECHOREPLY, *PECHOREPLY;<br />#pragma pack()
// ping.cpp<br />// 實現簡易ping功能<br />#include <stdio.h><br />#include <stdlib.h><br />#include <winsock.h><br />#include "ping.h"<br />void Ping(LPCSTR pstrHost);<br />void ReportError(LPCSTR pstrFrom);<br />int WaitForEchoReply(SOCKET s);<br />u_short in_chsum(u_short *addr, int len);<br />// ICMP 回送請求和應答函式宣告<br />int SendEchoRequest(SOCKET, LPSOCKADDR_IN);<br />DWORD RecvEchoReply(SOCKET, LPSOCKADDR_IN, u_char *);<br />// 主程式<br />void main(int argc, char **argv)<br />{<br />WSADATA wsaData;<br />WORD wVersionRequested = MAKEWORD(1, 1); // Winsock1.1<br />int nRet;</p><p>// 命令列參數檢查<br />if (argc != 2)<br />{<br />fprintf(stderr, "/nUsage: ping hostname/n");<br />return;<br />}<br />// 初始化Winsock<br />nRet = WSAStartup(wVersionRequested, &wsaData);<br />if (nRet)<br />{<br />fprintf(stderr, "/nError initializing Winsock/n");<br />return;<br />}<br />if (wsaData.wVersion != wVersionRequested)<br />{<br />fprintf(stderr, "/nWinsock version not supported/n");<br />return;<br />}<br />// 調用ping函數<br />Ping(argv[1]);<br />//Ping("www.sina.com");<br />// 釋放Winsock<br />WSACleanup();<br />}<br />void Ping(LPCSTR pstrHost)<br />{<br />SOCKET rawSocket;// 原始通訊端<br />LPHOSTENT lpHost;// 主機資訊<br />sockaddr_in saDest;// 目的地址<br />sockaddr_in saSrc;// 源地址<br />DWORD dwTimeSent;// 發送時間<br />DWORD dwElapsed;// 延遲時間<br />u_char cTTL;<br />int nLoop;<br />int nRet;<br />// 建立一個原始套介面<br />rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);<br />if (rawSocket == SOCKET_ERROR)<br />{<br />ReportError("socket()");<br />return;<br />}<br />lpHost = gethostbyname(pstrHost);<br />if (lpHost == NULL)<br />{<br />fprintf(stderr, "/nHost not found: %s/n", pstrHost);<br />return;<br />}<br />// 設定目標套介面地址<br />saDest.sin_addr.s_addr = *((u_long FAR*)(lpHost->h_addr));<br />saDest.sin_family = AF_INET;<br />saDest.sin_port = 0;<br />// 輸出ping程式的提示資訊<br />printf("/nPinging %s [%s] with %d bytes of data:/n",<br />pstrHost,<br />inet_ntoa(saDest.sin_addr),<br />REQ_DATASIZE);<br />// 控制ping執行的次數<br />for (nLoop = 0; nLoop < 4; nLoop++)<br />{<br />// 發送ICMP回送請求<br />SendEchoRequest(rawSocket, &saDest);<br />// 使用select()等待接收回送的資料<br />nRet = WaitForEchoReply(rawSocket);<br />if (nRet == SOCKET_ERROR)<br />{<br />ReportError("select()");<br />break;<br />}<br />if (!nRet)<br />{<br />printf("/nTimeOut/n");<br />break;<br />}<br />// 接收應答<br />dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL);<br />// 計算傳輸時間,並輸出提示資訊<br />dwElapsed = GetTickCount() - dwTimeSent;<br />printf("/nReply from: %s: bytes=%d time=%ldms TTL=%d",<br />inet_ntoa(saSrc.sin_addr),<br />REQ_DATASIZE,<br />dwElapsed,<br />cTTL);<br />}<br />printf("/n");<br />// 關閉通訊端<br />nRet = closesocket(rawSocket);<br />if (nRet == SOCKET_ERROR)<br />{<br />ReportError("closesocket()");<br />}<br />}<br />int SendEchoRequest(SOCKET s, LPSOCKADDR_IN lpstToAddr)<br />{<br />static ECHOREQUEST echoReq;// 回送請求資料結構<br />static nId = 1;// 標識符<br />static nSeq = 1;// 序號<br />int nRet;<br />int i;<br />// 填寫回送請求資訊<br />echoReq.icmpHdr.Type = ICMP_ECHOREQ; // 類型<br />echoReq.icmpHdr.Code = 0; // 代碼<br />echoReq.icmpHdr.Checksum = 0; // 校正和<br />echoReq.icmpHdr.ID = nId++; // 標識符<br />echoReq.icmpHdr.Seq = nSeq++; // 序號<br />// 填寫要發送的資料<br />for (i = 0; i < REQ_DATASIZE; i++)<br />{<br />echoReq.cData[i] = ' ' + i;<br />}<br />// 儲存發送時間<br />echoReq.dwTime = GetTickCount();<br />// 資料存入包中並計算校正和<br />echoReq.icmpHdr.Checksum = in_chsum((u_short*)&echoReq, sizeof(ECHOREQUEST));<br />// 發送回送請求<br />nRet = sendto(s,<br />(LPSTR)&echoReq,<br />sizeof(ECHOREQUEST),<br />0,<br />(LPSOCKADDR)lpstToAddr,<br />sizeof(SOCKADDR_IN));<br />if (nRet == SOCKET_ERROR)<br />{<br />ReportError("sendto()");<br />}<br />return nRet;<br />}<br />DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL)<br />{<br />ECHOREPLY echoReply;// 回送應答資料結構<br />int nRet;<br />int nAddrLen = sizeof(sockaddr_in);<br />// 接受回送應答<br />nRet = recvfrom(s,<br />(LPSTR)&echoReply,<br />sizeof(ECHOREPLY),<br />0,<br />(LPSOCKADDR)lpsaFrom,<br />&nAddrLen);<br />// 檢查返回的值<br />if (nRet == SOCKET_ERROR)<br />{<br />ReportError("recvfrom()");<br />}<br />*pTTL = echoReply.ipHdr.TTL;// 取得TTL值<br />return (echoReply.echoRequest.dwTime);// 返回所用時間<br />}<br />void ReportError(LPCSTR pstrFrom)<br />{<br />fprintf(stderr, "/n %d error: /n", WSAGetLastError());<br />}<br />// 等待套接子s是否有資料可讀<br />int WaitForEchoReply(SOCKET s)<br />{<br />timeval Timeout;<br />fd_set readfds;<br />readfds.fd_count = 1;<br />readfds.fd_array[0] = s;<br />Timeout.tv_sec = 5;<br />Timeout.tv_usec = 0;<br />return (select(1, &readfds, NULL, NULL, &Timeout));<br />}<br />u_short in_chsum(u_short *addr, int len)<br />{<br />register int nLeft = len;<br />register u_short *w = addr;<br />register u_short answer;<br />register int sum = 0;<br />while (nLeft > 1)<br />{<br />sum += *w++;<br />nLeft -= 2;<br />}<br />if (nLeft == 1)<br />{<br />u_short u = 0;<br />*(u_char*)(&u) = *(u_char*)w;<br />sum += u;<br />}<br />sum = (sum >> 16) + (sum & 0xffff);<br />sum += (sum >> 16);<br />answer = ~sum;<br />return (answer);<br />}