前言
在Linux下,我们经常使用 ping 命令来检查设备和网络之间的连通性。如果 ping 命令能够顺利执行,那么就可以判断当前设备网络通畅。如果提示“无法连接到目标主机”或“请求超时”,则网络连接可能有问题。
PING命令介绍
ping 命令是一个常用的网络诊断工具,它通过发送 ICMP(Internet Control Message Protocol)回应请求和回应应答来测试与目标主机之间的网络连接。
ping 命令使用的协议是 ICMP。但需要注意的是,虽然 ping 命令使用的是ICMP协议,但是它并不依赖于任何特定的应用层协议。也就是说,ping 命令不使用 TCP、UDP 等应用层协议。相反,它是直接与 IP 协议(网络层协议)交互的。
另外,ping 命令也可以通过其他协议进行操作,例如 IPSec(Internet Protocol Security)等安全协议。在这种情况下,ping命令会使用这些协议来发送和接收数据包。
Ping的原理是:向指定 IP 发送一定长度的数据包,按照约定,若指定 IP 存在的话,会返回同样大小的数据包,若没有在特定时间返回就是超时,就认为指定IP不存在或者网络未连通。因此,可以在代码中通过发送 ICMP 包的方式来判断网络是否连通。
源码
只需要在代码中调用 NetIsOK() 函数即可,该函数返回 true 即表示网络状态良好,否则表示网络状态不连通。本程序中发送了3个 icmp 包,在实际应用中可以根据需要来更改发包数量。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/time.h>
#ifdef _USE_DNS
#include <netdb.h>
#include <string.h>
#define FIXED_TEST_DOMAIN "www.baidu.com"
#else
#define FIXED_TEST_IP "39.156.66.10"
#endif
#define MAX_WAIT_TIME 3 //select接口的最大超时时长,超时则认为不可连通外网.
#define REC_MAX_WAIT_TIME 1 //recvfrom接口的最大超时时长,防止网络不稳定导致的误判.
#define MAX_CNT_PACKETS 3 //总计发送包数量.
#define ICMP_HEADSIZE 8
#define PACKET_SIZE 4096
#define NetStatePrint(fmt, ...) printf("[NetStatus] "fmt"\n", ##__VA_ARGS__)
struct sockaddr_in dest_addr;
int sockfd;
pid_t pid;
int sendPktSize;
char sendPacket[PACKET_SIZE];
char recvPacket[PACKET_SIZE];
static void _CloseSocket(void)
{
close(sockfd);
sockfd = 0;
}
static unsigned short cal_chksum(unsigned short *addr, int len)
{
int nleft = len;
int sum = 0;
unsigned short *w = addr;
unsigned short answer = 0;
/* 把ICMP报头二进制数据以2字节为单位累加起来 */
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
/* 若ICMP报头为奇数个字节,会剩下最后一字节.把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加 */
if (1 == nleft) {
*(unsigned char *)(&answer) = *(unsigned char *)w;
sum += answer;
}
sum = (sum>>16) + (sum&0xffff);
sum += (sum>>16);
return ~sum;
}
static int pack(int pkt_idx, char* sendPkt)
{
int packSize;
struct icmp *icmp;
struct timeval *tval;
icmp = (struct icmp*)sendPkt;
icmp->icmp_type = ICMP_ECHO; //设置类型为ICMP请求报文.
icmp->icmp_code = 0;
icmp->icmp_cksum = 0;
icmp->icmp_id = pid; //设置当前进程ID为ICMP标示符
icmp->icmp_seq = pkt_idx; //设置数据包序号
/* 将当前时间填充为icmp报文的内容 */
tval = (struct timeval *)icmp->icmp_data;
if (0 == gettimeofday(tval, NULL)) {
//printf("<DBG> sec:%ld,usec:%ld since 1970-01-01 00:00:00 UTC\n", tval->tv_sec, tval->tv_usec);
}
packSize = ICMP_HEADSIZE + sizeof(struct timeval); //16 Bytes.
/* recvfrom接口接收时会进行校验值检查,所以必须更新校验值,否则会导致接收失败 */
icmp->icmp_cksum = cal_chksum((unsigned short *)icmp, packSize);
//printf("<DBG> send, icmp_type:%d, icmp_id:%d, icmp_seq:%d, packSize:%d\n", icmp->icmp_type, icmp->icmp_id, icmp->icmp_seq, packSize);
return packSize;
}
static int unpack(int cur_seq, char* buf, int len)
{
int ipHeaderLen;
int recPktSize;
struct ip *ip;
struct icmp *icmp;
ip = (struct ip *)buf;
ipHeaderLen = ip->ip_hl<<2; //求ip报头长度,即ip报头的长度标志乘4.
icmp = (struct icmp *)(buf+ipHeaderLen); //越过ip报头,指向ICMP报头.
recPktSize = len - ipHeaderLen; //ICMP报头及ICMP数据报的总长度.
if (recPktSize < ICMP_HEADSIZE) {
return -1;
}
//printf("<DBG> rec, icmp_type:%d, icmp_id:%d, icmp_seq:%d, packsize:%d\n", icmp->icmp_type, icmp->icmp_id, icmp->icmp_seq, recPktSize);
if ((ICMP_ECHOREPLY == icmp->icmp_type) && (pid == icmp->icmp_id) && (cur_seq == icmp->icmp_seq) && (recPktSize == sendPktSize)) {
return 0;
} else {
return -1;
}
}
static int send_packet(int pktIdx, char* sendPkt)
{
sendPktSize = pack(pktIdx, sendPkt);
if (0 > sendto(sockfd, sendPkt, sendPktSize, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr))) {
NetStatePrint("error: sendto()!");
return -1;
}
return 0;
}
static int recv_packet(int pktIdx, char* recvPkt)
{
struct timeval currentTm;
int len;
int fromlen = sizeof(dest_addr);
struct timeval timeout;
fd_set rfds;
while (1) {
timeout.tv_sec = MAX_WAIT_TIME;
timeout.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(sockfd,&rfds);
select(sockfd+1, &rfds, NULL, NULL, &timeout);
if (FD_ISSET(sockfd,&rfds)) {
if (0 > (len=recvfrom(sockfd, recvPkt, PACKET_SIZE, 0, (struct sockaddr *)&dest_addr, (socklen_t *)&fromlen)))
{
NetStatePrint("error: recvfrom(),%d,%s", errno, strerror(errno));
return -2;
}
if (0 == unpack(pktIdx, recvPkt, len)) {
return 0;
}
} else {
NetStatePrint("error: ICMP Packet rec fail.");
return -1;
}
}
}
bool NetIsOk(void)
{
unsigned int i;
int iFlag;
struct timeval recTimeOut = {REC_MAX_WAIT_TIME, 0};
#ifdef _USE_DNS //如果定义该宏,则可以使用域名进行判断网络连接,例如www.baidu.com
/* 设置目的地址信息 */
struct hostent *host = NULL;
char hostname[32] = {0};
snprintf(hostname, sizeof(hostname)-1, FIXED_TEST_DOMAIN);
bzero(&dest_addr, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
if (NULL == (host=gethostbyname(hostname))) {
NetStatePrint("error: gethostbyname()!");
return false;
}
/*
NetStatePrint("h_name:%s", host->h_name);
char ip_addr[20] = {0};
if (inet_ntop(AF_INET, host->h_addr_list[0], ip_addr, sizeof(ip_addr)-1) != NULL) {
NetStatePrint("IP_addr:%s", ip_addr);
}
*/
bcopy((char*)host->h_addr, (char*)&dest_addr.sin_addr, host->h_length);
//printf("network ordered integer addr: %#x\n", dest_addr.sin_addr);
#else //如果不使用域名,则只能用ip地址直接发送icmp包,例如谷歌的地址:8.8.8.8
if (0 > inet_pton(AF_INET, FIXED_TEST_IP, &dest_addr.sin_addr)) {
NetStatePrint("error: inet_pton()!");
return false;
}
//printf("network ordered integer addr: %#x\n", dest_addr.sin_addr);
#endif
/* 创建原始ICMP套接字 */
if (0 > (sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP))) {
NetStatePrint("error: socket()!");
return false;
}
/* 将Socket设置为非阻塞模式 */
if (0 > (iFlag = fcntl(sockfd,F_GETFL,0))) {
NetStatePrint("error: fcntl(sockfd,F_GETFL,iFlag)!");
_CloseSocket();
return false;
}
iFlag |= O_NONBLOCK;
if (0 > (iFlag = fcntl(sockfd,F_SETFL,iFlag))) {
NetStatePrint("error: fcntl(sockfd,F_SETFL,iFlag)!");
_CloseSocket();
return false;
}
/* 设置接收函数recvfrom的超时时间 */
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &recTimeOut, sizeof(recTimeOut))) {
NetStatePrint("error: setsockopt()!");
_CloseSocket();
return false;
}
pid = getpid();
for (i=0; i<MAX_CNT_PACKETS; i++) {
if (0 != send_packet(i, sendPacket)) {
NetStatePrint("error: send_packet()!");
_CloseSocket();
return false;
}
if (0 == recv_packet(i, recvPacket)) {
_CloseSocket();
return true;
}
}
_CloseSocket();
return false;
}