极客时间已完结课程限时免费阅读

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

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

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

讲述:冯永吉

时长11:43大小10.74M

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

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

在谈论网络编程时,我们首先需要建立一个概念,也就是我们今天的主题“客户端 - 服务器”。
拿我们常用的网络购物来说,我们在手机上的每次操作,都是作为客户端向服务器发送请求,并收到响应的例子。
这个过程具体阐释如下:
当一个客户端需要服务时,比如网络购物下单,它会向服务器端发送一个请求。注意,这个请求是按照双方约定的格式来发送的,以便保证服务器端是可以理解的;
服务器端收到这个请求后,会根据双方约定的格式解释它,并且以合适的方式进行操作,比如调用数据库操作来创建一个购物单;
服务器端完成处理请求之后,会给客户端发送一个响应,比如向客户端发送购物单的实际付款额,然后等待客户端的下一步操作;
客户端收到响应并进行处理,比如在手机终端上显示该购物单的实际付款额,并且让用户选择付款方式。
在网络编程中,具体到客户端 - 服务器模型时,我们经常会考虑是使用 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 地址、端口、子网掩码和域名等基础概念,以下知识点你需要重点关注一下:
网络编程需要牢牢建立起“客户端”和“服务器”模型,两者编程的方法和框架是明显不同的。
TCP 连接是客户端 - 服务器的 IP 和端口四元组唯一确定的,IP 是一台机器在网络世界的唯一标识。
有两种截然不同的传输层协议,面向连接的“数据流”协议 TCP,以及无连接的“数据报”协议 UDP。
从下一讲开始,我们将开始使用套接字编写我们的第一个客户端 - 服务器程序。

思考题

最后给你布置几个思考题。
我们看到保留地址中第二行 172.16.0.0/12 描述为 16 个连续的 B 段,第三行 192.168.0.0/16 描述为 256 个连续的 C 段地址,怎么理解这种描述呢?
另外,章节里提到了服务端必须侦听在一个众所周知的端口上,这个端口怎么选择,又是如何让客户端知道的呢?
如果你仔细想过这个问题,欢迎在评论区写下你的思考,也欢迎把这篇文章分享给你的朋友或者同事,大家一起交流一下。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 33

提建议

上一篇
01 | 追古溯源:TCP/IP和Linux是如何改变世界的?
下一篇
03丨套接字和地址:像电话和电话号码一样理解它们
unpreview
 写留言

精选留言(68)

  • a、
    置顶
    2019-08-05
    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服务等。否则的话,就需要服务器开发者告诉客户端应该连接哪个端口
    展开

    作者回复: 给你点赞

    共 4 条评论
    153
  • 剑衣清风
    置顶
    2019-08-05
    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 为我结合所学及网络参考所成的博文,可以参考下
    展开

    作者回复: 总结的不错,这个部分还是蛮重要的

    共 3 条评论
    36
  • 莫珣
    置顶
    2019-08-05
    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来表示的,理论上服务端选择端口只要在这个短整型的表示范围内即可。知名服务端软件的默认端口本身是一种建议而非规范,但是因为它们太有名了,慢慢的就变成了一种约定俗成的东西。如果你在部署的时候给改变了这个默认端口,那么你就需要告诉客户端你部署时使用的端口是什么。 如果是你自己开发的服务端,那么端口号尽可能的不要与这些知名软件的端口冲突。当然你是不可能记住每一个软件的端口,所以你必须在你的文档中说明你所选择的默认端口是什么。并且你还需要将监听端口设计为可配置的,以便在端口冲突时为你的客户提供一个简单易行的解决方案。
    展开

    作者回复: 总结的很棒

    23
  • Skrpy
    置顶
    2019-08-05
    “网络地址位数由子网掩码(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 段网。
    展开

    作者回复: 非常棒的建议和总结。子网掩码定义了一个网络段共同的部分,这个是我的理解

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

    作者回复: 是这样的,因为TCP就是传输层协议,就是它帮我们做到顺序的

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

    作者回复: 按照道理说,是这样的

    共 2 条评论
    5
  • 啦啦的小猪
    2019-08-05
    讲的很好啊,期待下来的课程

    作者回复: 谢谢,会慢慢进入高潮部分的

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

    作者回复: 这些是计算机网络的基础知识,简单的说,这个是通过路由器、交换机,以及IP地址和DNS等一系列网络配套设备和协议一起完成的。建议你找一本计算机网络的书看一下。所谓长链接,并不是一条真正的物理链接,只是从软件实现角度来说,维护了这条通信的一条"逻辑"链路。

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

    作者回复: 网关是一个转发设备,跟我这里的网段和子网掩码其实关系不大,不过实际是把类似192.168.1.1和10.10.101.1这样的ip当做网关使用的。 也许我应该把网关内容加进去的,嗯。

    共 3 条评论
    4
  • 小伟
    2019-08-14
    172.16.0.0/12 B类 因为是B类,所以默认前16位是网络号,但这里/12,所以实际上前12位是网络号,后面的都是主机号,16比12多4位,4位换算成10进制最大是16,故该地址表示了16个B段。 192.168.0.0/16 同理,C类地址默认前24位都是主机号,/16表示前16位是网络号,24-16=8,故有8位本应表示网络号的,现在用来表示主机号,故有256个C段。 客户端和服务端的端口先靠约定,如1521就是Oracle的服务端默认监听端口,也是Oracle客户端默认请求端口,当然也可以改成1522,那需要两端一起改,不然不工作。 没有约定的端口需要显示指定,可以通过配置文件等来实现。
    展开
    3
  • 王亮平
    2019-08-08
    16位整数的ip端口最多为什么不是65535而是65536呢?

    作者回复: 整数的编码,0x0001到0xffff,因为端口0是不能用的

    共 4 条评论
    3
  • 郑祖煌
    2020-06-12
    172.16.0.0/12 描述为 16 个连续的 B 段说明 前12位是固定网段,如果是B段的话,则B的host有两个字节,则说明16+12=28 公共网络端+Host号已经占用了28位,然后32-28=4,2的4次方就代表有16个连续的B网络可以用了 172.16.0.0~172.31.255.255 。 192.168.0.0/16 描述为 256 个连续的 C 段地址, C段地址 是有1个字节的Host号 16+8=24 则还剩下2的8次方=256.
    共 1 条评论
    2
  • Eleven
    2019-11-13
    根据开发经验来看,如果是外部访问端口,一般都是固定的,例如https为443,http为80,如果内部服务间的通讯端口,就是大家事先约定好的,是多少无所谓了,一般是10000以上,防止占用常见端口。
    2
  • 码农Kevin亮
    2019-09-01
    请教一下老师,以C类保留网段为例,同一子网下的两主机通信,与不同子网下的两主机通信,它们有何差异之处呢

    作者回复: 一个需要通过核心路由,一个不需要通过,同一个子网下的通信效率要高很多

    2
  • 码农Kevin亮
    2019-09-01
    请教老师,能否补充一下在什么情况下会用到A类与B类的保留网段?因为平常常见的都只有C类保留网段

    作者回复: 大型组织内部进行网络划分的时候,很有可能是通过A类或者B类网段来进行规划的。像现在在云上进行VPC划分时,也都会使用A或B类网段来进行内部VPC网段的规划。

    2
  • null
    2021-03-12
    原文:其实它们二者的区别也很简单: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 请问老师,我的结论和推断过程,是否正确。谢谢老师!!
    展开

    作者回复: 第一个问题是说怎么区分客户端,TCP中连接发起方就是客户端;UDP中发送报文的一端就是客户端。 第二个推断我认为是正确的。

    1
  • 我也曾是少年
    2019-10-13
    老师,请教个问题,多线程对同一个socket进行并发写,在高并发的情况下会出现什么问题呢?

    作者回复: 这个问题的前提是你怎么知道现在这个socket可以被写入,这就需要通过事件分发知道socket的事件,如果可写了,我就往这个socket里写。如果感知socket这个事情在多个线程来做,就会引起事件的"脑裂",因为你不知道什么时候这个事件是真正有效的,有可能一个线程感知时,另外一个线程已经把这个事件给处理掉了。 所以,单独说多个线程往一个socket里并发写,并不是推荐的方式。如果把一个大文件分成很多小文件,通过多个连接(套接字)来做,这是另外一个话题了。

    共 2 条评论
    1
  • Suiyek
    2019-08-16
    准确来讲 端口号是一个 16 位的无符号整数,最多为 65535
    1
  • Hello_dzm
    2019-08-14
    当一个客户端发起连接请求时,客户端的端口是由操作系统内核临时分配; 这个操作系统是客户端的吧

    作者回复: 是的

    1
  • 影子
    2019-08-08
    我想问下老师,你写完后有人来校对么?还是请老师仔细检查一下呗

    作者回复: 欢迎指正

    1