129-IPv4 地址

网络真的是一个很复杂很复杂的东西。网络编程更是相当复杂,尽管它很复杂,也不过是由人发明的,基础就在那,就看你学不学。

我们使用的网络目前都是 IPv4 (IP version 4) 版本的,所以使用到的 IP 地址也是第 4 版本的,IPv6 版本的不是不讲,是我也还没用过!!!所以,这个博客涉及到的所有的网络编程,都是基于 IPv4 的东东。在学习 IPv4 前,掌握 ip 地址相关的知识那是必须的必。(千万别问我 ip 地址是干嘛的!!!^_^)

1. IP 地址

网络编程中,各种各样表示 ip 地址的类型让人眼花缭乱,所以必须得梳理一下!

1.1 in_addr_t 还是 in_addr?

IPv4 地址,本质上是 32 位无符号整数。在 Linux 操作系统中,使用了 in_addr_t 类型来表示这样一个 4 字节的整数,它是由 typedef 定义的。

typedef uint32_t in_addr_t;

in_addr_t 这个类型保存的数据,到底是按本机字节序保存的,还是网络字节序保存的,这是不确定的!!!

为了解决此问题,Linux 又定义了一个新的结构体类型 struct in_addr,它明确的表示,它保存的 ip 地址就是网络字节序的!!!

struct in_addr {
  in_addr_t s_addr; 
}

如果说你传了一个本机字节序的 unsigned int 类型的整数给 in_addr 的 s_addr 成员,很抱歉,后面使用到该结构体的函数都会出错。

2. ip 地址表示方法

在第 1 节中也说了,ip 地址实际上就是一个 unsigned int 类型的整数。比方说接下来你和你同学说,你把你的 ip 地址配置成 0xc0a8a605 吧……你同学可能觉得你是从火星来的。。。

所以说,生活中使用整数来表示 ip 地址是相当不方便的,我们平时实际上都是用“点分十进制(dotted-decimal)”这种表示方法,比如 192.168.1.1 等等类似这样的。

点分十进制表示的 ip 地址和整数表示法之间的转换相当容易,就比如说 0xc0a8a605,先将其拆分成 4 个字节:c0.a8.a6.05,然后将 4 个部分的 16 进制数变成 10 进制,即 192.168.166.5,是不是很容易?

3. 制作 in_addr 类型的地址

在掌握了第 2 节中的 ip 地址表示方法后,相信你会非常容易创建一个 in_addr 类型的地址。比方我们需要把 192.168.166.5 保存成 in_addr 类型的地址,编写下面的代码就行了:

struct in_addr addr;
addr.s_addr = htonl(0xc0a8a605);

我知道有同学又要吐槽了,好麻烦!!!还得自己先把点分十进制转成整数,然后再把整数转成网络字节序,是不是心中有一万头草泥马在狂奔……

还好,Linux 提供一些函数,能够相当方便的帮我们把点分十进制的 ip 地址转换成 in_addr 类型。

4. 和 ip 地址转换相关的函数

下面这些函数中,aton 中的 a(address) 表示点分十进制的地址,n 表示网络字节序地址,ntoa 类似。

函数 lnaof 表示 local network address part of the Internet address in,即主机号。

函数 netof 表示 network number part of the Internet address in,即网络号。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 将点分十进制直接转换成 in_addr 类型,我们很喜欢这个函数
int inet_aton(const char *cp, struct in_addr *inp);

// 将点分十进制转换成 in_addr_t 类型,返回值保存的是网络字节序
in_addr_t inet_addr(const char *cp);

// 将点分十进制转换成 in_addr_t 类型,返回值保存的是本机字节序
in_addr_t inet_network(const char *cp);

// 将 in_addr 地址转换成点分十进制,注意这个函数不是线程安全的
char *inet_ntoa(struct in_addr in);

// 根据网络号和主机号制作 in_addr 类型地址
struct in_addr inet_makeaddr(int net, int host);

// 返回 ip 地址的主机号,返回的结果是本机字节序的
in_addr_t inet_lnaof(struct in_addr in);

// 返回 ip 地址的网络号,返回的结果是本机字节序的
in_addr_t inet_netof(struct in_addr in);

5. 相关程序封装

这里的实验是相当有意思的,我把每个函数都单独写在了不同程序中,程序的名字就是函数的名字。每个程序都通过标准输入和标准输出接收参数和打印结果,如此一来,不同的程序就可以通过管道互相连接啦。

5.1 htonl 程序

// htonl.c
#include <arpa/inet.h>
#include <stdio.h>
int main() {
  int host;
  scanf("%x", &host);
  printf("0x%08x\n", htonl(host));
  return 0;
}

5.2 inet_aton 程序

// inet_aton.c
#include <arpa/inet.h>
#include <stdio.h>

int main() {
  struct in_addr addr;
  char ip[16] = { 0 };
  scanf("%s", ip);
  if (inet_aton(ip, &addr) == 0) {
    fprintf(stderr, "Invalid address\n");
    return 1;
  }
  printf("0x%08x\n", addr.s_addr);
  return 0;
}

5.3 inet_addr 程序

// inet_addr.c
#include <arpa/inet.h>
#include <stdio.h>
int main() {
  char ip[16] = { 0 };
  scanf("%s", ip);
  printf("0x%08x\n", inet_addr(ip));
  return 0;
}

5.4 inet_network 程序

// inet_network.c
#include <arpa/inet.h>
#include <stdio.h>

int main() {
  char ip[16] = { 0 };
  scanf("%s", ip);
  printf("0x%08x\n", inet_network(ip));
  return 0;
}

5.5 inet_lnaof 程序

// inet_lnaof.c
#include <arpa/inet.h>
#include <stdio.h>
int main() {
  struct in_addr addr;
  addr.s_addr = 0;

  scanf("%x", &addr.s_addr);
  printf("0x%08x\n", inet_lnaof(addr));
  return 0;
}

5.6 inet_netof 程序

// inet_netof.c
#include <arpa/inet.h>
#include <stdio.h>

int main() {
  struct in_addr addr;
  addr.s_addr = 0;

  scanf("%x", &addr.s_addr);
  printf("0x%08x\n", inet_netof(addr));
  return 0;
}

5.7 inet_ntoa 程序

// inet_ntoa.c
#include <arpa/inet.h>
#include <stdio.h>

int main() {
  struct in_addr addr;
  addr.s_addr = 0;

  scanf("%x", &addr.s_addr);
  printf("%s\n", inet_ntoa(addr));
  return 0;
}

5.8 inet_makeaddr 程序

// inet_makeaddr.c
#include <arpa/inet.h>
#include <stdio.h>
int main() {
  int net, host;
  struct in_addr addr;

  scanf("%x %x", &net, &host);
  addr = inet_makeaddr(net, host);
  printf("0x%08x\n", addr.s_addr);
  return 0;
}

5.9 Makefile 文件

main:inet_addr inet_aton inet_network inet_lnaof inet_netof inet_ntoa inet_makeaddr htonl

inet_addr:inet_addr.c
  gcc inet_addr.c -o inet_addr

inet_aton:inet_aton.c
  gcc inet_aton.c -o inet_aton

inet_network:inet_network.c
  gcc inet_network.c -o inet_network

inet_lnaof:inet_lnaof.c
  gcc inet_lnaof.c -o inet_lnaof

inet_netof:inet_netof.c
  gcc inet_netof.c -o inet_netof

inet_ntoa:inet_ntoa.c
  gcc inet_ntoa.c -o inet_ntoa

inet_makeaddr:inet_makeaddr.c
  gcc inet_makeaddr.c -o inet_makeaddr

htonl:htonl.c
  gcc htonl.c -o htonl 

6. 实验

把第 5 节中的所有程序复制到你的机器中,然后执行 make 命令就可以一次全部编译完成。


这里写图片描述
图1 使用 make 生成程序

这里我们需要一个文件 ip.txt,这个文件用下面的命令创建:

$ echo "192.168.166.5" > ip.txt

6.1 将点分十进制转换成网络字节序

有两个函数可以做这件事,如图 2.


这里写图片描述
图2 将点分十进制转换成网络字节序的地址

这两个函数是有区别的,inet_addr 函数如果出错会返回 -1,即 0xffffffff,因此地址 255.255.255.255 是无法转换的,虽然将此地址转换成的结果也是 0xffffffff,但是错误的 ip 地址比如 300.200.200.200 转换的结果也是 0xffffffff,所以 inet_addr 在不十分严谨的情况下才会使用。

inet_aton 是比较正式的,它单独使用返回值判断转换的地址是否有效,如果返回为 0,表示转换的地址是错误的。


这里写图片描述
图3 演示非法地址的转换情况

6.2 将点分十进制转换成本机字节序


这里写图片描述
图4 将点分十进制转换成本机字节序

6.3 将网络字节序的地址转换成点分十进制


这里写图片描述
图5 将网络字节序的地址转换成点分十进制

上面的过程表示:先由程序 inet_addr 将 ip.txt 中的地址转换成网络字节序的整数,再通过管道送入程序 inet_ntoa.

6.4 计算网络字节序 ip 的主机号和网络号

注意,这两个函数返回的结果都是本机字节序。


这里写图片描述
图6 计算网络字节序 ip 的主机号和网络号

6.5 根据网络号和主机号制作一个 in_addr 类型地址


这里写图片描述
图7 根据网络号和主机号制作一个 in_addr 类型地址

7. 总结

  • in_addr_t 和 in_addr 的区别和联系
  • in_addr 是网络字节序的
  • in_addr_t 是什么字节序是不确定的
  • 掌握相关的转换函数
  • inet_addr 和 inet_aton 的区别
  • inet_addr 和 inet_network 的区别

有关网络号和主机号,以及更多有关 ip 地址的知识,请参考谢希仁的《计算机网络》第 5 版。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值