linux上Ping程序的实现

    在学习了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_SIZE		4096
#define MAX_WAIT_TIME	5
#define MAX_NO_PACKETS	4

char 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);
	}
	else
		return -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
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,实现Ping程序需要使用ICMP协议。以下是一个简单的Python实现,可以用于Linux和Windows系统: ```python import os import sys import struct import socket import select import time # 计算校验和 def checksum(data): n = len(data) m = n % 2 sum = 0 for i in range(0, n - m, 2): sum += (data[i]) + ((data[i+1]) << 8) if m: sum += (data[-1]) sum = (sum >> 16) + (sum & 0xffff) sum += (sum >> 16) result = (~sum) & 0xffff result = result >> 8 | (result << 8 & 0xff00) return result # 发送ICMP请求报文 def send_ping(sock, dest_addr, pid, seq): # 构造ICMP请求报文 icmp_type = 8 # ICMP Echo Request icmp_code = 0 icmp_checksum = 0 icmp_id = pid & 0xffff icmp_seq = seq & 0xffff icmp_data = b'Hello, World!' icmp_checksum = checksum(struct.pack('!BBHHH', icmp_type, icmp_code, icmp_checksum, icmp_id, icmp_seq) + icmp_data) icmp_packet = struct.pack('!BBHHH', icmp_type, icmp_code, icmp_checksum, icmp_id, icmp_seq) + icmp_data # 发送ICMP请求报文 sock.sendto(icmp_packet, (dest_addr, 0)) # 接收ICMP响应报文 def receive_ping(sock, pid, seq, timeout): time_left = timeout while True: # 设置超时时间 start_time = time.time() ready = select.select([sock], [], [], time_left) elapsed = (time.time() - start_time) if not ready[0]: return None, None, None, None, None # 接收到ICMP响应报文 pkt, addr = sock.recvfrom(1024) icmp_header = pkt[20:28] icmp_type, icmp_code, icmp_checksum, icmp_id, icmp_seq = struct.unpack('!BBHHH', icmp_header) # 判断是否为ICMP响应报文 if icmp_type == 0 and icmp_id == pid and icmp_seq == seq: icmp_data = pkt[28:] return addr[0], icmp_type, icmp_code, icmp_checksum, icmp_data # 更新超时时间 time_left -= elapsed if time_left <= 0: return None, None, None, None, None # Ping指定主机 def ping(dest_addr, count, timeout): # 创建原始套接字 sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) pid = os.getpid() & 0xffff for i in range(count): seq = i + 1 # 发送ICMP请求报文 send_ping(sock, dest_addr, pid, seq) # 接收ICMP响应报文 addr, icmp_type, icmp_code, icmp_checksum, icmp_data = receive_ping(sock, pid, seq, timeout) # 输出结果 if addr is not None: print('Reply from {}: icmp_seq={}, time={}ms'.format(addr, seq, round(icmp_checksum * 1000 / timeout))) else: print('Request timed out.') sock.close() if __name__ == '__main__': if len(sys.argv) < 2: print('Usage: python ping.py <host> [count] [timeout]') else: dest_addr = sys.argv[1] count = int(sys.argv[2]) if len(sys.argv) > 2 else 4 timeout = int(sys.argv[3]) if len(sys.argv) > 3 else 1000 ping(dest_addr, count, timeout) ``` 您可以将以上代码保存为ping.py,并在终端中运行以下命令来Ping指定主机: ``` python ping.py <host> [count] [timeout] ``` 其中,<host>为目标主机的IP地址或域名,[count]为Ping包数量,默认为4,[timeout]为超时时间(毫秒),默认为1000。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值