客户端-服务端模型
一个连接可以通过客户端-服务器端的IP和端口唯一确定,这叫做套接字对,按照下面的四元组表示:(clientaddr:clientport, serveraddr: serverport)。下图表示了一个客户端-服务器之间的连接:
保留网段
国际标准组织在IPv4地址空间里面,专门划出了一些网段,这些网段不会用做公网上的IP,而是仅仅保留做内部使用,我们把这些地址称作保留网段。下表是三个保留网段,其可以容纳的计算机主机个数分别是 16777216 个、1048576 个和 65536 个。
子网掩码
在网络IP划分的时候,我们需要区分两个概念。第一是网络(network)的概念,直观点说,它表示的是这组IP共同的部分,比如在192.168.1.1-192.168.1.255这个区间里,它们共同的部分是192.168.1.0。第二是主机(host)的概念,它表示的是这组IP不同的部分,上面的例子中1-255就是不同的那些部分,表示有255个可用的不同IP。例如IPv4地址,192.0.2.12,我们可以说前面三个bytes是子网,最后一个byte是host,或者换个方式,我们能说host为8位,子网掩码为192.0.2.0/24(255.255.255.0)。
很久以前,有子网(subnet)的分类,在这里,一个IPv4地址的第一个,前两个或前三个字节是属于网络的一部分。如果你很幸运地可以拥有一个字节的网络,而另外三个字节是host地址,那在你的网络里,你有价值三个字节,也就是24个比特的主机地址,这是什么概念呢?2的24次方,大约是一千六百万个地址左右。这是一个“Class A”(A 类)网络。
再来重新看一下保留网段的这张表格,表格第一行就是这样的一个A类网络,10是对应的网络字节部分,主机的字节是3,我们将一个字节的子网记作255.0.0.0。相对的,“ClassB”(B类)的网络,网络有两个字节,而host只有两个字节,也就是说拥有的主机个数为65536。“ClassC”(C类)的网络,网络有三个字节,而host只有一个字节,也就是说拥有的主机个数为256。
网络地址位数由子网掩码(Netmask)决定,你可以将IP地址与子网掩码进行“位与”操作,就能得到网络的值。子网掩码一般看起来像是255.255.255.0(二进制为11111111.11111111.11111111.00000000),比如你的IP是192.0.2.12,使用这个子网掩码时,你的网络就会是192.0.2.12与255.255.255.0所得到的值:192.0.2.0,192.0.2.0就是这个网络的值。
子网掩码能接受任意个位,而不单纯是上面讨论的8,16或24个比特而已。所以你可以有一个子网掩码255.255.255.252(二进制位11111111.11111111.11111111.11111100),这个子网掩码能切出一个30个位的网络以及2个位的主机,这个网络最多有四台host。为什么是4台host呢?因为不变的部分只有最后两位,所有的可能为2的2次方,即4台host。
注意,子网掩码的格式永远都是二进制格式:前面是一连串的1,后面跟着一连串的0。不过一大串的数字会有点不好用,比如像255.192.0.0这样的子网掩码,人们无法直观地知道有多少个1,多少个0,后来人们发明了新的办法,你只需要将一个斜线放在IP地址后面,接着用一个十进制的数字用以表示网络的位数,类似这样:192.0.2.12/30,这样就很容易知道有30个1,2个0,所以主机个数为4。
例如,从172.16.0.0/12这个IP中得出信息,172.16.0.0为B类网,12为网络号,默认B类网的网络号(也就是子网掩码中1的个数)是16位,而此处为12位,那么便有2^(16-12)=16个连续子网。而对于192.168.0.0/16,192.168.0.0为C类网,16为网络号,默认C类网的网络号是24位,而此处为16位,那么便有2^(24-16)=256个连续的子网。注意,这里说的子网是说网络,并不是说可连接的主机数。从以上的分析可以看出,子网掩码决定了不同类型网络中子网的个数。
全球域名系统
全球域名按照从大到小的结构,形成了一棵树状结构。实际访问一个域名时,是从最底层开始写起,例如www.google.com,www.tinghua.edu.cn等。结构如下图:
套接字和地址
在网络编程中,我们经常会提到socket这个词,它的中文翻译为套接字,有的时候也叫做套接口。在网络编程中,到底应该怎么理解socket呢?首先看一张图:
这张图表达的其实是网络编程中,客户端和服务器工作的核心逻辑。具体来说,客户端进程向操作系统内核发起write字节流写操作,内核协议栈将字节流通过网络设备传输到服务器端,服务器端从内核得到信息,将字节流从内核读入到进程中,并开始业务逻辑的处理,完成之后,服务器端再将得到的结果以同样的方式写给客户端。可以看到,一旦连接建立,数据的传输就不再是单向的,而是双向的,这也是TCP的一个显著特性。
以上所有的操作,都是通过socket来完成的。无论是客户端的connect,还是服务端的accept,或者read/write操作等,socket是我们用来建立连接,传输数据的唯一途径。
在使用套接字时,首先要解决通信双方寻址的问题。我们需要套接字的地址建立连接,就像打电话时首先需要查找电话簿,找到你想要联系的那个人,你才可以建立连接,开始交流。下面先看一下套接字的通用地址结构:
1 | /* POSIX.1g 规范规定了地址族为 2 字节的值. */ |
在这个结构体里,第一个字段是地址族,它表示使用什么样的方式对地址进行解释和保存,好比电话簿里的手机格式,或者是固话格式,这两种格式的长度和含义都是不同的。地址族常用的有:AF_LOCAL(本地地址,对应的是Unix套接字,这种情况一般用于本地socket通信,很多情况下也可以写成AF_UNIX、AF_FILE)、AF_INET(因特网使用的IPv4地址)、AF_INET6(因特网使用的IPv6地址)。这里的AF_表示的含义是AddressFamily,但是很多情况下,我们也会看到以PF_表示的宏,比如PF_INET、PF_INET6等,实际上PF_的意思是ProtocolFamily,也就是协议族的意思。我们用AF_xxx这样的值来初始化socket地址,用PF_xxx这样的值来初始化socket。