ping是怎么实现的?它依赖的是原始套接字与ICMP(Internet Control Message Protocol,网际控制消息协议),向目标主机投递回射请求,而目标主机将响应一个回射应答。通常在请求体中包含时间戳,用来计算RTT(Round-trip Time,回环时间),也就是玩家常说的服务器延迟了。
int
main(int argc, char **argv)
{
int c;
struct addrinfo *ai;
char *h;
opterr = 0;
while((c = getopt(argc, argv, "v")) != -1)
{
switch(c)
{
case ‘v‘:
verbose++;
break;
case ‘?‘:
printf("unrecognized option: %c\n", c);
return -1;
}
}
if(optind != argc-1)
{
printf("usage: ping [-v] <hostname>\n");
return -1;
}
host = argv[optind];
pid = getpid() & 0xffff;
signal(SIGALRM, sig_alrm);
ai = host_serv(host, NULL, 0, 0);
h = sock_ntop_host(ai->ai_addr, ai->ai_addrlen);
printf("PING %s (%s): %d data bytes\n",
ai->ai_canonname ? ai->ai_canonname : h, h, datalen);
if(ai->ai_family == AF_INET)
{
pr = &proto_v4;
}
else
{
printf("cannot ping IPv4-mapped IPv6 address\n");
return -1;
}
pr->sasend = ai->ai_addr;
pr->sarecv = (sockaddr*)calloc(1, ai->ai_addrlen);
pr->salen = ai->ai_addrlen;
readloop();
}
void
readloop()
{
int size;
char recvbuf[BUFSIZE];
char controlbuf[BUFSIZE];
struct msghdr msg;
struct iovec iov;
ssize_t n;
struct timeval tval;
sockfd = socket(pr->sasend->sa_family, SOCK_RAW, pr->icmpproto);
setuid(getuid());
if(pr->finit)
(*pr->finit)();
size = 60 * 1024;
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
sig_alrm(SIGALRM); // 发送部分,下面先分析此部分
iov.iov_base = recvbuf;
iov.iov_len = sizeof(recvbuf);
msg.msg_name = pr->sarecv;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = controlbuf;
for(;;)
{
msg.msg_namelen = pr->salen;
msg.msg_controllen = sizeof(controlbuf);
n = recvmsg(sockfd, &msg, 0);
if(n < 0)
{
if(errno == EINTR)
continue;
else
{
printf("recvmsg error\n");
exit(-1);
}
}
gettimeofday(&tval, NULL);
(*pr->fproc)(recvbuf, n, &msg, &tval); // 接受部分
}
}
void
sig_alrm(int signo)
{
(*pr->fsend)(); // 构造一个ICMP回射请求
alarm(1); // 定时发包
}
void
send_v4()
{
int len;
struct icmp *icmp;
icmp = (struct icmp*)sendbuf;
icmp->icmp_type = ICMP_ECHO;
icmp->icmp_code = 0;
icmp->icmp_id = pid;
icmp->icmp_seq = nsent++;
memset(icmp->icmp_data, 0xa5, datalen);
gettimeofday((struct timeval*)icmp->icmp_data, NULL);
len = 8 + datalen;
icmp->icmp_cksum = 0;
icmp->icmp_cksum = in_cksum((u_short*)icmp, len);
sendto(sockfd, sendbuf, len, 0, pr->sasend, pr->salen); // 虽然是原始套接字,但IP头由OS添加
}
void
proc_v4(char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)
{
int hlenl, icmplen;
double rtt;
struct ip *ip;
struct icmp *icmp;
struct timeval *tvsend;
ip = (struct ip*)ptr; // 但接受到的则是整个IP包了,要自己提取ICMP部分
hlenl = ip->ip_hl << 2;
if(ip->ip_p != IPPROTO_ICMP)
return;
icmp = (struct icmp*)(ptr + hlenl);
if((icmplen = len - hlenl) < 8)
return;
if(icmp->icmp_type == ICMP_ECHOREPLY)
{
if(icmp->icmp_id != pid)
return;
if(icmplen < 16)
return;
tvsend = (struct timeval*)icmp->icmp_data;
tv_sub(tvrecv, tvsend);
rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;
printf("%d bytes from %s: seq=%u, ttl=%d, rtt=%.3f ms\n",
icmplen, sock_ntop_host(pr->sarecv, pr->salen), icmp->icmp_seq, ip->ip_ttl, rtt);
}
else if(verbose)
{
printf("%d bytes from %s: type=%d, code=%d\n",
icmplen, sock_ntop_host(pr->sarecv, pr->salen), icmp->icmp_type, icmp->icmp_code);
}
}