這個程序主要運用了ICMPv4協議(回顯請求)來測試本機到某服務器的網絡是否連通,因為其中用到了原始套接字,所以運行該程序需要管理員權限。 PS:本程序只支持一種輸入方式:./myping <hostname>,不支持其他參數。 思路: 1:根據hostname參數創建原始套接字。 2:每隔1秒鐘向服務器發送一個ICMP回顯請求。 3:循環接收從服務器返回的應答並處理其數據。 上代碼: [cpp] #include <signal.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <arpa/inet.h> #include <netdb.h> #include <sys/un.h> //各種緩沖區的長度 #define BUFSIZE 1500 //ICMP回顯請求的長度 #define DATA_LEN 56 struct proto { struct sockaddr *sasend; /* sockaddr{} for send, from getaddrinfo */ struct sockaddr *sarecv; /* sockaddr{} for receiving */ socklen_t salen; /* length of sockaddr{}s */ int icmpproto; /* IPPROTO_xxx value for ICMP */ }; //全局變量 pid_t g_pid; int g_sockfd; struct proto g_proto = { NULL, NULL, 0, IPPROTO_ICMP }; //處理服務器返回的ICMP回顯信息 void proc_msg(char *, ssize_t, struct msghdr *, struct timeval *); //發送ICMP回顯請求 void send_msg(void); //循環發送、接收信息 void readloop(void); //定時器入門函數,每隔一秒一次發送ICMP請求 void sig_alrm(int); //計算兩個時間之間的間隔 void tv_sub(struct timeval *, struct timeval *); //獲取服務器的地址等信息 struct addrinfo *host_serv(const char *host, const char *serv, int family, int socktype); //根據服務器信息,得到服務器的IP地址 char *sock_ntop_host(const struct sockaddr *sa, socklen_t salen); //計算校驗和 uint16_t in_cksum(uint16_t *addr, int len); //輸出錯誤信息,退出程序 void error_quit(const char *str); int main(int argc, char **argv) { int c; struct addrinfo *ai; struct sockaddr_in *sin; char *ip_address; char *host; //本程序只支持一種輸入方式:./myping <hostname> if( argc != 2 ) error_quit("usage: myping <hostname>"); host = argv[1]; //將pid的高二位全置為0,ICMP的ID只有16位 g_pid = getpid() & 0xffff; //設置定時器,每秒鐘向服務器發送一次請求 signal(SIGALRM, sig_alrm); //獲取服務器的信息(addrinfo結構) ai = host_serv(host, NULL, 0, 0); ip_address = sock_ntop_host(ai->ai_addr, ai->ai_addrlen); printf("PING %s (%s): %d data bytes\n", ai->ai_canonname ? ai->ai_canonname : ip_address, ip_address, DATA_LEN); //如果返回的協議簇不是AF_INET(IPv4),則退出 if ( ai->ai_family != AF_INET ) error_quit("unknown address family"); //設置proto結構體 g_proto.sasend = ai->ai_addr; g_proto.sarecv = calloc(1, ai->ai_addrlen); g_proto.salen = ai->ai_addrlen; //開始循環發送/接收請求 readloop(); return 0; } void readloop(void) { int size; char recvbuf[BUFSIZE]; char controlbuf[BUFSIZE]; struct msghdr msg; struct iovec iov; ssize_t n; struct timeval tval; //創建一個IPv4的原始套接字 g_sockfd = socket(g_proto.sasend->sa_family, SOCK_RAW, g_proto.icmpproto); if( -1 == g_sockfd ) error_quit("socket error"); //放棄管理員權限 //這個程序中,只用創建原始套接字時需要管理員權限 setuid(getuid()); //設置socket的接收緩沖區 size = 60 * 1024; setsockopt(g_sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); //發出第一個請求 sig_alrm(SIGALRM); //為recvmsg調用設置msghdr結構 iov.iov_base = recvbuf; iov.iov_len = sizeof(recvbuf); msg.msg_name = g_proto.sarecv; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = controlbuf; //開始死循環,不斷讀取和處理從服務器中返回的信息 while( 1 ) { msg.msg_namelen = g_proto.salen; msg.msg_controllen = sizeof(controlbuf); n = recvmsg(g_sockfd, &msg, 0); if (n < 0) { if (errno == EINTR) continue; else error_quit("recvmsg error"); } //分析返回內容,產生輸出 gettimeofday(&tval, NULL); proc_msg(recvbuf, n, &msg, &tval); } } void proc_msg(char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv) { int hlen1, icmplen; double rtt; struct ip *ip; struct icmp *icmp; struct timeval *tvsend; //將服務器返回的字符串強轉為ip結構 ip = (struct ip *) ptr; //得到IP表頭的長度 hlen1 = ip->ip_hl << 2; //如果不是ICMP的應答,則返回 if (ip->ip_p != IPPROTO_ICMP) return; icmp = (struct icmp *) (ptr + hlen1); //長度不足,不是合法應答 if ( (icmplen = len - hlen1) < 8) return; //不是回顯應答,返回 if (icmp->icmp_type != ICMP_ECHOREPLY) return; //不是我們發出請求的應答,返回 if (icmp->icmp_id != g_pid) return; //長度不足,非法應答 if (icmplen < 16) return; //計算網絡延時 tvsend = (struct timeval *) icmp->icmp_data; tv_sub(tvrecv, tvsend); rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0; //輸出信息 printf("%d bytes from %s: seq=%u, ttl=%d, rtt=%.3f ms\n", icmplen, sock_ntop_host(g_proto.sarecv, g_proto.salen), icmp->icmp_seq, ip->ip_ttl, rtt); } void send_msg(void) { int len; int res; struct icmp *icmp; char sendbuf[BUFSIZE]; static int nsent = 0; //根據ICMPv4協議來設置發送信息 icmp = (struct icmp *) sendbuf; //ICMP回顯請求 icmp->icmp_type = ICMP_ECHO; icmp->icmp_code = 0; //ICMP標識符字段為本進程的PID icmp->icmp_id = g_pid; //ICMP序列號字段為不斷遞增的全局變量nsent icmp->icmp_seq = nsent++; //ICMP數據字段為當前時間截,空白部分填充0xa5 memset(icmp->icmp_data, 0xa5, DATA_LEN); gettimeofday((struct timeval *)icmp->icmp_data, NULL); //計算並填充校驗和 len = 8 + DATA_LEN; icmp->icmp_cksum = 0; icmp->icmp_cksum = in_cksum((u_short *) icmp, len); //發送數據 res = sendto(g_sockfd, sendbuf, len, 0, g_proto.sasend, g_proto.salen); if( -1 == res ) error_quit("sendto error"); } void sig_alrm(int signo) { send_msg(); alarm(1); } void tv_sub(struct timeval *out, struct timeval *in) { //將兩個時間相減,並把結果存入第一個參數中( out -= in ) if ( (out->tv_usec -= in->tv_usec) < 0) { --out->tv_sec; out->tv_usec += 1000000; } out->tv_sec -= in->tv_sec; } struct addrinfo *host_serv(const char *host, const char *serv, int family, int socktype) { int n; struct addrinfo hints, *res; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_CANONNAME; hints.ai_family = family; hints.ai_socktype = socktype; n = getaddrinfo(host, serv, &hints, &res); if ( n != 0 ) error_quit("getaddrinfo error"); return res; } char *sock_ntop_host(const struct sockaddr *sa, socklen_t salen) { static char str[128]; struct sockaddr_in *sin = (struct sockaddr_in *) sa; //本程序只支持IPv4協議 if( sa->sa_family != AF_INET ) error_quit("sock_ntop_host: the type must be AF_INET"); if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL) error_quit("inet_ntop error"); return str; } //《UNIX網絡編程》書上的源碼 uint16_t in_cksum(uint16_t *addr, int len) { int nleft = len; uint32_t sum = 0; uint16_t *w = addr; uint16_t answer = 0; /* * Our algorithm is simple, using a 32 bit accumulator (sum), we add * sequential 16 bit words to it, and at the end, fold back all the * carry bits from the top 16 bits into the lower 16 bits. */ while (nleft > 1) { sum += *w++; nleft -= 2; } /* 4mop up an odd byte, if necessary */ if (nleft == 1) { *(unsigned char *)(&answer) = *(unsigned char *)w ; sum += answer; } /* 4add back carry outs from top 16 bits to low 16 bits */ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ sum += (sum >> 16); /* add carry */ answer = ~sum; /* truncate to 16 bits */ return(answer); } void error_quit(const char *str) { //輸出錯誤信息,退出程序 fprintf(stderr, "%s", str); if( errno != 0 ) fprintf(stderr, " : %s", strerror(errno)); fprintf(stderr, "\n"); exit(1); } www.2cto.com 運行示例: qch@LinuxMint ~/program/tcode $ gcc myping.c -o myping qch@LinuxMint ~/program/tcode $ sudo ./myping www.baidu.com PING www.a.shifen.com (115.239.210.26): 56 data bytes 64 bytes from 115.239.210.26: seq=0, ttl=128, rtt=31.272 ms 64 bytes from 115.239.210.26: seq=1, ttl=128, rtt=34.722 ms 64 bytes from 115.239.210.26: seq=2, ttl=128, rtt=30.822 ms 64 bytes from 115.239.210.26: seq=3, ttl=128, rtt=31.273 ms 64 bytes from 115.239.210.26: seq=4, ttl=128, rtt=29.995 ms ...........................