1、背景

在进行网络编程的时候,通常使用的协议有TCP协议,UDP协议。这些协议在简历套接字之初需要制定套接字的类型,比如TCP应当设置为 SOCK_STREAM,

UDP对应的套接字应当设置为SOCK_DGRAM。但是这些套接字并非能够提供网络所需的全部功能,我们还需要其他的套接字,比如原始套接字OCK_RAW。原始

套接字可以提供SOCK_STREAM和SOCK_DGRAM所不及的能力。比如:

(1)有了原始套接字,进程可以读取ICMPV4、ICMPV6、IGMP等的分组。正如ping所使用的套接字,就是SOCK_RAW类型的。这样使得使用ICMP和IGMP的程

完全能够作为用户进程处理,而无需向内核添加代码。

(2)有了原始套接字,进程可以处理内核不处理其协议字段的IPV4数据报。

(3)有了原始套接字,进程使用IP_HDRINCL套接字选项定制自己的IPV4头部。

当然,上述的三个功能,并不是本文都要涉及的;只关注第一个能力,编写代码,实现ping程序。

2、基本使用

a.定义原始套接字与定义其他套接字没有形式上的巨大差别。

int sockfd;

sockfd = socket(AF_INET, SOCK_RAW, protocol);

protocol 的值是型为 IPPROTO_XXX的量,这些量定义在中,比如ping使用的 IPPROTO_ICMP(关于IPV6的实现,再后续补充)。

只有超级用户才可以创建SOCK_RAW类型的套接字。

b. 原始套接字并不存在端口的概念。可以在原始套接字上调用bind函数,但是这么做并不常见。bind函数会设置发送数据报的源IP地址,如果没有使用

bind函数,那么内核将出发的借口地址作为源地址。

c. 同样,一般不会使用connect函数,connect函数会指定目的地址,但是因为原始套接字不存在端口概念,所以connect函数并不重要了。

使用sendto函数发送原始套接字的数据。

3、ping简介

ping是检查网络是否通畅或者网络连接速度的命令,它的原理是想服务端发送一个定长的数据包,再要求对方返回一个同样大小的数据包来确定两台网络机器

是否连接相通,延迟是多少。

先看一下,ip协议的头部和ICMP协议的头部。

IP头部一般在ICMP协议中没有附加数据,所以IP头部的长度为20字节,ICMP头部的长度为8字节,规定ICMP数据包的数据部分为56字节,那么最终使用的

ICMP的数据包的长度为64字节。

ICMP的头部数据结构:

在Linux中定义了不同协议的头部信息,对于ICMP协议的头部定义如下:

structicmphdr {

unsignedchartype; //标记当前ICMP数据包的类型 ECHO、ECHOREPLY等等。

unsignedcharcode; //code取值与type协同,或者对type具体化

unsignedshortchecksum;   //校验和

union {struct{

unsignedshortid;

unsignedshortsequence;

} echo;

unsignedlonggateway;

} un;

};

这个ICMP的头部可能与最新的版本不太一致,但是基本结构是类似的。从这个结构可以计算出来,对于32位的机器来说,这个头部的长度就是8字节。

IP头部如果不考虑附加信息的话,固定长度应该为20字节。当然在ping代码中,我们并不考虑IP头部的初始化等操作,只是了解一下。

1 structiphdr {2 #if defined(LITTLE_ENDIAN_BITFIELD)

3 __u8 ihl:4,4 version:4;5 #elif defined (BIG_ENDIAN_BITFIELD)

6 __u8 version:4,7 ihl:4;8 #else

9 #error "Please fix "

10 #endif

11 __u8 tos;12 __u16 tot_len;13 __u16 id;14 __u16 frag_off;15 __u8 ttl;16 __u8 protocol;17 __u16 check;18 __u32 saddr;19 __u32 daddr;20 /*The options start here.*/

21 };

基本的头部信息就介绍到这里,现在说一下,发送和接收ICMP数据包的过程。

对于一个ICMP数据包来说,我们要做的大致分为这么几步:

1、申请一个空间sendbuf,对这个缓冲区内容的更改结果,作为我们生成的ICMP数据包。

2、在写入ICMP的头部以后,将这个缓冲区的内容交给原始套接字发送,并等待服务端的ICMP回复。

3、收到返回的数据放入recvbuf中,此时收到的数据,是包含了IP头部的,我们首先解析IP头部,然后解析ICMP头部,最后解析数据包的内容。

4、循环往复上述步骤。

说起来容易做起来就就不容易了。现在一步一步的做吧。

首先,我们要发送一个ICMP数据包,那目的IP地址要知道的,但是一般IP都记不住的,而且有的服务器IP地址有很多,所以我们一般都是通过域名来获取IP

地址。相对gethostent()函数,getaddrinfo()更加优秀一些,怎么个优秀法,咱们别处再说,在这里只说怎么获取的。代码丑陋,将就看。

voidGetAddrInfo(char *host, char *canonname, char *ip_char, int *sockfamily,

socklen_t *addrlen, struct sockaddr *send_addr){int i = 0;int retval = 0;char IPParse[20];char *service = "domain";structaddrinfo hint ;struct addrinfo *result, *p, *q;

memset(&hint, 0, sizeof(hint));

hint.ai_flags=AI_CANONNAME;

hint.ai_family=AF_INET;retval= getaddrinfo(host, service, &hint, &result);if(retval != 0)return;

printf("We successfully get access to the Host %s\n",host);if(result->ai_flags ==AI_CANONNAME){

printf("AI_CANONNAMW\n");

strcpy(canonname, result->ai_canonname);

}if(result->ai_family ==AF_INET){

printf("AF_INET\n");*sockfamily = result->ai_family;

}if(result->ai_addr){

inet_ntop(result->ai_family, (void *)result->ai_addr, ip_char, result->ai_addrlen);*send_addr = *(result->ai_addr); // CANONNAME 对应的IP地址

printf("IP Address We Get Is %s\n",ip_char);

}if(result->ai_addrlen > 0)*addrlen = result->ai_addrlen;

i=0; //虽然函数回调后会自动释放内存,但是显示的释放还是可取的

p=result;

q= p->ai_next;while(p)

{

q= p->ai_next;

free(p);

p=q;

i++;

}

printf("There are %d Address Here\n",i);}

运行这个函数,会发现result链表中的IP地址都一样,这是咋回事呢?这与getaddrinfo()中的参数hint有关,怎么个关系,不讨论。我们知道这个函数可以

获得一个可用的IP地址结构,可以用来进行ICMP数据包的发送就好了。

然后,现在有一个IP地址了,那就可以建立一个原始套接字了。

sockfd =socket(sockfamily, SOCK_RAW, IPPROTO_ICMP);

setuid(getuid());//so we get the root authority

现在,地址有了,套接字有了,再申请个空间,套上ICMP的头部,我们就可以给服务器发过去了。

void send_ipv4(void)

{intlen;

int datalen = 56;struct icmp *icmp;

char sendbuf[1500];

icmp= (struct icmp *)sendbuf;

icmp->icmp_type =ICMP_ECHO;

icmp->icmp_code = 0;

icmp->icmp_id =getpid();

icmp->icmp_seq = nsent++;

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

gettimeofday((struct timeval *)icmp->icmp_data, NULL); //用这个计算RTT

len= 8+datalen; //这里为啥要+8 ? 因为ICMP头部长度是 8 字节

icmp->icmp_cksum = 0;

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

sendto(sockfd, sendbuf, len,0, &out_send_addr, out_addr_len);

}

ok,到此基本上发送端都搞定了,这里面有一个函数in_cksum()用于校验,这个函数网上哪儿哪儿都是,感兴趣的就搜一下。要是能弄明白为啥加来加去

然后各种与还有移位,那就更好了。

发送结束后,服务器会回复相应的数据包,然后咱们就可以拿着这个数据包计算我们想要的数据了,怎么处理呢?看代码。。

void proc_ipv4(char *recvbuf,ssize_t recv_len, struct msghdr *msg, struct timeval *time_recv)

{intip_hdr_len;

char recvbuf[1500];inticmplen;struct ip *ip;struct icmp *icmp;struct timeval *time_send;char char_seq[20];doublertt;

ip= (struct ip*)recvbuf; //收到的是包含IP头部的数据包

ip_hdr_len= ip->ip_hl>>2; //这么做是有道理的,看看IP数据包的头部就知道了。if(ip->ip_p !=IPPROTO_ICMP){

printf("This Packet Is Not A ICMP \n");return;

}

icmp= (struct icmp *)(recvbuf +ip_hdr_len);if((icmplen = recv_len - ip_hdr_len) < 8){

printf("Malformed Packet\n");return;

}if(icmp->icmp_type ==ICMP_ECHOREPLY){if(icmp->icmp_id !=getpid()){

printf("This Packet belong to other Process");return;

}if(icmplen < 16){

printf("No Enough Data For processing\n");return;

}

time_send= (struct timeval*)icmp->icmp_data;

tv_sub(time_recv, time_send);

rtt=time_recv->tv_sec * 1000.0 + time_recv->tv_usec/1000.0;

printf("Recv %d Bytes from %s: Seq: %d ttl = %d, rtt: %.3f ms\n",

icmplen,

inet_ntop(AF_INET, (void *)msg->msg_name, char_seq,msg->msg_namelen ),

icmp->icmp_seq,

ip->ip_ttl,

rtt);

}else{

printf("Fail To Process\n");

}

}

这里面用到了一个计算时间差的函数tv_sub(),具体实现是这样的,比较适合重复利用。

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;

}

现在,主要的代码部分都有了,至于怎么循环发送很多包,怎么依次接收,要不要定时器等等,这些都仁者见仁智者见智了,方法很多,就不再赘述了。

上面的代码都是参考<>编写的,没有关注IPV6的部分,因为初始入门,或者为了探究ICMP和原始套接口,例子越简单越好。

其实从本文可以看出,原始套接字并不复杂,复杂的还是程序设计、协议理解。真正的ICMP协议可不是这么实现的,看看Linux源代码就知道了,那里面的实现

数据结构是基于sk_buff,icmp_hdr,ip_hdr等等。咱们写的这个也就是玩玩而已,这个代码实现了ICMP协议的五分之一功能吧,不会再多了。

linux 网络编程 ping,Linux 网络编程基础(4) -- Ping 的C代码实现相关推荐

  1. 【socket】从计算机网络基础到socket编程——Windows Linux C语言 + Python实现(TCP+UDP)

    一.部分基础知识 1.1 计算机网络的体系结构 1.11 互联网简介 1.12 计算机网络的分类 1.13 协议与网络的分层体系结构 ▶ 协议 ▶ 网络的分层体系结构 1.14 OSI 七层模型(重要 ...

  2. Linux网络编程之套接字基础

    Linux网络编程之套接字基础 1.套接字的基本结构 struct sockaddr 这个结构用来存储套接字地址. 数据定义: struct sockaddr { unsigned short sa_ ...

  3. Linux网络编程篇之ICMP协议分析及ping程序实现

    Linux网络编程系列: Linux网络编程篇之Socket编程预备知识 Linux网络编程篇之TCP协议分析及聊天室功能实现 如果对Linux网络编程,对socket通信不是太清楚的同学,强烈推荐看 ...

  4. 嵌入式学习笔记-linux应用编程和网络编程-3.8 网络基础

    一.再论进程 1.从进程间通信说起 网络域套接字socket,网络通信其实就是位于网络中不同主机上面的2个进程之间的通信. 2.网络通信概述 硬件部分:网卡 操作系统底层:网卡驱动 操作系统API:s ...

  5. IT视频课程集(包含各类Oracle、DB2、Linux、Mysql、Nosql、Hadoop、BI、云计算、编程开发、网络、大数据、虚拟化

    马哥Linux培训视频课程:http://pan.baidu.com/s/1pJwk7dp Oracle.大数据系列课程:http://pan.baidu.com/s/1bnng3yZ 天善智能BI培 ...

  6. C++教程网之Linux网络编程视频 Unix网络编程视频

    教程非常不错,价值280元,绝对是干货 Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章. Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 ...

  7. 网络编程二-LINUX网络IO模型

    目录 前言:网络编程里通用常识 一.同步和异步与阻塞和非阻塞 同步和异步 阻塞和非阻塞 两者的组合 二.五种I/O模型 阻塞I/O模型 非阻塞IO模型 IO复用模型 信号驱动IO 异步IO模型 5个I ...

  8. 【Linux网络编程】TCP网络编程中connect listen和accept三者之间的关系

    00. 目录 文章目录 00. 目录 01. TCP服务端和客户端流程 02. connect函数 03. listen函数 04. 三次握手 05. accept函数 06. 附录 01. TCP服 ...

  9. 【Linux网络编程】TCP网络编程中connect()、listen()和accept()三者之间的关系

    基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下: connect()函数 对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三 ...

最新文章

  1. 保证计算机网络的稳定运行,厦门大学校园网管理保证网络稳定运行
  2. 惠普10亿美元锻造Helion云品牌
  3. 全屏显示的包含webview的页面中弹出的软键盘覆盖输入框的问题
  4. =空值返回空值_@ParameterizedTest在@CvsSource中具有空值
  5. [小技巧][JAVA][转换]List, Integer[], int[]的相互转换
  6. Shell 基础介绍 [1]
  7. 了解VS2005为你的MFC程序做的一些事
  8. 有钱人也开始消费降级了!
  9. 为qt程序添加ico图标
  10. 解决高并发(数据库锁机制)
  11. oracle odac 配置,.Net項目中通過ODAC方式鏈接Oracle數據庫相關配置
  12. BZOJ5336 DP套DP
  13. 讲讲传奇架设教程跟传奇开区教程,我们首先要明白传奇如何形成
  14. OpenCV2:特征匹配及其优化
  15. python看网络电视
  16. .NET5.0 初始
  17. 2021-10-13-草稿纸
  18. SPI 及 NOR Flash 介绍
  19. 图片在线收款发货系统源码
  20. 出包王女全集名字和顺序

热门文章

  1. Django源码分析6:auth认证及登陆保持
  2. E: Could not get lock /var/lib/dpkg/lock-frontend - open (11: Resource temporarily unavailable)E: U
  3. 规格选项表管理之查询获取规格选项表列表数据
  4. 创建Django项目和模型(创建工程、子应用、设置pycharm环境、使用Django进行数据库开发的步骤)
  5. OpenCV中直方图反向投影算法详解与实现
  6. 卷积神经网络如何处理一维时间序列数据?
  7. Python加速运行技巧
  8. HTML5 - Websocket
  9. Eclipse搭建java分布式商城项目
  10. Confluence 6 指派和撤销空间权限