【分享】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()函数。

四、


数据运维技术 » 【分享】Linux C实现ping程序的源码 (linux c ping程序源码)