跳转至

02 网络编程模型:认识客户端 服务器网络模型的基本概念

你好,我是盛延敏。上一讲我们学习了TCP/IP的创建和历史,以及Linux操作系统的建立和发展,相信你对网络编程这棵大树已经有了一个宏观上的认识,那么今天我们再往前走几步,近距离看看这棵大树的细枝末节到底是怎样的。

从哪里开始呢?从网络编程的基本概念开始说起吧。

客户端-服务器网络编程模型

在谈论网络编程时,我们首先需要建立一个概念,也就是我们今天的主题“客户端-服务器”。

拿我们常用的网络购物来说,我们在手机上的每次操作,都是作为客户端向服务器发送请求,并收到响应的例子。

这个过程具体阐释如下:

  1. 当一个客户端需要服务时,比如网络购物下单,它会向服务器端发送一个请求。注意,这个请求是按照双方约定的格式来发送的,以便保证服务器端是可以理解的;
  2. 服务器端收到这个请求后,会根据双方约定的格式解释它,并且以合适的方式进行操作,比如调用数据库操作来创建一个购物单;
  3. 服务器端完成处理请求之后,会给客户端发送一个响应,比如向客户端发送购物单的实际付款额,然后等待客户端的下一步操作;
  4. 客户端收到响应并进行处理,比如在手机终端上显示该购物单的实际付款额,并且让用户选择付款方式。

在网络编程中,具体到客户端-服务器模型时,我们经常会考虑是使用TCP还是UDP,其实它们二者的区别也很简单:TCP中连接是谁发起的,在UDP中报文是谁发送的。在TCP通信中,建立连接是一个非常重要的环节。区别出客户端和服务器,本质上是因为二者编程模型是不同的。

服务器端需要在一开始就监听在一个众所周知的端口上,等待客户端发送请求,一旦有客户端连接建立,服务器端就会消耗一定的计算机资源为它服务,服务器端是需要同时为成千上万的客户端服务的。如何保证服务器端在数据量巨大的客户端访问时依然能维持效率和稳定,这也是我们讲述高性能网络编程的目的。

客户端相对来说更为简单,它向服务器端的监听端口发起连接请求,连接建立之后,通过连接通路和服务器端进行通信。

还有一点需要强调的是,无论是客户端,还是服务器端,它们运行的单位都是进程(process),而不是机器。一个客户端,比如我们的手机终端,同一个时刻可以建立多个到不同服务器的连接,比如同时打游戏,上知乎,逛天猫;而服务器端更是可能在一台机器上部署运行了多个服务,比如同时开启了SSH服务和HTTP服务。

IP和端口

正如寄信需要一个地址一样,在网络世界里,同样也需要地址的概念。在TCP/IP协议栈中,IP用来表示网络世界的地址。

前面我们提到了,在一台计算机上是可以同时存在多个连接的,那么如何区分出不同的连接呢?

这里就必须提到端口这个概念。我们拿住酒店举例子,酒店的地址是唯一的,每间房间的号码是不同的,类似的,计算机的IP地址是唯一的,每个连接的端口号是不同的。

端口号是一个16位的整数,最多为65536。当一个客户端发起连接请求时,客户端的端口是由操作系统内核临时分配的,称为临时端口;然而,前面也提到过,服务器端的端口通常是一个众所周知的端口。

一个连接可以通过客户端-服务器端的IP和端口唯一确定,这叫做套接字对,按照下面的四元组表示:

(clientaddr:clientport, serveraddr: serverport)

下图表示了一个客户端-服务器之间的连接:

保留网段

一个比较常见的现象是,我们所在的单位或者组织,普遍会使用诸如10.0.x.x或者192.168.x.x这样的IP地址,你可能会纳闷,这样的IP到底代表了什么呢?不同的组织使用同样的IP会不会导致冲突呢?

背后的原因是这样的,国际标准组织在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,我们可以说前⾯3个字节(byte) 是⼦⽹(subnet),最后1个字节是主机,或者换个⽅式,我们能说主机为 8 位,⼦⽹掩码为192.0.2.0/24(255.255.255.0)。

有点晕?别着急,接下来要讲的是一些基本概念。

很久很久以前,有子网(subnet)的分类,在这里,一个IPv4地址的第一个,前两个或前三个 字节是属于网络的一部分。

如果你很幸运地可以拥有IPv4地址的第1个字节⽹络,另外3个字节表示主机地址,那在你的⽹络⾥,你有价值3个字节,也就是24个⽐特的主机地址,这是什么概念呢? 2 的 24 次⽅,⼤约是⼀千六百万个地址左右(图中Number of addresses)。这是⼀个“Class A”(A 类)⽹络。

我们再来重新看⼀下这张表格,表格第⼀⾏就是这样⼀个 A 类⽹络,10对应的是⽹络字节部分,主机部分是3个字节,我们将第⼀个字节的⼦⽹掩码记作 255.0.0.0。

相对的,“Class B”(B 类)的网络,网络有两个字节,而 host 只有两个字节,也就是说拥有的主机个数为65536。“Class C”(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个位的主机,这个网络最多有四台主机。为什么是4台主机呢?因为不变的部分只有最后两位,所有的可能为2的2次方,即4台主机。

注意,子网掩码的格式永远都是二进制格式:前面是一连串的 1,后面跟着一连串的 0。

不过一大串的数字会有点不好用,比如像 255.192.0.0 这样的子网掩码,人们无法直观地知道有多少个1,多少个0,后来人们发明了新的办法,你只需要将一个斜线放在IP地址后面,接着用一个十进制的数字用以表示网络的位数,类似这样:192.0.2.12/30, 这样就很容易知道有30个1, 2个0,所以主机个数为4。

最后,再强调一点,实际可以用的主机数目,一般要减去⼴播地址(全1的地址)和不可用的地址(全0的地址)。也就是说,图中的Number of addresse(即地址数),如果换算为真实的主机数,是需要减去2的。

相信这个时候再去看保留网段,你应该能理解表格里的内容了。这里就不再赘述。

全球域名系统

如果每次要访问一个服务,都要记下这个服务对应的IP地址,无疑是一种枯燥而繁琐的事情,就像你要背下200多个好友的电话号码一般无聊。

此时,你应该知道我将要表达什么。对的,正如电话簿记录了好友和电话的对应关系一样,域名(DNS)也记录了网站和IP的对应关系。

全球域名按照从大到小的结构,形成了一棵树状结构。实际访问一个域名时,是从最底层开始写起,例如 www.google.comwww.tinghua.edu.cn等。

数据报和字节流

尽管名称是TCP/IP协议栈,但是从上一讲关于OSI和TCP/IP协议栈的对比中,我们看到传输层其实是有两种协议的,一种是大家广为熟悉的TCP, 而另一种就是UDP。

TCP,又被叫做字节流套接字(Stream Socket),注意我们这里先引入套接字socket,套接字socket在后面几讲中将被反复提起,因为它实际上是网络编程的核心概念。当然,UDP也有一个类似的叫法, 数据报套接字(Datagram Socket),一般分别以“SOCK_STREAM”与“SOCK_DGRAM”分别来表示TCP和UDP套接字。

Datagram Sockets 有时称为“无连接的sockets”(connectionless sockets)。

Stream sockets 是可靠的、双向连接的通讯串流。比如以“1-2-3”的顺序将字节流输出到套接字上,它们在另一端一定会以“1-2-3”的顺序抵达,而且不会出错。

这种高质量的通信是如何办到的呢?这就是由TCP(Transmission Control Protocol)协议完成的,TCP通过诸如连接管理,拥塞控制,数据流与窗口管理,超时和重传等一系列精巧而详细的设计,提供了高质量的端到端的通信方式。

这部分内容不是我们这里讲解的重点,有感兴趣的同学可以去读《TCP/IP详解卷一:协议》 。

我们平时使用浏览器访问网页,或者在手机端用天猫App购物时,使用的都是字节流套接字。

等等,如果是这样,世界都用TCP好了,哪里有UDP什么事呢?

事实上,UDP在很多场景也得到了极大的应用,比如多人联网游戏、视频会议,甚至聊天室。如果你听说过NTP,你一定很惊讶NTP也是用UDP实现的。

使用UDP的原因,第一是速度,第二还是速度。

想象一下,一个有上万人的联网游戏,如果要给每个玩家同步游戏中其他玩家的位置信息,而且丢失一两个也不会造成多大的问题,那么UDP是一个比较经济合算的选择。

还有一种叫做广播或多播的技术,就是向网络中的多个节点同时发送信息,这个时候,选择UDP更是非常合适的。

UDP也可以做到更高的可靠性,只不过这种可靠性,需要应用程序进行设计处理,比如对报文进行编号,设计Request-Ack机制,再加上重传等,在一定程度上可以达到更为高可靠的UDP程序。当然,这种可靠性和TCP相比还是有一定的距离,不过也可以弥补实战中UDP的一些不足。

在后面的章节中,我们将会分别介绍TCP和UDP的网络编程技术。

总结

这一讲我们主要介绍了客户端-服务器网络编程模型,初步介绍了IP地址、端口、子网掩码和域名等基础概念,以下知识点你需要重点关注一下:

  1. 网络编程需要牢牢建立起“客户端”和“服务器”模型,两者编程的方法和框架是明显不同的。
  2. TCP连接是客户端-服务器的IP和端口四元组唯一确定的,IP是一台机器在网络世界的唯一标识。
  3. 有两种截然不同的传输层协议,面向连接的“数据流”协议TCP,以及无连接的“数据报”协议UDP。

从下一讲开始,我们将开始使用套接字编写我们的第一个客户端-服务器程序。

思考题

最后给你布置几个思考题。

我们看到保留地址中第二行172.16.0.0/12描述为16个连续的B段,第三行192.168.0.0/16描述为256个连续的C段地址,怎么理解这种描述呢?

另外,章节里提到了服务端必须侦听在一个众所周知的端口上,这个端口怎么选择,又是如何让客户端知道的呢?

如果你仔细想过这个问题,欢迎在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,大家一起交流一下。

精选留言(15)
  • Skrpy 👍(10) 💬(3)

    “网络地址位数由子网掩码(Netmask)决定,你可以将 IP 地址与子网掩码进行“位与”操作,就能得到网络的值。” 老师这句话体现了子网掩码有什么用 “128 ~ 191.某.某.某” 属于 B 类网络;“192 ~ 223.某.某.某” 属于 C 类网络。(这里老师可以把 A、B、C 类网的网段区间说明一下) (一)172.16.0.0/12: 172.16.0.0 和 1111 1111.1111 0000.0.0(12个连续的 1 的子网掩码,也称 CIDR 地址掩码) 进行“与”操作后,得到网络地址的值:172.0.0.0;B 类网络中,网络号从左到右占 16 位,即 172.16(172.0000 1111)为网络号,故从 172.0000 1111 ~ 172.1111 1111 共有16个连续的 B 段网。 (二)192.168.0.0/16: 192.168.0.0 和 1111 1111.1111 1111.0.0(16个连续的 1 的子网掩码)进行“与”操作后,得到网络地址的值:192.168.0.0;C 类网络中,网络号从左到右占 24 位,即 192.168.0(192.168.0000 0000)为网络号,故从 192.168.0000 0000 ~ 192.168.1111 1111 共有 256 个连续的 C 段网。

    2019-08-05

  • 莫珣 👍(27) 💬(1)

    172.16.0.0/12,首先172表明这是这个B类网络,12表示子网掩码从左往右有12个1,可以得出网络地址是172.16,但B类网络的网络地址一共有16位,所有还有4位是可以0和1任意组合的,所以总得意思就是说在172.16这个网络地址下面你还可以划分出15个局域网,那么加上172.16自己,刚好就是16个连续的B段网络。192.168.0.0/16的理解过程和172.16.0.0/12是一样的。 端口选择,服务端端口是用一个unsigned short来表示的,理论上服务端选择端口只要在这个短整型的表示范围内即可。知名服务端软件的默认端口本身是一种建议而非规范,但是因为它们太有名了,慢慢的就变成了一种约定俗成的东西。如果你在部署的时候给改变了这个默认端口,那么你就需要告诉客户端你部署时使用的端口是什么。 如果是你自己开发的服务端,那么端口号尽可能的不要与这些知名软件的端口冲突。当然你是不可能记住每一个软件的端口,所以你必须在你的文档中说明你所选择的默认端口是什么。并且你还需要将监听端口设计为可配置的,以便在端口冲突时为你的客户提供一个简单易行的解决方案。

    2019-08-05

  • 剑衣清风 👍(36) 💬(3)

    172.16.0.0/12 中得出信息,172.16.0.0 为 B 类网,12 为网络号,默认 B 类网的网络号是 2*8=16 位,而此处为 12 位,那么便有 2^(16-12) = 16 个连续子网 相应的 192.168.0.0/16 ,192.168.0.0 为 C 类网,16 为网络号,默认 C 类网的网络号是 3*8=24 位,而此处为 16 位,那么便有 2^(24-16) = 256 个连续的子网 大家可以看看 趣谈网络协议,里面有介绍,另外 https://blog.csdn.net/molaifeng/article/details/88109717 为我结合所学及网络参考所成的博文,可以参考下

    2019-08-05

  • a、 👍(156) 💬(4)

    1.172.16.0.0~172.31.255.255,因为b类网络的host只占最后两个字节,172.16~172.31就代表了16个连续的b类网络可用 2.192.168.0.0~192.168.255.255,因为c类网络的host只占最后一个字节,所以从192.168.0到192.168.255,就有256个连续的c类网络可用 3.服务器可以监听的端口有从0到65535,理论上这台服务器的这个端口只要没被占用,你都可以给服务器绑定。 4.如果是一些默认的服务,服务器绑的也是默认的端口,那么客户端是可以知道的。比如:80是给http服务,443是给https服务,21是给ftp服务等。否则的话,就需要服务器开发者告诉客户端应该连接哪个端口

    2019-08-05

  • Eglinux 👍(8) 💬(1)

    ”Stream sockets 是可靠的,双向连接的通讯串流。⽐如以“1-2-3”的顺序将字节流输出到套接字 上,它们在另⼀端⼀定会以“1-2-3”的顺序抵达,⽽且不会出错。“ 这里还是要分层的吧,在应用层可以说是顺序到达的,那是因为传输层干了活,但是传输层收到包就不一定是顺序到达的了。

    2019-08-05

  • 摸鱼哥 👍(7) 💬(2)

    无论是原先子网划分还是 CIDR 表示法都应该有 2 个保留地址,全是 0 的网关地址和全是 1 的广播地址,那个 4 个 host 是不是应该只有 2 个

    2019-08-08

  • 👍(4) 💬(3)

    老师,IP是自包含区域信息的嘛?比如:中国有台电脑,美国有台电脑,他们都接入互联网有自己唯一的IP,当他们需要通信的时候,寻找彼此,是直接就知道寻找的方向了吧!而不是全世界都找一遍,这个是不是就是路由信息?另外,每个路由器的路由信息是都包括世界上所有的嘛?TCP的长链接是怎么维护的,还是刚才的例子中国的一台机器和美国的一台机器,他们中间隔了许多的网络设备和很远的距离,他们第一次通信和第二次通信,走的路很难想象是一样,距离太远变数太大了,不过我也很难想象他们之间的长链接是怎么维护的,好像有了一条专用通信线路,实际不可能,它怎么实现的?

    2019-11-19

  • 👍(4) 💬(3)

    我觉得老师对保留网段和子网掩码那部分讲得太晦涩了。本来看之前我还有点懂,看完之后反而不太懂了。这部分缺乏一个比较通俗的例子来说明,而且只字未提网关。平时配置局域网的时候ip,肯定会遇到网关。我看到评论里也有人说这部分看不明白。

    2019-08-29

  • 啦啦的小猪 👍(4) 💬(1)

    讲的很好啊,期待下来的课程

    2019-08-05

  • 王亮平 👍(3) 💬(4)

    16位整数的ip端口最多为什么不是65535而是65536呢?

    2019-08-08

  • 码农Kevin亮 👍(2) 💬(1)

    请教一下老师,以C类保留网段为例,同一子网下的两主机通信,与不同子网下的两主机通信,它们有何差异之处呢

    2019-09-01

  • 码农Kevin亮 👍(2) 💬(1)

    请教老师,能否补充一下在什么情况下会用到A类与B类的保留网段?因为平常常见的都只有C类保留网段

    2019-09-01

  • null 👍(1) 💬(1)

    原文:其实它们二者的区别也很简单:TCP 中连接是谁发起的,在 UDP 中报文是谁发送的。 问题:没太理解冒号之后的这句话。 -------------------------------- 原文:类似这样:192.0.2.12/30, 这样就很容易知道有 30 个 1, 2 个 0,所以主机个数为 4。 问题:从 192.0.2.0/24,可以反推出这 256 台机器的 IP 地址是:192.0.2.[0~255]。 同理可得,从 192.0.2.12/30 反推出这 4 台机器的 IP 地址是:192.0.2.[12~15]。 可得结论:「IP_ADDR/十进制」这种格式的子网掩码,其首个 IP 地址「必然」是 IP_ADDR,是么? 推断过程: 从 192.0.2.12/30 可得: IP 地址 192.0.2.12 转为二进制:11111111 11111111 11111111 00001100 子网掩码:11111111 11111111 11111111 111111_xx 拿 IP 地址与子网掩网做与运算,子网掩网高 30 位均为 1,为了方便观看,我们只取低 8 位: 00001100 & 111111_00 => 192.0.2.12 00001100 & 111111_01 => 192.0.2.13 00001100 & 111111_10 => 192.0.2.14 00001100 & 111111_11 => 192.0.2.15 请问老师,我的结论和推断过程,是否正确。谢谢老师!!

    2021-03-12

  • 我也曾是少年 👍(1) 💬(2)

    老师,请教个问题,多线程对同一个socket进行并发写,在高并发的情况下会出现什么问题呢?

    2019-10-13

  • Hello_dzm 👍(1) 💬(1)

    当一个客户端发起连接请求时,客户端的端口是由操作系统内核临时分配; 这个操作系统是客户端的吧

    2019-08-14