Ping的c语言实现
函数介绍(续)
重要的函数: socket
int socket(int family, int type, int protocol); 返回值:成功时返回文件描述符 / 失败时返回-1 头文件: #include <sys/socket.h> 函数作用:创建一个套接字 一般为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数
函数参数
第一个参数family:指明套接字中使用的协议族信息。 常见值有: 第二个参数type:指明套接口类型,也即套接字的数据传输方式。 常见值有: 在常见的使用socket进行网络编程中,经常使用SOCK_STREAM和SOCK_DGRAM,也就是TCP和UDP编程。在本项目中,我们将使用SOCK_RAW(原始套接字)。 原始套接字的主要作用在三个方面: 1.通过原始套接字发送/接收 ICMP 协议包。 2.接收发向本级的,但 TCP/IP 协议栈不能处理的IP包。 3.用来发送一些自己制定源地址特殊作用的IP包(自己写IP头)。 ping 命令使用的就是 ICMP 协议,因此我们不能直接通过建立一个 SOCK_STREAM或SOCK_DGRAM 来发送协议包,只能自己构建 ICMP 包通过 SOCK_RAW 来发送。 第三个参数 protocol:指明协议类型。 常见值有: 图片描述信息 参数 protocol 指明了所要接收的协议包。 如果指定了 IPPROTO_ICMP,则内核碰到ip头中 protocol 域和创建 socket 所使用参数 protocol 相同的 IP 包,就会交给我们创建的原始套接字来处理。 因此,一般来说,要想接收什么样的数据包,就应该在参数protocol里来指定相应的协议。当内核向我们创建的原始套接字交付数据包的时候,是包括整个IP头的,并且是已经重组好的IP包。如下所示: 图片描述信息 这里的数据也就是前面所说的时间戳。 但是,当我们发送IP包的时候,却不用自己处理IP首部,IP首部由内核自己维护,首部中的协议字段被设置成调用 socket 函数时传递给它的第三个参数。 我们发送 IP 包时,发送数据时从 IP 首部的第一个字节开始的,所以只需要构造一个如下所示的数据缓冲区就可以了。 图片描述信息 如果想自己处理 IP 首部,则需要设置 IP_HDRINCL 的 socket 选项,如下所示: int flag = 1; setsocketopt(sockfd, IPPROTO_TO, IP_HDRINCL, &flag, sizeof(int)); 此时,我们需要构造如下所示的数据缓冲区。 注意,我们自己填充 IP 首部时,也不是填充 IP 首部的所有字段,而是应该将 IP 首部的 id 字段设置为0,表示让内核来处理这个字段。同时,内核还会自动完成 IP 首部的校验和的计算并填充。 最后介绍发送和接收 IP 包的两个函数:recvfrom 和 sendto。
1 | #include <sys/socket.h> |
校验和算法
检验和算法在 TCP/IP 协议族中是比较常见的算法。 IP、ICMP、UDP和TCP报文头部都有校验和字段,不过IP、TCP、UDP只针对首部计算校验和, 而 ICMP 对首部和报文数据一起计算校验和。 检验和算法可以分成两步来实现。 首先在发送端,有以下三步: 1.把校验和字段置为0。 2.对需要校验的数据看成以16bit为单位的数字组成,依次进行二进制求和。 3.将上一步的求和结果取反,存入校验和字段。 其次在接收端,也有相应的三步: 1. 对需要校验的数据看成以16bit为单位的数字组成,依次进行二进制求和,包括校验和字段。 2. 将上一步的求和结果取反。 3. 判断最终结果是否为0。如果为0,说明校验和正确。如果不为0,则协议栈会丢掉接收到的数据。 从上可以看出,归根到底,校验和算法就是二进制反码求和。由于先取反后相加与先相加后取反,得到的结果是一样的,所以上面的步骤都是先求和后取反。 下面用C语言来实现校验和算法,代码如下:
1 | /** |
上面的代码首先定义了一个32位无符号整型的变量sum,用来保存16bit二进制数字相加的结果,由于16bit相加可能会产生进位,所以这里使用32位变量来保存结果,其中高16bit保存的是相加产生的进位。 然后下面的 while 循环,对数据按16bit累加求和。 接下来的if语句判断是否还剩下8bit(一字节)。如果校验的数据为奇数个字节,会剩下最后一字节。把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加。 之后的两行代码作用是将 sum 高16bit的值加到低16bit上,即把累加中最高位的进位加到最低位上。(sum >> 16)将高16bit右移到低16bit,(sum & 0xffff)将高16bit全部置为0。注意,这两步都不会改变sum原来的值。 进行了两次相加可以保证 sum 高16bit都为0,没有进位了。 最后取反,并返回。 扩展: 为什么使用二进制反码求和,而不是原码或补码呢? 这是因为,使用反码计算校验和比较简单和快速。对于网络通信来说,最重要的就是效率和速度。
编码实现
整个程序的流程图如下所示: 图片描述信息 第一步,首先创建原始套接字。 第二步,封装 ICMP 报文,向目的IP地址发送 ICMP 报文,1秒后接收 ICM P响应报文,并打印 TTL,RTT。 第三步:循环第二步N次,本项目设置为5。 第四步:输出统计信息。
栗子:
1 | #include <stdio.h> |
执行提示 socket() error 错误,也就是调用 socket 函数的时候出现了错误。这是因为我们创建的是原始套接字,原始套接字必须有 root 权限才能创建,所以我们可以加 sudo 执行 Ping笔记(一)
参考文献
原文链接: https://www.delta1037.cn/2017/Linux/Ping笔记(二)/
版权声明: 转载请注明出处.