【分享】Linux C实现ping程序的源码 (linux c ping程序源码)
作为计算机网络中最基础的工具之一,Ping是广泛使用的网络诊断工具。它通过向指定的目标主机发送ICMP回显请求(即“ping请求”),并接收到相应的ICMP回显应答(即“ping响应”),来测试目标主机是否能够访问、响应的时间以及包丢失率等。在Linux系统中,我们可以自行开发一个Ping程序,并完成自己的定制化需求。本文将分享如何在Linux C语言中实现基本的Ping程序的源码,希望对初学C语言和网络编程的读者有所帮助。
一、Ping程序的实现原理
实际上,Ping程序并不是一件很复杂的事情。它主要分为两个部分:发送ICMP Echo请求和接收ICMP Echo回复。具体而言,实现Ping程序主要通过以下三个步骤:
1. 创建ICMP Echo请求报文:这个报文包含了目标主机的IP地址、类型为0x08的ICMP Echo请求的头部信息、以及发送请求的时间等信息。
2. 发送ICMP Echo请求:调用系统提供的“sendto()”函数,将构建好的ICMP Echo请求报文发送到目标主机的IP地址上,即向目标IP地址发送一个ICMP Echo请求报文。
3. 接收ICMP Echo回复:调用系统提供的“recvfrom()”函数,接收目标主机返回的ICMP Echo回复报文(如果有的话),并进行相关的解析。
有了以上三步,我们就可以实现一个基本的Ping程序。
二、Ping程序的源码实现
以下是一个基于Linux C语言实现的Ping程序的源码:
“`
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PACKET_SIZE 4096
char sendpacket[PACKET_SIZE];
char recvpacket[PACKET_SIZE];
int sockfd,datalen = 56;
int nsend = 0,nreceived = 0;
pid_t pid;
struct sockaddr_in dest_addr;
struct sockaddr_in from;
struct timeval tvrecv;
void statistics(void);
unsigned short cal_chksum(unsigned short *addr,int len);
void bl(const char *errMsg) {
printf(“%s\n”, errMsg);
exit(1);
}
void send_packet(void) {
int pktsize;
nsend++;
memset(sendpacket, 0, PACKET_SIZE);
sprintf(sendpacket, “%c%d”, 8, nsend);
pktsize = 8 + datalen;
*(unsigned short *)(sendpacket + 2) = htons(pktsize);
*(unsigned short *)(sendpacket + 4) = htons(0);
*(unsigned short *)(sendpacket + 6) = htons(1);
if (sendto(sockfd, sendpacket, pktsize, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr))
bl(“sendto error”);
}
}
void recv_packet(void) {
int n,fromlen;
fromlen = sizeof(from);
signal(SIGALRM,statistics);
alarm(10);
while (1) {
n = recvfrom(sockfd, recvpacket, sizeof(recvpacket), 0, (struct sockaddr *)&from, &fromlen);
if (n
if (errno == EINTR) {
continue;
}
bl(“recvfrom error”);
}
if (n
printf(“The received packet size is less than 16 bytes\n”);
continue;
}
if (recvpacket[0] != 0x08) {
printf(“The received packet is not ICMP Echo Reply packet\n”);
continue;
}
if (recvpacket[1] != 0) {
printf(“The received packet is not ICMP Echo Reply packet\n”);
continue;
}
if (*(unsigned short *)(recvpacket + 4) != htons(0)) {
printf(“The received packet header checksum is not correct\n”);
continue;
}
if (*(unsigned short *)(recvpacket + 6) != htons(1)) {
printf(“The received packet header identifier is not correct\n”);
continue;
}
if (*(unsigned short *)(recvpacket + 8) != htons(nsend)) {
printf(“The received packet sequence number is not correct\n”);
continue;
}
gettimeofday(&tvrecv,NULL);
statistics();
break;
}
}
unsigned short
cal_chksum(unsigned short *addr,int len) {
int nleft = len;
int sum = 0;
unsigned short *w = addr;
unsigned short answer = 0;
while(nleft > 1) {
sum += *w++;
nleft -= 2;
}
if(nleft == 1) {
*(unsigned char *)(&answer) = *(unsigned char *)w ;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return(answer);
}
void statistics(void) {
char buffer[256];
sprintf(buffer,”————-PING statistics————-\n%d packets tranitted, %d received”, nsend, nreceived);
if(nsend – nreceived > 0) {
sprintf(buffer + strlen(buffer),”, %%%d packet loss\n”,(nsend – nreceived) / nsend * 100);
}
else {
strcat(buffer,”, 0.00% packet loss\n”);
}
printf(“%s\n”,buffer);
if (nsend >= 3) {
exit(0);
}
alarm(1);
send_packet();
recv_packet();
}
int mn(int argc,char **argv) {
struct hostent *host;
struct protoent *protocol;
unsigned long inaddr = 0l;
int size = 50 * 1024;
if (argc
bl(“usage: ping “);
}
if ((protocol = getprotobyname(“icmp”)) == NULL) {
bl(“getprotobyname() fled”);
}
if ((sockfd = socket(AF_INET, SOCK_RAW, protocol->p_proto))
bl(“socket() fled”);
}
setuid(getuid());
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
bzero(&dest_addr,sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
if ((inaddr = inet_addr(argv[1])) == INADDR_NONE) {
if ((host = gethostbyname(argv[1])) == NULL) {
bl(“gethostbyname() fled”);
}
memcpy((char *)&dest_addr.sin_addr, host->h_addr, host->h_length);
}
else {
dest_addr.sin_addr.s_addr = inaddr;
}
pid = getpid();
printf(“PING %s (%s): %d bytes data in ICMP packets.\n”, argv[1], inet_ntoa(dest_addr.sin_addr), datalen);
send_packet();
recv_packet();
return 0;
}
“`
三、源码解析
1. 在程序开头,我们先定义了一些全局变量,准备接下来的码实现所需要的参数。
“`
char sendpacket[PACKET_SIZE];
char recvpacket[PACKET_SIZE];
int sockfd,datalen = 56;
int nsend = 0,nreceived = 0;
pid_t pid;
struct sockaddr_in dest_addr;
struct sockaddr_in from;
struct timeval tvrecv;
“`
其中,sendpacket和recvpacket用于存储发送和接收的数据包;sockfd是套接字描述符;datalen表示数据部分的大小;nsend和nreceived分别表示发送的和接收的数量;pid表示进程的PID;dest_addr表示目标地址;from表示数据发送方的地址信息;tvrecv是记录接收到数据包的详细时间信息。
2. statistics()是用于统计发送和接收信息的函数。
“`
void statistics(void) {
char buffer[256];
sprintf(buffer,”————-PING statistics————-\n%d packets tranitted, %d received”, nsend, nreceived);
if(nsend – nreceived > 0) {
sprintf(buffer + strlen(buffer),”, %%%d packet loss\n”,(nsend – nreceived) / nsend * 100);
}
else {
strcat(buffer,”, 0.00% packet loss\n”);
}
printf(“%s\n”,buffer);
if (nsend >= 3) {
exit(0);
}
alarm(1);
send_packet();
recv_packet();
}
“`
其中,sprintf()用于格式化打印,将统计信息存储在buffer中;if…else语句用于计算丢包率;printf()用于将字符串打印到终端;nsend>=3时用于结束程序;alarm()用于设置定时器,1秒后开始轮训执行send_packet()和recv_packet()。
3. cal_chksum()函数用于计算校验和。
“`
unsigned short
cal_chksum(unsigned short *addr,int len) {
int nleft = len;
int sum = 0;
unsigned short *w = addr;
unsigned short answer = 0;
while(nleft > 1) {
sum += *w++;
nleft -= 2;
}
if(nleft == 1) {
*(unsigned char *)(&answer) = *(unsigned char *)w ;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return(answer);
}
“`
我们主要用于对构建报文头进行校验和计算。
4. send_packet()函数用于发送数据包。
“`
void send_packet(void) {
int pktsize;
nsend++;
memset(sendpacket, 0, PACKET_SIZE);
sprintf(sendpacket, “%c%d”, 8, nsend);
pktsize = 8 + datalen;
*(unsigned short *)(sendpacket + 2) = htons(pktsize);
*(unsigned short *)(sendpacket + 4) = htons(0);
*(unsigned short *)(sendpacket + 6) = htons(1);
if (sendto(sockfd, sendpacket, pktsize, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr))
bl(“sendto error”);
}
}
“`
在该函数中,我们首先增加nsend的值以记录发送数量;然后重置sendpacket数组;接下来根据记录时间和编号构建请求包并计算包大小;然后向目标地址发送ICMP Echo请求,可以看到这个过程主要通过系统提供的sendto函数实现;最后增加错误处理,避免出现异常退出。
5. recv_packet()函数用于接收数据包。
“`
void recv_packet(void) {
int n,fromlen;
fromlen = sizeof(from);
signal(SIGALRM,statistics);
alarm(10);
while (1) {
n = recvfrom(sockfd, recvpacket, sizeof(recvpacket), 0, (struct sockaddr *)&from, &fromlen);
if (n
if (errno == EINTR) {
continue;
}
bl(“recvfrom error”);
}
if (n
printf(“The received packet size is less than 16 bytes\n”);
continue;
}
if (recvpacket[0] != 0x08) {
printf(“The received packet is not ICMP Echo Reply packet\n”);
continue;
}
if (recvpacket[1] != 0) {
printf(“The received packet is not ICMP Echo Reply packet\n”);
continue;
}
if (*(unsigned short *)(recvpacket + 4) != htons(0)) {
printf(“The received packet header checksum is not correct\n”);
continue;
}
if (*(unsigned short *)(recvpacket + 6) != htons(1)) {
printf(“The received packet header identifier is not correct\n”);
continue;
}
if (*(unsigned short *)(recvpacket + 8) != htons(nsend)) {
printf(“The received packet sequence number is not correct\n”);
continue;
}
gettimeofday(&tvrecv,NULL);
statistics();
break;
}
}
“`
在该函数中,我们主要是用于在规定的时间内接收报文,并对其的长度、类型头部信息、校验和、标识符和编号进行检查,确认接收到的数据包是有效的ICMP Echo回传包。
6. mn()函数是整个程序的入口函数。
“`
int mn(int argc,char **argv) {
struct hostent *host;
struct protoent *protocol;
unsigned long inaddr = 0l;
int size = 50 * 1024;
if (argc
bl(“usage: ping “);
}
if ((protocol = getprotobyname(“icmp”)) == NULL) {
bl(“getprotobyname() fled”);
}
if ((sockfd = socket(AF_INET, SOCK_RAW, protocol->p_proto))
bl(“socket() fled”);
}
setuid(getuid());
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
bzero(&dest_addr,sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
if ((inaddr = inet_addr(argv[1])) == INADDR_NONE) {
if ((host = gethostbyname(argv[1])) == NULL) {
bl(“gethostbyname() fled”);
}
memcpy((char *)&dest_addr.sin_addr, host->h_addr, host->h_length);
}
else {
dest_addr.sin_addr.s_addr = inaddr;
}
pid = getpid();
printf(“PING %s (%s): %d bytes data in ICMP packets.\n”, argv[1], inet_ntoa(dest_addr.sin_addr), datalen);
send_packet();
recv_packet();
return 0;
}
“`
在mn函数中,首先我们通过输入参数获取IP地址;然后创建原始套接字sci;接着设置套接字传输协议和接收缓冲区大小;接下来根据输入的IP地址,构建目标地址;最后输出ping的基本信息并开始轮询执行send_packet()和recv_packet()函数。
四、