1. C/S模型的基本概念

285 阅读7分钟

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

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

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

image.png

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

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

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

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

IP 和端口

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

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

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

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

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

(clientaddr:clientport, serveraddr: serverport)

image.png

保留网段

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

国际标准组织在 IPv4 地址空间里面,专门划出了一些网段,这些网段不会用做公网上的 IP,而是仅仅保留做内部使用,我们把这些地址称作保留网段。

下表是三个保留网段,其可以容纳的计算机主机个数分别是 16777216 个、1048576 个和 65536 个。

image.png 在详细讲述这个表格之前,我们需要首先了解一下子网掩码的概念。

子网掩码

在网络 IP 划分的时候,我们需要区分两个概念。

  1. 网络:直观点说,它表示的是这组 IP 共同的部分,比如在 192.168.1.1~192.168.1.255 这个区间里,它们共同的部分是 192.168.1.0。
  2. 主机:它表示的是这组 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 个bit的主机地址,这是什么概念呢? 2 的 24 次方,大约是一千六百万个地址左右。这是一个“Class A”(A 类)网络。

image.png 我们再来重新看一下这张表格,表格第一行就是这样的一个 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。

子网掩码能接受任意个位,而不单纯是上面讨论的 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。

数据报和字节流

TCP,又被叫做字节流套接字(Stream Socket)。UDP 也有一个类似的叫法, 数据报套接字(Datagram Socket),一般分别以“SOCK_STREAM”与“SOCK_DGRAM”表示。

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

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

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

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

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

事实上,UDP 在很多场景也得到了极大的应用,比如多人联网游戏、视频会议,甚至聊天室。

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

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

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

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