📅  最后修改于: 2023-12-03 15:37:35.134000             🧑  作者: Mango
Ping 是一种网络工具,用于测试主机之间的连通性。在 C 语言中,可以使用 socket 库编写程序实现 Ping 功能。
Ping 协议基于 ICMP 协议,Ping 命令会向目标主机发送 ICMP Echo Request 数据包,如果目标主机接收到请求,它将响应一个 ICMP Echo Reply 数据包,这样就测试了两台主机之间的连通性。
C语言中可以使用 socket 库的 sendto
和 recvfrom
函数实现 Ping 功能。具体可以分为以下几个步骤:
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
int ping(char *hostname, int count);
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <hostname>\n", argv[0]);
exit(1);
}
ping(argv[1], 4);
return 0;
}
int ping(char *hostname, int count) {
int sockfd;
struct sockaddr_in addr;
struct hostent *host;
struct timeval tv_start, tv_end;
double rtt_ms;
char sendbuf[BUFSIZ], recvbuf[BUFSIZ];
int send_bytes, recv_bytes;
int seq = 0;
int nsend = 0, nrecv = 0;
int i;
host = gethostbyname(hostname);
if (!host) {
printf("[ERROR] Unknown host %s\n", hostname);
return -1;
}
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd < 0) {
perror("[ERROR] Create socket failed");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr = *(struct in_addr *) host->h_addr;
for (i = 1; i <= count; i++) {
seq++;
memset(sendbuf, 0, BUFSIZ);
memset(recvbuf, 0, BUFSIZ);
struct icmp *icmp_hdr = (struct icmp *) sendbuf;
icmp_hdr->icmp_type = ICMP_ECHO;
icmp_hdr->icmp_code = 0;
icmp_hdr->icmp_id = getpid() & 0xffff;
icmp_hdr->icmp_seq = seq;
gettimeofday(&tv_start, NULL);
send_bytes = sendto(sockfd, sendbuf, sizeof(struct icmp), 0, (struct sockaddr *) &addr, sizeof(addr));
if (send_bytes <= 0) {
perror("[ERROR] Send ICMP ECHO request failed");
continue;
}
nsend++;
socklen_t addr_len = sizeof(addr);
recv_bytes = recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *) &addr, &addr_len);
if (recv_bytes <= 0) {
perror("[ERROR] Recv ICMP ECHO reply failed");
continue;
}
if (recv_bytes < sizeof(struct iphdr) + sizeof(struct icmp)) {
printf("[WARNING] ICMP packet too short (%d bytes)\n", recv_bytes);
continue;
}
struct iphdr *ip_hdr = (struct iphdr *) recvbuf;
struct icmp *icmp_reply = (struct icmp *) (recvbuf + sizeof(struct iphdr));
if ((icmp_reply->icmp_type == ICMP_ECHOREPLY) && (icmp_reply->icmp_id == getpid())) {
nrecv++;
gettimeofday(&tv_end, NULL);
rtt_ms = (tv_end.tv_sec - tv_start.tv_sec) * 1000 + (tv_end.tv_usec - tv_start.tv_usec) / 1000.0;
printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.2f ms\n", recv_bytes, hostname, seq, ip_hdr->ttl, rtt_ms);
} else {
printf("[WARNING] ICMP packet not correct (%d bytes)\n", recv_bytes);
continue;
}
sleep(1);
}
printf("--- %s ping statistics ---\n", hostname);
printf("%d packets transmitted, %d received, %.2f%% packet loss\n", nsend, nrecv, (nsend - nrecv) * 100.0 / nsend);
close(sockfd);
return nrecv;
}
本例中使用了 gettimeofday
函数获取系统时间,icmp_seq
作为 Echo Request 和 Echo Reply 数据包的报文序列号,通过计算时间差计算往返时间(RTT)。在发送和接收数据包时,需要处理错误情况,例如发送数据包失败、接收数据包失败或接收到的数据包不正确时,需要显示错误信息并继续处理下一个数据包。
编译并运行:
gcc ping.c -o ping
./ping 127.0.0.1
输出:
84 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.02 ms
84 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.02 ms
84 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.02 ms
84 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.02 ms
--- 127.0.0.1 ping statistics ---
4 packets transmitted, 4 received, 0.00% packet loss