linux中用c语言实现ping函数,ping下令的C语言实现(linux, IPv4,简单版)

这是一个简单的C语言程序,用于实现Linux下的ping命令,利用ICMPv4协议进行网络连通性测试。程序创建原始套接字,每隔1秒向指定服务器发送ICMP回显请求,并接收并处理返回的应答,计算网络延迟。运行程序需要管理员权限。示例展示了程序如何与www.baidu.com交互,显示回显应答的序列号、TTL和往返时间。
摘要由CSDN通过智能技术生成

ping命令的C语言实现(linux, IPv4,简单版)

这个程序主要运用了ICMPv4协议(回显请求)来测试本机到某服务器的网络是否连通,因为其中用到了原始套接字,所以运行该程序需要管理员权限。

PS:本程序只支持一种输入方式:./myping ,不支持其他参数。

思路:

1:根据hostname参数创建原始套接字。

2:每隔1秒钟向服务器发送一个ICMP回显请求。

3:循环接收从服务器返回的应答并处理其数据。

上代码:#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

//各种缓冲区的长度

#define BUFSIZE 1500

//ICMP回显请求的长度

#define DATA_LEN 56

struct proto

{

struct sockaddr *sasend; /* sockaddr{} for send, from getaddrinfo */

struct sockaddr *sarecv; /* sockaddr{} for receiving */

socklen_t salen; /* length of sockaddr{}s */

int icmpproto; /* IPPROTO_xxx value for ICMP */

};

//全局变量

pid_t g_pid;

int g_sockfd;

struct proto g_proto = { NULL, NULL, 0, IPPROTO_ICMP };

//处理服务器返回的ICMP回显信息

void proc_msg(char *, ssize_t, struct msghdr *, struct timeval *);

//发送ICMP回显请求

void send_msg(void);

//循环发送、接收信息

void readloop(void);

//定时器入门函数,每隔一秒一次发送ICMP请求

void sig_alrm(int);

//计算两个时间之间的间隔

void tv_sub(struct timeval *, struct timeval *);

//获取服务器的地址等信息

struct addrinfo *host_serv(const char *host,

const char *serv, int family, int socktype);

//根据服务器信息,得到服务器的IP地址

char *sock_ntop_host(const struct sockaddr *sa, socklen_t salen);

//计算校验和

uint16_t in_cksum(uint16_t *addr, int len);

//输出错误信息,退出程序

void error_quit(const char *str);

int main(int argc, char **argv)

{

int c;

struct addrinfo *ai;

struct sockaddr_in *sin;

char *ip_address;

char *host;

//本程序只支持一种输入方式:./myping

if( argc != 2 )

error_quit("usage: myping ");

host = argv[1];

//将pid的高二位全置为0,ICMP的ID只有16位

g_pid = getpid() & 0xffff;

//设置定时器,每秒钟向服务器发送一次请求

signal(SIGALRM, sig_alrm);

//获取服务器的信息(addrinfo结构)

ai = host_serv(host, NULL, 0, 0);

ip_address = sock_ntop_host(ai->ai_addr, ai->ai_addrlen);

printf("PING %s (%s): %d data bytes\n",

ai->ai_canonname ? ai->ai_canonname : ip_address,

ip_address, DATA_LEN);

//如果返回的协议簇不是AF_INET(IPv4),则退出

if ( ai->ai_family != AF_INET )

error_quit("unknown address family");

//设置proto结构体

g_proto.sasend = ai->ai_addr;

g_proto.sarecv = calloc(1, ai->ai_addrlen);

g_proto.salen = ai->ai_addrlen;

//开始循环发送/接收请求

readloop();

return 0;

}

void readloop(void)

{

int size;

char recvbuf[BUFSIZE];

char controlbuf[BUFSIZE];

struct msghdr msg;

struct iovec iov;

ssize_t n;

struct timeval tval;

//创建一个IPv4的原始套接字

g_sockfd = socket(g_proto.sasend->sa_family, SOCK_RAW, g_proto.icmpproto);

if( -1 == g_sockfd )

error_quit("socket error");

//放弃管理员权限

//这个程序中,只用创建原始套接字时需要管理员权限

setuid(getuid());

//设置socket的接收缓冲区

size = 60 * 1024;

setsockopt(g_sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));

//发出第一个请求

sig_alrm(SIGALRM);

//为recvmsg调用设置msghdr结构

iov.iov_base = recvbuf;

iov.iov_len = sizeof(recvbuf);

msg.msg_name = g_proto.sarecv;

msg.msg_iov = &iov;

msg.msg_iovlen = 1;

msg.msg_control = controlbuf;

//开始死循环,不断读取和处理从服务器中返回的信息

while( 1 )

{

msg.msg_namelen = g_proto.salen;

msg.msg_controllen = sizeof(controlbuf);

n = recvmsg(g_sockfd, &msg, 0);

if (n < 0)

{

if (errno == EINTR)

continue;

else

error_quit("recvmsg error");

}

//分析返回内容,产生输出

gettimeofday(&tval, NULL);

proc_msg(recvbuf, n, &msg, &tval);

}

}

void proc_msg(char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)

{

int hlen1, icmplen;

double rtt;

struct ip *ip;

struct icmp *icmp;

struct timeval *tvsend;

//将服务器返回的字符串强转为ip结构

ip = (struct ip *) ptr;

//得到IP表头的长度

hlen1 = ip->ip_hl << 2;

//如果不是ICMP的应答,则返回

if (ip->ip_p != IPPROTO_ICMP)

return;

icmp = (struct icmp *) (ptr + hlen1);

//长度不足,不是合法应答

if ( (icmplen = len - hlen1) < 8)

return;

//不是回显应答,返回

if (icmp->icmp_type != ICMP_ECHOREPLY)

return;

//不是我们发出请求的应答,返回

if (icmp->icmp_id != g_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(g_proto.sarecv, g_proto.salen),

icmp->icmp_seq, ip->ip_ttl, rtt);

}

void send_msg(void)

{

int len;

int res;

struct icmp *icmp;

char sendbuf[BUFSIZE];

static int nsent = 0;

//根据ICMPv4协议来设置发送信息

icmp = (struct icmp *) sendbuf;

//ICMP回显请求

icmp->icmp_type = ICMP_ECHO;

icmp->icmp_code = 0;

//ICMP标识符字段为本进程的PID

icmp->icmp_id = g_pid;

//ICMP序列号字段为不断递增的全局变量nsent

icmp->icmp_seq = nsent++;

//ICMP数据字段为当前时间截,空白部分填充0xa5

memset(icmp->icmp_data, 0xa5, DATA_LEN);

gettimeofday((struct timeval *)icmp->icmp_data, NULL);

//计算并填充校验和

len = 8 + DATA_LEN;

icmp->icmp_cksum = 0;

icmp->icmp_cksum = in_cksum((u_short *) icmp, len);

//发送数据

res = sendto(g_sockfd, sendbuf, len, 0, g_proto.sasend, g_proto.salen);

if( -1 == res )

error_quit("sendto error");

}

void sig_alrm(int signo)

{

send_msg();

alarm(1);

}

void tv_sub(struct timeval *out, struct timeval *in)

{

//将两个时间相减,并把结果存入第一个参数中( out -= in )

if ( (out->tv_usec -= in->tv_usec) < 0)

{

--out->tv_sec;

out->tv_usec += 1000000;

}

out->tv_sec -= in->tv_sec;

}

struct addrinfo *host_serv(const char *host, const char *serv, int family, int socktype)

{

int n;

struct addrinfo hints, *res;

memset(&hints, 0, sizeof(struct addrinfo));

hints.ai_flags = AI_CANONNAME;

hints.ai_family = family;

hints.ai_socktype = socktype;

n = getaddrinfo(host, serv, &hints, &res);

if ( n != 0 )

error_quit("getaddrinfo error");

return res;

}

char *sock_ntop_host(const struct sockaddr *sa, socklen_t salen)

{

static char str[128];

struct sockaddr_in *sin = (struct sockaddr_in *) sa;

//本程序只支持IPv4协议

if( sa->sa_family != AF_INET )

error_quit("sock_ntop_host: the type must be AF_INET");

if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)

error_quit("inet_ntop error");

return str;

}

//《UNIX网络编程》书上的源码

uint16_t in_cksum(uint16_t *addr, int len)

{

int nleft = len;

uint32_t sum = 0;

uint16_t *w = addr;

uint16_t answer = 0;

/*

* Our algorithm is simple, using a 32 bit accumulator (sum), we add

* sequential 16 bit words to it, and at the end, fold back all the

* carry bits from the top 16 bits into the lower 16 bits.

*/

while (nleft > 1)

{

sum += *w++;

nleft -= 2;

}

/* 4mop up an odd byte, if necessary */

if (nleft == 1) {

*(unsigned char *)(&answer) = *(unsigned char *)w ;

sum += answer;

}

/* 4add back carry outs from top 16 bits to low 16 bits */

sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */

sum += (sum >> 16); /* add carry */

answer = ~sum; /* truncate to 16 bits */

return(answer);

}

void error_quit(const char *str)

{

//输出错误信息,退出程序

fprintf(stderr, "%s", str);

if( errno != 0 )

fprintf(stderr, " : %s", strerror(errno));

fprintf(stderr, "\n");

exit(1);

}

运行示例:

qch@LinuxMint ~/program/tcode $ gcc myping.c -o myping

qch@LinuxMint ~/program/tcode $ sudo ./myping www.baidu.com

PING www.a.shifen.com (115.239.210.26): 56 data bytes

64 bytes from 115.239.210.26: seq=0, ttl=128, rtt=31.272 ms

64 bytes from 115.239.210.26: seq=1, ttl=128, rtt=34.722 ms

64 bytes from 115.239.210.26: seq=2, ttl=128, rtt=30.822 ms

64 bytes from 115.239.210.26: seq=3, ttl=128, rtt=31.273 ms

64 bytes from 115.239.210.26: seq=4, ttl=128, rtt=29.995 ms

...........................

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值