之前有朋友在文章的评论区问过这样的问题,有没有一种通过代码去ping远端地址的办法,并根据是否能ping成功来判断远端设备的网络是否出现故障。答案是肯定的,我之前就帮朋友写过一个小工具,其中就有个ping IP地址的功能。其实很简单,调用系统API函数IcmpSendEcho就可以实现。今天我们就来简单地聊一下这个话题,并讲解一下其中需要注意的一些细节。
我们在无法访问远端网络地址时,我们的第一反应是打开cmd命令窗口,去ping一下这个远端地址,看看能不能ping通。远端设备的地址可以是IP,也可以是域名。执行ping命令时是给远端发送3个ping包,看远端的回应情况,网络正常时会有如下的回应:
根据ping的结果可以看出网络连接是否正常,网络是否有很大的延时,网络是否有丢包。
我们也可以在后面添加一个-t的参数,一直发ping包:
看看是否存在网路时好时坏,网络抖动的情况。
下面我们来了解一下ping指令相关的内容。ping (Packet Internet Groper),因特网包探索器,用于检查网络是否通畅或者网络连接速度的命令。执行ping操作时会给远端的IP发送若干条ICMP报文信息,并根据远端是否回应报文信息或者回应报文的时间来判断网络的连接状况。发起ping操作时发出的数据包是通过ICMP协议和远端进行交互的。
ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,它属于网络层协议,主要用于在主机与路由器之间传递控制信息,包括报告错误、交换受限控制和状态信息等。当遇到IP数据无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。
我们可以通过代码区触发ping操作,然后把ping打印出的信息重定向输出到匿名管道中,然后从管道中读出ping操作打印出来的信息。最后对ping操作打印出的信息进行分析,分析出网络连接情况,网络延时,分析丢包率等信息。
将ping指令输出的信息重定向到匿名管道中的代码如下所示:
// 1、创建匿名管道
SECURITY_ATTRIBUTES sa;
ZeroMemory( &sa, sizeof(sa) );
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
if( !CreatePipe( &hRead, &hWrite, &sa, 0 ) )
{
return 0;
}
// 2、设置ping参数,启动系统默认进程去执行ping操作,系统会使用cmd去执行
STARTUPINFO si;
PROCESS_INFORMATION pi;
si.cb = sizeof(STARTUPINFO);
// 让ping命令的执行结果信息重定向到管道中,即将ping的信息写道管道中
si.hStdError = hWrite;
si.hStdOutput = hWrite;
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
char cmdLine[MAX_PATH] = "ping 192.168.0.8"; //
f( !CreateProcess( NULL, cmdLine, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi ) )
{
return 0;
}
// 3、等待ping命令执行完成,进程退出
WaitForSingleObject( pi.hProcess, INFINITE );
// 4、ping的信息已经写到匿名管道中,此处从管道中读出ping的信息
char szPingText[1024] = {0};
DWORD dwReadBytes = 0;
PeekNamedPipe( hRead, szPingText, sizeof(szPingText), &dwReadBytes, NULL, NULL );
IcmpSendEcho接口是给远端发送一个ICMP数据包,然后等待远端的回应。该接口中需要传入目标ip地址,还可以设置等待远端回应的超时时间。
根据微软MSDN上对该接口的说明,当IcmpSendEcho执行成功时会返回非0,执行失败时会返回0,是不是仅仅通过该接口的返回值就能判断出能否连上远端呢?MSDN上给出的示例代码也是仅仅判断返回值的:
#include
#include
#include
#include
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")
int __cdecl main(int argc, char **argv) {
// Declare and initialize variables
HANDLE hIcmpFile;
unsigned long ipaddr = INADDR_NONE;
DWORD dwRetVal = 0;
char SendData[32] = "Data Buffer";
LPVOID ReplyBuffer = NULL;
DWORD ReplySize = 0;
// Validate the parameters
if (argc != 2) {
printf("usage: %s IP address\n", argv[0]);
return 1;
}
ipaddr = inet_addr(argv[1]);
if (ipaddr == INADDR_NONE) {
printf("usage: %s IP address\n", argv[0]);
return 1;
}
hIcmpFile = IcmpCreateFile();
if (hIcmpFile == INVALID_HANDLE_VALUE) {
printf("\tUnable to open handle.\n");
printf("IcmpCreatefile returned error: %ld\n", GetLastError() );
return 1;
}
ReplySize = sizeof(ICMP_ECHO_REPLY) + sizeof(SendData);
ReplyBuffer = (VOID*) malloc(ReplySize);
if (ReplyBuffer == NULL) {
printf("\tUnable to allocate memory\n");
return 1;
}
dwRetVal = IcmpSendEcho(hIcmpFile, ipaddr, SendData, sizeof(SendData),
NULL, ReplyBuffer, ReplySize, 1000);
if (dwRetVal != 0) {
PICMP_ECHO_REPLY pEchoReply = (PICMP_ECHO_REPLY)ReplyBuffer;
struct in_addr ReplyAddr;
ReplyAddr.S_un.S_addr = pEchoReply->Address;
printf("\tSent icmp message to %s\n", argv[1]);
if (dwRetVal > 1) {
printf("\tReceived %ld icmp message responses\n", dwRetVal);
printf("\tInformation from the first response:\n");
}
else {
printf("\tReceived %ld icmp message response\n", dwRetVal);
printf("\tInformation from this response:\n");
}
printf("\t Received from %s\n", inet_ntoa( ReplyAddr ) );
printf("\t Status = %ld\n",
pEchoReply->Status);
printf("\t Roundtrip time = %ld milliseconds\n",
pEchoReply->RoundTripTime);
}
else {
printf("\tCall to IcmpSendEcho failed.\n");
printf("\tIcmpSendEcho returned error: %ld\n", GetLastError() );
return 1;
}
return 0;
}
后来通过测试发现,当我们设置一个ping不通的IP时,IcmpSendEcho有时返回0,有时返回非0,所以根据IcmpSendEcho接口的返回值判断是否能连接上远端是不靠谱的。通过搜索得知,可以判断IcmpSendEcho接口传出的结构体ICMP_ECHO_REPLY中的Status字段来判断,如果远端可以ping通,IcmpSendEcho接口会返回非0,并且ICMP_ECHO_REPLY结构体中的Status字段等于IP_SUCCESS,所以判断远端是否ping成功的代码如下:
ICMP_ECHO_REPLY reply;
IPAddr ipAddr = inet_addr( achDevIp );
DWORD dwRet = IcmpSendEcho( hIcmp, ipAddr, NULL, 0, 0, &reply, sizeof(reply), 1000 );
if ( dwRet != 0 )
{
if ( reply.Status != IP_SUCCESS )
{
// ping成功
//::MessageBoxA( NULL, "成功", "Tip", MB_OK );
}
else
{
// ping失败
}
}
else
{
// ping失败
}
同时ICMP_ECHO_REPLY结构体中的RoundTripTime字段对应的是远端回复数据的时间长短(以毫秒为单位),通过该字段可以判断对远端的网络状况的好坏。如果回复时间在几毫秒到几十毫秒,则表示网络是正常的;如果回复时间是几百毫秒,甚至上千秒,则表示网络状况不太好了,有很大的延时。