ping程序的实现
2013-02-05 14:05
344 查看
关于网络编程,知之甚少,linux环境下编程的经验也比较缺乏,于是乎,在百度文库上下载了一个关于ping的程序设计,照着将代码敲打一遍,顺便熟悉某些东东。 敲完代码,运行时,发现错误极多,正好也试着学习用GDB调试。下面是一些琐碎的知识点,写写加深印象。
1. 关于ping程序
用于确定本地主机与网络中其它主机的网络通信情况,常使用ping程序。ping程序向指定的IP地址发送ICMP数据包,通过返回的信息来判断网络的连接状况。
ping程序的返回信息中有一个值为TTL(time to live),表示ping程序发送的icmp数据包的生存周期,每经过一个网段,TTL的值减1,当其值被减为0时,该数据包将被丢弃,但该数据包的源地址将会被告知情况,以重新发送该数据包。另外,不同操作系统的TTL值是不相同的,linux操作系统是64。
2. ICMP协议
ICMP(Internet Control Message Protocol),即网际控制报文协议,可用在网络中实现主机探测、路由维护、路由选择和流量控制。
由于IP协议没有机制来获取网络错误信息以及没有对错误进行处理,所以需要另一协议来解决这一问题,这个协议就是ICMP协议。ICMP常被认为是IP层的一部分,用于传输差错报文及控制报文。ICMP报文是封装在IP数据报内部的。
关于ICMP协议的更多内容,请参考上篇文章《ICMP协议》。
3. ping程序主要流程
创建通信的套接字-->将地址、端口信息与套接字绑定-->构建IP包头与ICMP包头-->发送构建的数据包-->接收对方主机的回应-->给出程序反馈信息
4. 涉及的一及结构体及函数接口简介
(1) struct sockaddr_in :此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。
sa_family是地址家族,一般都是“AF_xxx”的形式。通常大多用的是都是AF_INET,代表TCP/IP协议族;
sin_port存储端口号(使用网络字节顺序);
sin_addr存储IP地址,使用in_addr这个数据结构
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节;
(2) struct protoent :相关函数: getprotobyname, getprotoent, setprotoent, endprotoent
p_name: 网络协议名 p_aliases: 别名 p_proto: 网络协议编号
(3) struct hostent :hostent是host entry的缩写,该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
注:其所需要的头文件还有这一宏定义 #define h_addr h_addr_list[0]
(4) struct imcp
(5) getprotobyname(const char *protoname)
getprotobyname()会返回一个protoent结构,参数protoname为欲查询的网络协议名。此函数会从 /etc/protocols中查找符合条件的数据并由结构protoent返回。
(6) gethostbyname()
gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针。
(7) socket :函数原型 int socket(int domain, int type, int protocol);
第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置AF_INET;
第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM、原始套接字SOCK_RAW;
第三个参数指定应用程序所使用的通信协议;
该函数如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该套接字描述符表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构,另外需知套接字数据结构是在操作系统的内核缓冲里的。
(8) setsockopt():用于任意类型、任意状态套接口的设置选项值
函数原型:int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd:标识一个套接口的描述字
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
optname:需设置的选项
optval:指针,指向存放选项值的缓冲区
optlen:optval缓冲区长度
(9) inet_addr():将一个点分十进制的IP转换成一个长整数型数
(10) sendto():经socket传送数据
函数原型:int sendto ( socket s , const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen);
函数说明:sendto() 用来将数据由指定的socket传给对方主机。
参数s为已建好连线的socket,如果利用UDP协议则不需经过连线操作;
参数msg指向欲连线的数据内容,参数flags 一般设0,详细描述请参考send();
参数to用来指定欲传送的网络地址,结构sockaddr请参考bind();
参数tolen为sockaddr的结果长度;
返回值:成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。
(11) recvfrom():经socket接收数据
函数原型:ssize_t recvfrom(int sockfd,void *buf,int len,unsigned int flags, struct sockaddr *from,socket_t *fromlen);ssize_t 相当于 int,socket_t 相当于int ,这里用这个名字为的是提高代码的自说明性。
参数说明:
s:标识一个已连接套接口的描述字
buf:接收数据缓冲区
len:缓冲区长度
flags:调用操作方式
from:(可选)指针,指向装有源地址的缓冲区
fromlen:(可选)指针,指向from缓冲区长度值
返回值:如果正确接收返回接收到的字节数,失败返回0。
下面是运行的结果:
1. 关于ping程序
用于确定本地主机与网络中其它主机的网络通信情况,常使用ping程序。ping程序向指定的IP地址发送ICMP数据包,通过返回的信息来判断网络的连接状况。
ping程序的返回信息中有一个值为TTL(time to live),表示ping程序发送的icmp数据包的生存周期,每经过一个网段,TTL的值减1,当其值被减为0时,该数据包将被丢弃,但该数据包的源地址将会被告知情况,以重新发送该数据包。另外,不同操作系统的TTL值是不相同的,linux操作系统是64。
2. ICMP协议
ICMP(Internet Control Message Protocol),即网际控制报文协议,可用在网络中实现主机探测、路由维护、路由选择和流量控制。
由于IP协议没有机制来获取网络错误信息以及没有对错误进行处理,所以需要另一协议来解决这一问题,这个协议就是ICMP协议。ICMP常被认为是IP层的一部分,用于传输差错报文及控制报文。ICMP报文是封装在IP数据报内部的。
关于ICMP协议的更多内容,请参考上篇文章《ICMP协议》。
3. ping程序主要流程
创建通信的套接字-->将地址、端口信息与套接字绑定-->构建IP包头与ICMP包头-->发送构建的数据包-->接收对方主机的回应-->给出程序反馈信息
4. 涉及的一及结构体及函数接口简介
(1) struct sockaddr_in :此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。
sa_family是地址家族,一般都是“AF_xxx”的形式。通常大多用的是都是AF_INET,代表TCP/IP协议族;
sin_port存储端口号(使用网络字节顺序);
sin_addr存储IP地址,使用in_addr这个数据结构
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节;
(2) struct protoent :相关函数: getprotobyname, getprotoent, setprotoent, endprotoent
p_name: 网络协议名 p_aliases: 别名 p_proto: 网络协议编号
(3) struct hostent :hostent是host entry的缩写,该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
注:其所需要的头文件还有这一宏定义 #define h_addr h_addr_list[0]
(4) struct imcp
(5) getprotobyname(const char *protoname)
getprotobyname()会返回一个protoent结构,参数protoname为欲查询的网络协议名。此函数会从 /etc/protocols中查找符合条件的数据并由结构protoent返回。
(6) gethostbyname()
gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针。
(7) socket :函数原型 int socket(int domain, int type, int protocol);
第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置AF_INET;
第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM、原始套接字SOCK_RAW;
第三个参数指定应用程序所使用的通信协议;
该函数如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该套接字描述符表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构,另外需知套接字数据结构是在操作系统的内核缓冲里的。
(8) setsockopt():用于任意类型、任意状态套接口的设置选项值
函数原型:int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd:标识一个套接口的描述字
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
optname:需设置的选项
optval:指针,指向存放选项值的缓冲区
optlen:optval缓冲区长度
(9) inet_addr():将一个点分十进制的IP转换成一个长整数型数
(10) sendto():经socket传送数据
函数原型:int sendto ( socket s , const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen);
函数说明:sendto() 用来将数据由指定的socket传给对方主机。
参数s为已建好连线的socket,如果利用UDP协议则不需经过连线操作;
参数msg指向欲连线的数据内容,参数flags 一般设0,详细描述请参考send();
参数to用来指定欲传送的网络地址,结构sockaddr请参考bind();
参数tolen为sockaddr的结果长度;
返回值:成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。
(11) recvfrom():经socket接收数据
函数原型:ssize_t recvfrom(int sockfd,void *buf,int len,unsigned int flags, struct sockaddr *from,socket_t *fromlen);ssize_t 相当于 int,socket_t 相当于int ,这里用这个名字为的是提高代码的自说明性。
参数说明:
s:标识一个已连接套接口的描述字
buf:接收数据缓冲区
len:缓冲区长度
flags:调用操作方式
from:(可选)指针,指向装有源地址的缓冲区
fromlen:(可选)指针,指向from缓冲区长度值
返回值:如果正确接收返回接收到的字节数,失败返回0。
#include<stdio.h> #include<stdlib.h> #include<signal.h> #include<unistd.h> #include<netinet/ip_icmp.h> #include<netdb.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<sys/time.h> #include<netinet/in.h> #include<arpa/inet.h> #include<pthread.h> struct sockaddr_in dst_addr; struct sockaddr_in recv_addr; struct timeval tvrecv; char icmp_pkt[1024] = {0}; char recv_pkt[1024] = {0}; int sockfd = 0, bytes = 56, nsend_pkt = 0, nrecv_pkt = 0; pid_t pid; void statistics(); int in_chksum(unsigned short *buf, int size); int pack(int send_pkt); void *send_ping(); int unpack(char *recv_pkt, int size); void *recv_ping(); void tv_sub(struct timeval *out,struct timeval *in); int main(int argc, char **argv) { int size = 50 * 1024; int errno = -1; int ttl = 64; void *tret; pthread_t send_id,recv_id; struct in_addr ipv4_addr; struct hostent *ipv4_host; struct protoent *protocol = NULL; if (argc < 2) { printf("usage: ./ping <host>\n"); return -1; } if ((protocol = getprotobyname("icmp")) == NULL) { printf("unkown protocol\n"); return -1; } if ((sockfd = socket(AF_INET, SOCK_RAW, protocol->p_proto)) < 0) { printf("socket fail\n"); return -1; } setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); memset(&dst_addr, 0, sizeof(dst_addr)); dst_addr.sin_family = AF_INET; errno = inet_aton(argv[1], &ipv4_addr); if (errno == 0) { ipv4_host = gethostbyname(argv[1]); if (NULL == ipv4_host) { printf("connect: Invalid argument\n"); return -1; } memcpy(&(dst_addr.sin_addr), ipv4_host->h_addr, sizeof(struct in_addr)); } else { memcpy(&(dst_addr.sin_addr), &(ipv4_addr.s_addr), sizeof(struct in_addr)); } pid = getpid(); printf("PING %s (%s) %d bytes of data.\n",argv[1], inet_ntoa(dst_addr.sin_addr), bytes); signal(SIGINT, statistics); errno = pthread_create(&send_id, NULL, send_ping, NULL); if (errno != 0) { printf("send_ping thread fail\n"); return -1; } errno = pthread_create(&recv_id, NULL, recv_ping, NULL); if (errno != 0) { printf("recv_ping thread fail\n"); return -1; } pthread_join(send_id, &tret); pthread_join(recv_id, &tret); return 0; } void statistics() { printf("\n--- %s ping statistics ---\n", inet_ntoa(dst_addr.sin_addr)); printf("%d packets transmitted, %d received, %.3f%c packet loss\n", nsend_pkt, nrecv_pkt, (float)100*(nsend_pkt - nrecv_pkt)/nsend_pkt, '%'); close(sockfd); exit(0); } int in_chksum(unsigned short *buf, int size) { int nleft = size; int sum = 0; unsigned short *w = buf; unsigned short ans = 0; while(nleft > 1) { sum += *w++; nleft -= 2; } if (nleft == 1) { *(unsigned char *) (&ans) = *(unsigned char *)w; sum += ans; } sum = (sum >> 16) + (sum & 0xFFFF); sum += (sum >> 16); ans = ~sum; return ans; } int pack(int send_pkt) { struct icmp *pkt = (struct icmp *)icmp_pkt; struct timeval *time = NULL; pkt->icmp_type = ICMP_ECHO; pkt->icmp_cksum = 0; pkt->icmp_seq = htons(nsend_pkt); pkt->icmp_id = pid; time = (struct timeval *)pkt->icmp_data; gettimeofday(time, NULL); pkt->icmp_cksum = in_chksum((unsigned short *)pkt, bytes + 8); return bytes + 8; } void *send_ping() { int send_bytes = 0; int ret = -1; while(1) { nsend_pkt++; send_bytes = pack(nsend_pkt); ret = sendto(sockfd, icmp_pkt, send_bytes, 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr)); if (ret == -1) { printf("send fail\n"); sleep(1); continue; } sleep(1); } } void tv_sub(struct timeval *out,struct timeval *in) { if ((out->tv_usec-=in->tv_usec) < 0) { --out->tv_sec; out->tv_usec += 1000000; } out->tv_sec -= in->tv_sec; } int unpack(char *recv_pkt, int size) { struct iphdr *ip = NULL; int iphdrlen; struct icmp *icmp; struct timeval *tvsend; double rtt; ip = (struct iphdr *)recv_pkt; iphdrlen = ip->ihl<<2; icmp = (struct icmp *)(recv_pkt + iphdrlen); size -= iphdrlen; if (size < 8) { printf("ICMP size is less than 8\n"); return -1; } if ((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)) { tvsend = (struct timeval *)icmp->icmp_data; tv_sub(&tvrecv, tvsend); rtt = tvrecv.tv_sec * 1000 + (double)tvrecv.tv_usec / (double)1000; printf("%d byte from %s: icmp_seq = %d ttl=%d rtt=%.3fms\n", size,inet_ntoa(recv_addr.sin_addr),ntohs(icmp->icmp_seq), ip->ttl, rtt); } else { return -1; } return 0; } void *recv_ping() { fd_set rd_set; struct timeval time; time.tv_sec = 5; time.tv_usec = 0; int ret = 0, nread = 0,recv_len = 0; recv_len = sizeof(recv_addr); while(1) { FD_ZERO(&rd_set); FD_SET(sockfd, &rd_set); ret = select(sockfd + 1, &rd_set, NULL, NULL, &time); if (ret <= 0) { continue; } else if (FD_ISSET(sockfd, &rd_set)) { nread = recvfrom(sockfd, recv_pkt, sizeof(recv_pkt), 0, (struct sockaddr *)&recv_addr,(socklen_t *) &recv_len); if (nread < 0) { continue; } gettimeofday(&tvrecv, NULL); if (unpack(recv_pkt, nread) == -1) { continue; } nrecv_pkt++; } } }
下面是运行的结果:
相关文章推荐
- ping程序的C#实现
- icmp的程序(ping的实现)
- windows下ping程序使用C语言实现
- 用C语言实现Ping程序功能---转
- 用C语言实现Ping程序功能
- 【TCP/IP】C语言实现Ping小程序
- 基于C语言实现的Ping程序
- 用C语言实现Ping程序功能
- 唯快不破:ICMP报文剖析-自己实现ping程序
- 用ICMP协议(C#)实现Ping程序-在公司最近十天的工作内容总结(四)
- 用C语言实现Ping程序功能---转
- ping程序的实现
- 网络程序之ping指令的实现
- 003.同时Ping多个IP(select实现IO复用,信号计时),ping程序升级版
- Linux网络编程---ICMP协议分析及ping程序实现
- 用C语言实现Ping程序功能
- ping程序实现
- ping程序的实现
- 个人简单ping程序学习及实现
- 用C语言实现Ping程序功能