学习C网络编程笔记
ping 命令
ping命令是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。
|
|
ping与ICMP
要真正了解 ping 命令实现原理,就要了解 ping 命令所使用到的 TCP/IP 协议:ICMP 协议。
ICMP 是(Internet Control Message Protocol)Internet 控制报文协议。它是 TCP/IP 协议族的一个子协议,用于在 IP 主机、路由器之间传递控制消息。
控制消息有:目的不可达消息,超时信息,重定向消息,时间戳请求和时间戳响应消息,回显请求和回显应答消息。
ping 命令使用回显请求和回显应答消息。具体表现是向网络上的另一个主机系统发送 ICMP 报文,如果指定系统得到了报文,它将把报文一模一样地传回给发送者。
回显请求报文其中类型为 0,代码为 0。
回显应答报文其中类型为 8,代码为 0。
校验和字段:包括数据在内的整个 ICMP 协议数据包的校验和,具体实现方法会在下面详细介绍。
标识符字段:用于唯一标识 ICMP 报文,本项目使用程序的进程 id。因为如果同时在两个命令行终端执行 ping 命令的话,每个 ping 命令都会接收到所有的回显应答,所以需要根据标识符来判断回显应答是否应该接收。
序号字段:ICMP 报文的序号。
数据字段:也就是报文,本项目中我们将发送报文的时间戳放入数据字段,这样当接收到该报文应答的时候可以取出发送时间戳,将接收应答的时间戳减去发送时间戳就是报文往返时间(rtt)。提前预告一下,这里使用gettimeofday()API函数获取时间戳,详细介绍会在函数介绍部分说明。
数据结构
ICMP 报文 C 语言实现可以用下面的数据结构表示:
地址信息表示
当我们编写网络应用程序时,必然要使用地址信息指定数据传输给网络上哪个主机,那么地址信息应该包含哪些呢?
- 地址族,基于IPv4的地址族还是IPv6的地址族。
- IP地址。
- 端口号。
为了便于记录地址信息,系统定义了如下结构体:123456struct sockaddr_in{sa_family_t sin_family; // 地址族uint16_t sin_port; // 端口号struct in_addr sin_addr; // 32位IP地址char sin_zero[8]; // 不使用};
其中struct in_addr定义
in_addr_t使用如下宏指令定义,也就是无符号整型32位。
但实际上,还有一种结构体也可以表示地址信息,如下所示:
成员sa_data保存的信息包含IP地址和端口号,剩余部分填充0。
在网络编程中,常用的是struct sockaddr_in结构体,因为相对于struct sockaddr结构体,前者填充数据比较方便。
不过网络编程接口函数定义使用的是struct sockaddr结构体类型,这是由于最先使用的是struct sockaddr结构体,struct sockaddr_in结构体是后来为了方便填充地址信息数据定义。这就出现矛盾了,不过也不用担心上面两个结构体之间是可以相互转换的。定义地址信息时使用struct sockaddr_in结构体,然后将该结构体类型转为struct sockaddr结构体类型传递给网络编程接口函数即可。
相关函数
gettimeofday()
|
|
该函数的作用是把当前的时间放入struct timeval结构体中返回。
注意:
1.精确级别,微妙级别
2.受系统时间修改影响
3.返回的秒数是从1970年1月1日0时0分0秒开始
其参数tv是保存获取时间结果的结构体,参数tz用于保存时区结果。
结构体timeval的定义为:
|
|
结构体timezone的定义为:
timezone 参数若不使用则传入0即可,本项目传入0。
inet_addr函数。
|
|
该函数的作用是将用点分十进制字符串格式表示的IP地址转换成32位大端序整型。
成功时返回32位大端序整型数值,失败时返回INADDR_NONE。
gethostbyname
|
|
该函数的作用是根据域名获取IP地址。
成功时返回hostent结构体地址,失败时返回NULL指针。
struct hosten结构体定义如下:
我们最关心的是h_addr_list成员,它保存的就是域名对应IP地址。由于一个域名对应的IP地址不止一个,所以h_addr_list成员是char **类型,相当于二维字符数组。
socket。
|
|
校验
检验和算法可以分成两步来实现。
- 首先在发送端,有以下三步: 1.把校验和字段置为0。
- 对需要校验的数据看成以16bit为单位的数字组成,依次进行二进制求和。
将上一步的求和结果取反,存入校验和字段。
其次在接收端,也有相应的三步:对需要校验的数据看成以16bit为单位的数字组成,依次进行二进制求和,包括校验和字段。
将上一步的求和结果取反。
判断最终结果是否为0。如果为0,说明校验和正确。如果不为0,则协议栈会丢掉接收到的数据。
从上可以看出,归根到底,校验和算法就是二进制反码求和。由于先取反后相加与先相加后取反,得到的结果是一样的,所以上面的步骤都是先求和后取反。
下面用C语言来实现校验和算法,代码如下:
上面的代码首先定义了一个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秒后接收ICMP响应报文,并打印TTL,RTT。
第三步:循环第二步N次,本项目设置为5。
第四步输出统计信息。
完整代码: