linux上Ping程序的实现

来源:互联网 发布:淘宝店被关了交易记录 编辑:程序博客网 时间:2024/06/12 22:46

    在学习了TCP/IP详解之后,对于Ping程序的理解比较深了。在对发送原始套接字了解之后,就决定自己去实现一下Ping程序。代码如下:

/* * Ping程序 * 2014年5月15日 * Version 1.0 * Designed by WJY * */#include <stdio.h>#include <string.h>#include <stdlib.h>#include <signal.h>#include <unistd.h>#include <netdb.h>#include <setjmp.h>#include <errno.h>#include <arpa/inet.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <netinet/ip_icmp.h>#define PACKET_SIZE4096#define MAX_WAIT_TIME5#define MAX_NO_PACKETS4char sendpacket[PACKET_SIZE];char recvpacket[PACKET_SIZE];int sockfd;int datalen = 56;int nsend = 0;int nreceived = 0;struct sockaddr_in dest_addr;pid_t pid;struct sockaddr_in from;struct timeval tvrecv;//统计报文发送和接收情况void statistics(int signo);//计算校验和unsigned short cal_chksum(unsigned short *addr, int len);//设置ICMP报头int pack(int pack_no);//发送报文void send_packet(void);//接收报文void recv_packet(void);//解析ICMP报文int unpack(char *buf, int len);//计算时间void tv_sub(struct timeval *out, struct timeval *in);/* 统计发送的ICMP报文和丢失的ICMP报文 */void statistics(int signo){       printf("\n--------------------PING statistics-------------------\n");printf("%d packets transmitted, %d received , %%%d lost\n",nsend, nreceived, (nsend - nreceived) / nsend * 100);close(sockfd);exit(1);}/*校验和算法*/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( nleft==1){       *(unsigned char *)(&answer) = *(unsigned char *)w;sum += answer;}sum = (sum >> 16) + (sum & 0xffff);sum += (sum >> 16);answer = ~sum;return answer;}/* 设置ICMP报头 */int pack(int pack_no){       int i, packsize;struct icmp *icmp;struct timeval *tval;icmp=(struct icmp *) sendpacket;icmp->icmp_type = ICMP_ECHO;icmp->icmp_code = 0;icmp->icmp_cksum = 0;icmp->icmp_seq = pack_no;icmp->icmp_id = pid;packsize = 8 + datalen;/*记录发送时间*/tval = (struct timeval *) icmp->icmp_data;    gettimeofday(tval,NULL);/*校验算法*/icmp->icmp_cksum = cal_chksum((unsigned short *)icmp, packsize);return packsize;}/*发送四个ICMP报文*/void send_packet(){       int packetsize;while(nsend < MAX_NO_PACKETS){       /* 设置ICMP报头 */nsend++;packetsize = pack(nsend);if(sendto(sockfd, sendpacket, packetsize , 0,(struct sockaddr *) &dest_addr, sizeof(dest_addr)) < 0){       printf("发送ICMP报文失败!\n");continue;}/* 每隔1毫秒发送一个ICMP报文 */usleep(1);}}/* 接收所有ICMP报文 */void recv_packet(){       int n, fromlen;extern int errno;signal(SIGALRM, statistics);fromlen = sizeof(from);while(nreceived < nsend){       alarm(MAX_WAIT_TIME);if((n = recvfrom(sockfd, recvpacket, sizeof(recvpacket), 0,(struct sockaddr *) &from, &fromlen)) < 0){       if(errno==EINTR)continue;perror("recvfrom error");continue;}/* 记录接收时间 */gettimeofday(&tvrecv, NULL);if(unpack(recvpacket, n) == -1)continue;nreceived++;}}/* 解析ICMP报头 */int unpack(char *buf, int len){       int i, iphdrlen;struct ip *ip;struct icmp *icmp;struct timeval *tvsend;double rtt;ip = (struct ip *)buf;/* 求IP报头长度,即ip报头的长度标志乘4(IP首部的长度是以32位为单位的)*/iphdrlen = ip->ip_hl << 2;/* 越过IP报头,指向ICMP报头 */icmp= (struct icmp *)(buf + iphdrlen);/* ICMP报头及ICMP数据报的总长度 */len -= iphdrlen;/* 小于ICMP报头长度则不合理 */if( len < 8){       printf("ICMP packets\'s length is less than 8.\n");return -1;}/* 确保所接收的是我所发的的ICMP的回应 */if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)){       tvsend = (struct timeval *)icmp->icmp_data;tv_sub(&tvrecv,tvsend); /*接收和发送的时间差*//* 以毫秒为单位计算rtt */rtt = tvrecv.tv_sec * 1000 + tvrecv.tv_usec / 1000;/* 显示相关信息 */printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms\n",len, inet_ntoa(from.sin_addr), icmp->icmp_seq, ip->ip_ttl, rtt);}elsereturn -1;}/* 两个timeval结构相减 */void tv_sub(struct timeval *out, struct timeval *in){       if( (out->tv_usec -= in->tv_usec) < 0)    {       --out->tv_sec;out->tv_usec += 1000000;}out->tv_sec -= in->tv_sec;}int main(int argc, char *argv[]){       unsigned long inaddr = 0l;int waittime = MAX_WAIT_TIME;int size = 50*1024;struct hostent *host; struct protoent *protocol;if (argc != 2) {       printf("Error. Usage: %s <Hostname or IP address>\n", argv[0]);exit(1);}/* 生成使用ICMP的原始套接字,这种套接字只有root才能生成 */if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {printf("创建原始套接字失败!\n");exit(1);}/* 扩大接收缓冲区,主要是防止ping到一个广播地址或多播地址,造成缓冲区溢出 */setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));bzero(&dest_addr, sizeof(dest_addr));dest_addr.sin_family = AF_INET;/* 判断输入的是主机名还是ip地址 */if((inaddr = inet_addr(argv[1])) == INADDR_NONE){       if((host = gethostbyname(argv[1])) == NULL){       printf("通过主机名获取主机IP失败!\n");exit(1);}memcpy((char *) &dest_addr.sin_addr, host->h_addr, host->h_length);}else{if (inet_pton(AF_INET, argv[1], &dest_addr.sin_addr) <= 0){printf("inet_pton error for %s\n", argv[1]);exit(1);}}/* 获取进程的id,用于设置ICMP的标志符 */pid = getpid();printf("PING %s (%s): %d bytes data in ICMP packets.\n",argv[1], inet_ntoa(dest_addr.sin_addr), datalen);/*发送所有ICMP报文*/send_packet();/*接收所有ICMP报文*/recv_packet();/*进行统计*/statistics(SIGALRM);return 0;}


运行结果如下:



0 0
原创粉丝点击