TCP/IP

349 阅读17分钟

1. 套接字 socket

浏览器、邮件等一般应用程序收发数据时用 TCP; DNS 查询等收发较短的控制数据时用 UDP。

ICMP 用于告知网络包传送过程中产生的错误以及各种控制消息,ARP 用 于根据 IP 地址查询相应的以太网 MAC 地址 。

协议栈是根据套接字中记录的控制信息来工作的,例如通信对象的 IP 地址、端口号、通信操作的 进行状态等。

在 Windows 中可以用 netstat 命令显示套接字内容

创建套接字时,首先分配一个套接字所需的内存空间,然后向其中写入初始状态

接下来,需要将表示这个套接字的描述符告知应用程序。

套接字刚刚创建完成的时候,里面并没有存放任何数据,也不知道通信的对象是谁。需要把服务器的IP 地址和端口号等控制信息告知协议栈,于是,让客户端向服务器告知必要的信息,比如“我想和你开始通信,我的 IP 地址是 xxx.xxx. xxx.xxx,端口号是 yyyy。” 连接实际上是通信双方交换控制信息。 在连接操作的过程中会分配一块用来临时存放要收发的数据的内存空间

通信操作中使用的控制信息分为两类。

(1)头部中记录的信息

(2)套接字(协议栈中的内存空间)中记录的信息

用来控制协议栈操作的信息 。应用程序传递来的信息以及从通信对象接收到的信息,还有收发数据操作的执行状态等信息也会保存在这里,协议栈会根据这些信息来执行每一步的操作。

连接操作的实际过程

connect(< 描述符 >, < 服务器 IP 地址和端口号 >, …)

服务器的 IP 地址和端口号会传递给协议栈中的 TCP 模块。然后,TCP 模块会与该 IP 地址对应服务器的 TCP 模块交换控制信息。

首先,客户端先创建一个包含表示开始数据收发操作的控制信息的头部。头部包含很多字段,重点是发送方和接收方的端口号

当 TCP 头部创建好之后,接下来 TCP 模块会将信息传递给 IP 模块并委托它进行发送 。IP 模块执行网络包发送操作后,网络包就会通过网络到达服务器,然后服务器上的 IP 模块会将接收到的数据传递给 TCP 模块。

服务器的 TCP 模块根据 TCP 头部中的信息找到端口号对应的套接字,从处于等待连接状态的套接字中找到与 TCP 头部中记录的端口号相同的套接字就可以了。当找到对应的套接字之后,套接字中会写入相应的信息,并将状态改为正在连接 。

上述操作完成后,服务器的 TCP 模块会返回响应,这个过程和客户端一样,需要在 TCP 头部中设置发送方和接收方端口号以及 SYN 比特 。此外,在返回响应时还需要将 ACK 控制位设为 1,表示已经接收到相应的网络包。而设置 ACK 比特就是用来进行这一确认的。接下来,服务器 TCP 模块会将 TCP 头部传递给 IP 模块,并委托 IP 模块向客户端返回响应。

网络包就会返回到客户端,通过 IP 模块到达 TCP 模块,并通 过 TCP 头部的信息确认连接服务器的操作是否成功。如果 SYN 为 1 则表示连接成功,这时会向套接字中写入服务器的 IP 地址、端口号等信息,同时还会将状态改为连接完毕。

到这里,客户端的操作就已经完成。刚才服务 器返回响应时将 ACK 比特设置为 1,相应地, 客户端也需要将 ACK 比特设置为 1 并发回服务器,告诉服务器刚才的响应包已经收到。当这个服务器收到这个返回包之后,连接操作才算全部完成。

收发数据

数据收发操作是从应用程序调用 write 将要发送的数据交给协议栈开始的

首先,应用程序在调用 write 时会指定发送数据的长度,协议栈并不是一收到数据就马上发送出去,而是会将数据存放 在内部的发送缓冲区中,并等待应用程序的下一段数据。

应用程序在发送数据时可以指定一些选项,比如如果指定“不等待填满缓冲区直接发 送”,则协议栈就会按照要求直接发送数据。

MTU:一个网络包的最大长度,以太网中一般为 1500 字节。

MSS:除去头部之后,一个网络包所能容纳的 TCP 数据的最大长度 ,TCP 和 IP 的头部加起来一般是 40 字节

对较大的数据进行拆分

当发送缓冲区中的数据超过 MSS 的长度时,发送缓冲区中的数据会被以 MSS 长度为单位进行拆分,拆分出来的每块数据会被放进单独的网络包中。

使用 ACK 号确认网络包已收到

TCP 模块在拆分数据时, 会先算好每一块数据相当于从头开始的第几个字节,接下来在发送这一块 数据时,将算好的字节数写在 TCP 头部中,即“序号”字段。

在实际的通信中,为了安全,序号并不是从 1 开始的,而是需要用随机数计算出一个初始值,在连接过程中,有一个将 SYN 控制位设为 1并发送给服务器的操作,在这一步将序号的初始值告知对方的。在将 SYN 设为 1 的同时,设置序号字段的值,而这里的值就代表序号的初始值 。

双向传输时, 首先客户端先计算出一个序号,然后将序号和数据一起发送给服务器,服务器收到之后会计算 ACK 号并返回给客户端;相反地,服务器也需要先计算出另一个序号,然后将序号和数据一起发送给客户端,客户端收到之后计算 ACK 号并返回给服务器。

TCP 采用这样的方式确认对方是否收到了数据,在得到对方确认前,发送过的包都会保存在发送缓冲区中。如果对方没有返回某些包对应 的 ACK 号,那么就重新发送这些包。 TCP 会在尝试几次重传无效之后强制结束通信,并向应用程序报错。

TCP 会在发送数据的过程中持续测量 ACK 号的返回时间,如果 ACK 号返回变慢,则相应延长等待时间;相对地,如果 ACK 号马上就能返回,则相应缩短等待时间

使用窗口有效管理 ACK 号

滑动窗口,就是在发送一 个包之后,不等待 ACK 号返回,而是直接发送后续的一系列包。

滑动窗口:接收方告诉发送方最多能接收多少数据,然后发送方根据这个值对数据发送操作进行控制

接收方将数据暂存到接收缓冲区中并执行接收操作。当接收操作完成后,接收缓冲区中的空间会被释放出来,接收方会通过 TCP 头部中的窗口字段将自己能接收的数据量告知发送方。

ACK 与窗口的合并

接收方在发送 ACK 号和窗口更新时,并不会马上把包发送出去,而是会等待一段时间,在这个过程中很有可能会出现其他的通知操作, 这样就可以把两种通知合并在一个包里面发送了。

在等待发送 ACK 号的时候正好需要更新窗口,这时就可以把 ACK 号和窗口更新放在一个包里发送 , 要连续发送 ACK 号或者多个窗口更新时,发送最后一个即可。

接收 HTTP 响应消息

和发送数据一样,接收数据将数据暂存到接收缓冲区中 ,首先,协议栈尝试从接收缓冲区中取出数据并传递给应用程序,但响应消息可能还没返回,这时,协议栈会将应用程序的委托暂时挂起,等服务器返回的响应消息到达之后再继续执行接收操作。

协议栈会检查收到的数据块和 TCP 头 部的内容,判断是否有数据丢失,如果没有问题则返回 ACK 号。然后,协议栈将数据块暂存到接收缓冲区中,并将数据块按顺序连接起来还原出原始的数据,最后将数据交给应用程序,最后找合适的时机向发送方发送窗口更新。

从服务器断开并删除套接字

首先,服务器(也可能客户端)的应用程序会调用 Socket 库的 close 程序。然后,服务器的协议栈会生成包含断开信息的 TCP 头部,即将控制位中的 FIN 比特设为 1。接下来,协议栈会委托 IP 模块向客户端发送数据。同时,服务器的套接字中也会记录下断开操作的相关信息。

当收到服务器发来的 FIN 为 1 的 TCP 头部时,客户端的协议栈会将自己的套接字标记为进入断开操作状态。然后客户端会向服务器返回一个 ACK 号

过了一会儿,应用程序就会调用 read 来读取数据 。这时,协议栈不会向应用程序传递数据 ,而是会告知应用程序(浏览器)来自服务器的数据已经全部收到了。

客户端应用程序调用 close 来结束数据收发操作,生成一个 FIN 比特为 1 的 TCP 包,然后 委托 IP 模块发送给服务器。一段时间之后,服务器就会返回 ACK 号。到这里通信就全部结束了。

应用程序有可能在收到 FIN 为 1 的包之前就来读取数据,这时读取数据的操作会被挂起,等到 FIN 包到达再继续执行。

删除套接字

和服务器的通信结束之后,用来通信的套接字就可以删除了。不过,套接字并不会立即被删除,而是会等待一段时间之后再被删除。

举一个例子

(1)客户端发送 FIN

(2)服务器返回 ACK 号

(3)服务器发送 FIN

(4)客户端返回 ACK 号

如果最后客户端返回的 ACK 号丢失了,这时,服务器没有接收到 ACK 号,可能会重发一次 FIN。如果这时客户端的套接字已经删除了,套接字被删除,那么套接字中保存的控制信息也就跟着消失了,套接字对应的端口号就会被释放出来。这时,如果别的应用程序要创建套接字,新套接字碰巧又被分配了同一个端口号 ,而服 务器重发的 FIN 正好到达,本来这个 FIN 是要发给刚刚删除的那个套接字的,但新套接字具有相同的端口号,于是这个 FIN 就会错误地跑到新套接字里面,新套接字就开始执行断开操作了。

2. IP 与以太网的包

首先,发送方的网络设备会负责创建包,是生成含有正确控制信息的头部,然后再附加上要发送的数据。接下来,包会被发往最近的网络转发设备。当到达最近的转发设备之后,转发设备会根据头部中的信息判断接下来应该发往哪里。

各种地址的由来mp.weixin.qq.com/s/jiPMUk6zU…

包收发操作

包收发操作的起点是 TCP 模块在数据块的前面加上 TCP 头部,然后整个传递给 IP 模块, (“①发送”)TCP 模块指定通信对象的 IP 地址 。

收到委托后,IP 模块会将包的内容当作一整块数据

IP 模块负责添加如下两个头部。

(1) MAC 头部:以太网用的头部,包含 MAC 地址

(2) IP 头部:IP 用的头部,包含 IP 地址

接下来,封装好的包会被交给网络硬件(“②发送”)

无论要收发的包是控制包还是数据包,IP 对各种类型的包的收发操作都是相同的。

生成包含接收方 IP 地址的 IP 头部

IP地址是由 TCP 模块告知的

IP头部的“接收方 IP 地址”填写通信对象的 IP 地址。 需要判断发送所使用的网卡,并填写该网卡的 IP 地址。

Interface 列表示网卡等网络接口,这些网络接口可以将包发送给通信对象。

Gateway 列表示下一个路由器的 IP 地址,将包发给这个 IP 地址,该地址对应的路由器 就会将包转发到目标地址。

第 1 行中,目标地址和子网掩码 A 都是 0.0.0.0,表示默认网关,如果其他所有条目都无法匹配,就会自动匹配这一行。

接下来填写协议号,表示包的内容来自哪个模块的。如果是 TCP 模块委托的内容,则设置为 06(十六进制),如果是 UDP 模块委托的内容,则设置为 17(十六进制)

生成以太网用的 MAC 头部

IP 头部中的接收方 IP 地址表示网络包的目的地,通过这个地址我们就可以判断要将包发到哪里,但在以太网(局域网)的世界中,TCP/ IP 的这个思路是行不通的。

MAC 地址是在网卡生产时写入 ROM 里的,只要将这个值读取出来写入 MAC 头部就可以。

通过 ARP 查询目标路由器的 MAC 地址

如果对方和自己处于同一个子网中,那么通过上面的操作就可以得到对方的 MAC 地址

在发送包时,先查询一下 ARP 缓存,如果其中已经保存 了对方的 MAC 地址,就不需要发送 ARP 查询,值在经过一段时间后会被删除,一般这个时间在几分钟左右。

以太网

将 IP 包转换成电或光信号发送出去

IP 生成的网络包只是存放在内存中的一串数字信息,没有办法直接发送给对方。需要将数字信息 转换为电或光信号,才能在网线上传输 ,负责执行这一操作的是网卡, 还需要网卡驱动程序。

网卡的 ROM 中保存着全世界唯一的 MAC 地址,这是在生产网卡时写入的。

网卡中保存的 MAC 地址会由网卡驱动程序读取并分配给 MAC 模块。

网卡驱动从 IP 模块获取包之后,会将其复制到网卡内的缓冲区中,然后向 MAC 模块发送发送包的命令。

首先,MAC 模块会将包从缓冲区中取出,并在开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列

报头用于测量时钟信号

起始帧分界符末尾比特排列有少许变化。接收方以这一变化作为标记,从这里开始提取网络包数 据。 末尾的 FCS(帧校验序列)用来检查包传输过程中因噪声导致的波形紊 乱、数据错误,32 比特的序列,通过一个公式对包中从头到尾的所有内容进行计算而得出来的。

网卡的 MAC 模块生成通用信号,然后由 PHY(MAU)模块转换成可在网线中传输的格式,并通过网线发送出去。

接收返回包

首先,PHY (MAU)模块会将信号转换成通用格式并发送给 MAC 模块,MAC 模块再 从头开始将信号转换为数字信息,并存放到缓冲区中。当到达信号的末尾时,检查 FCS。

接下来就要看一下 MAC 头部中接收方 MAC 地址与网卡在初始化时分配给自己的 MAC 地址是否一致,以判断这个包是不是发给自己的。

接下来网卡会通过中断通知计算机收到了一个包。从网卡的缓冲区中取出收到的包, 并通过 MAC 头部中的以太类型字段判断协议的类型。 如 0080(十六进制)代表 IP 协议,网卡驱动就会把这样的包交给 TCP/IP 协议栈;

IP 模块第一步是检查 IP 头部,确认格式是否正确。如果格式没有问题,下一步就是查看接收方 IP 地址,如果接收方 IP 地址不是自己的地址,IP 模块会通过 ICMP 消息将错误告知发送方。

接下来包会被交给 TCP 模块。TCP 模块会根据 IP 头部中的接收方和发送方 IP 地址,以及 TCP 头部中的接收方和发送方端口号来查找对应的套接字 。找到对应的套接字之后,就可以根据套接字中记录的通信状态,执行相应的操作了。例如,如果包的内容是应用程序数据,则返回确认接收的包,并将数据放入缓冲区,等待应用程序来读取;如果是建立或断开连接的控制包,则返回相应的响应控制包, 并告知应用程序建立和断开连接的操作状态。

接收方和发送方的 IP 地址,由 IP 模块 以附加参数的形式告知 TCP 模块

3. UDP 协议

UDP 可发送的数据最大长度为 IP 包的最大长度减去 IP 头部和 UDP 头部 的长度。

IP 包的最大长度是由 IP 头部中的“全长”字段决定的。“全长”字段的长度为 16 比特,IP 包的最大长度为 65535 字节,再减去 IP 头部和 UDP 头部的长度,就是 UDP 协议所能发送的数据最大长度。如果不考虑可选字段的话,一般来说 IP 头部为 20 字节,UDP 头部为 8 字 节,因此 UDP 的最大数据长度为 65 507 字节。这么长的数据已经超过了以太网和通信线路的最大传输长度,因此需要让 IP 模块使用分片功能拆分之后再传输。

UDP 没有 TCP 的接收确认、窗口等机制,因此不需要交换控制信息,不需要建立和断开连接的步骤,只要在从应用程序获取的数据前面加上 UDP 头部,然后交给 IP 进行发送就可以了

接收只要根据 IP 头部中的接收方和发送方 IP 地址, 以及 UDP 头部中的接收方和发送方端口号,找到相应的套接字并将数据交给相应的应用程序就可以了。 遇到错误或者丢包也一概不管。

音频和视频数据

音频和视频数据必须在规定的时间内送达,一旦送达晚了,就会错过播放时机, 导致声音和图像卡顿。重发也没有意义,因此适合UDP