首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C语言 >

ping下令的C语言实现(linux, IPv4,简单版)

2013-02-24 
ping命令的C语言实现(linux, IPv4,简单版)这个程序主要运用了ICMPv4协议(回显请求)来测试本机到某服务器的

ping命令的C语言实现(linux, IPv4,简单版)
这个程序主要运用了ICMPv4协议(回显请求)来测试本机到某服务器的网络是否连通,因为其中用到了原始套接字,所以运行该程序需要管理员权限。

PS:本程序只支持一种输入方式:./myping <hostname>,不支持其他参数。

思路:
1:根据hostname参数创建原始套接字。
2:每隔1秒钟向服务器发送一个ICMP回显请求。
3:循环接收从服务器返回的应答并处理其数据。

上代码:

#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;elseerror_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标识符字段为本进程的PIDicmp->icmp_id = g_pid;//ICMP序列号字段为不断递增的全局变量nsenticmp->icmp_seq = nsent++;//ICMP数据字段为当前时间截,空白部分填充0xa5memset(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); }


运行示例:
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
...........................

热点排行