|
楼主 |
发表于 2023-3-15 11:07:38
|
显示全部楼层
[6] 传输层
网络层从底层上实现了不同主机之间的网络通信,但是在应用层面,假设我的电脑收到了一个 IP 数据包,那我怎么知道这个数据包是要发给我电脑上哪个软件来处理的。因此,我们需要传输层。
1. 端口
传输层实现的关键在于给主机的每一个进程绑定了一个特定的端口(port),这是一个 16bit 的数字。这个数字并不大,如何保证不同的进程使用的端口不发生冲突?这就需要额外标识了,而为了规范端口的使用和标识,协议和应用的开发者需要向 ICANN/IANA 机构注册所需要的端口,每一个传输层协议只能注册一个端口。但是需要指出,并不是所有端口都是可以注册的,其中用户端口(也称注册端口)的范围是 1024-49151,一般都是注册这些端口。而 1-1023 称为系统端口(也称预留端口),这些端口当然也可以注册(往往用于操作系统级别,安全要求较高),但我们肯定用不到。最后,49152-65536 称为动态端口,这部分端口不需要注册,想用就用,如果你发现某个动态端口被占用了,就尝试换一个。
总之,做网络游戏一般用动态端口就行了,顶多可能还需要去注册一个用户端口,但一般来说真的没啥必要。
注:IP 地址和端口经常由冒号连接在一起,从而表示一个完整的源地址或目标地址,举例:18.19.20.21:80。
有了端口之后,我们还需要传输层协议才能发送数据,接下来介绍几个。
2. UDP
UDP(user datagram protocol,用户数据报协议)是一个很轻量的协议,其数据报包含了一个 8 字节的报头,后面跟着数据,其中报头写明了源端口和目标端口,可以说是非常廉价的一类协议。
廉价不一定是好事,UDP 并不保证数据的顺序传输和准确到达,换言之这还是不可靠的。
3. TCP
TCP(transmission control protocol,传输控制协议)是在两台主机之间创建的持久性的连接协议,提供了可靠的数据流传输(终于可靠了,泪目)。这里的可靠指的是保证所有的数据都按序抵达接收方,其中实现按序的关键在于 TCP 报文引入了序列号和确认号(ACK,用于确认响应,在初始化的时候设置)用于保证顺序;实现准确到达的关键是源主机会等待来自目的主机的确认响应数据包,如果在一定时间内没有收到期望的确认,就重新发送。
当然,具体的实现流程远没有说的这么简单,由于 TCP 策略涉及到了重新发送数据以及跟踪期望的序列号,所以每一台主机都必须维护所有活跃 TCP 连接的状态。此外,TCP 的初始化也比较麻烦,这得从“三次握手”说起。
(1). 三次握手
假设主机 A 需要与主机 B 建立 TCP 连接,初始化的过程需要两步:
* 主机 A 随机选一个初始序列号(举例 1000),向主机 B 发送第一个报文:seq #: 1000, SYN(这是 sync 的意思,请求连接的时候用的)
人话:“主机 B,我想给你发一段以我这边是 1000+1 序列号为起始位置的数据流,收到请回复。”
* 主机 B 收到该报文,如果主机 B 愿意开放该连接,就会响应一个由主机 B 随机选择的初始序列号(举例 3000),由主机 A 给定的初始序列号+1 得到的 ACK 组成的报文:seq #: 3000, ack #: 1001, SYN ACK
人话:“主机 A,我同意了,我这里的响应报文的起始序列号是 3000+1,你接下来给我发 1001 吧。”
接下来,如果主机 A 成功得到响应,两个主机就正式建立 TCP 连接,其流程大概是:
* 主机 A 发送数据:seq # :1001, ack #: 3001, ACK
人话:“我给你发来 1001 了,收到请给我发 3001 回复。”
* 主机 B 发送响应:seq #: 3001, ack #: 1002, ACK
人话:“OK 我收到了,你继续发 1002。”
(2). 意外情况
刚才提到了,如果没有收到响应就会重复发送数据,但实际情况非常复杂,TCP 也针对性地作了各种优化,包括但不限于流量控制,拥塞控制等。这些我们不必了解,但必须指出,拥塞控制中的一环——Nagle 算法,是网络游戏玩家的克星,尽管它有效减少了带宽的使用,但会带来巨大的延时。万幸的是,TCP 实现一般允许我们关掉这个 Nagle 算法,嗯,所以有需要的话别忘了。
(3). 断开连接
正常情况下,发送方发送 FIN 标识,接收方也以 FIN 响应后,就进入了准备断开连接的状态,因为如果之前有还没发过去正在尝试重发的,那还是会继续尝试重发。如果全部发完了,发送方就会再发送一个包含 FIN 标识的报文响应刚才接收方 FIN 报文的 ACK,表示可以正式断开连接了。当然,超时的意外情况也会导致断开连接。 |
|