-
像浏览器、 邮件等一般的应用程序都是使用 TCP 收发数据的, 而像 DNS 查询等收发较短的控制数据的时候则使用 UDP。
-
-
IP 协议控制网络包收发操作的部分
-
ICMP 用于告知网络包传送过程中产生的错误以及各种控制消息
-
ARP 用于根据 IP 地址查询相应的以太网 MAC 地址
-
IP 下面的网卡驱动程序负责控制网卡硬件, 而最下面的网卡则负责完成实际的收发操作, 也就是对网线中的信号执行发送和接收的操作
-
-
在协议栈内部有一块用于存放控制信息的内存空间, 这里记录了用于控制通信操作的控制信息, 例如通信对象的 IP 地址、 端口号、 通信操作的进行状态等
-
协议栈是根据套接字中记录的控制信息来工作的
-
调用socket()时的操作
-
创建套接字时,首先分配一个套接字所需的内存空间,然后向其中写入初始状态。
-
然后将这个套接字的描述符告知应用程序 ,描述符用来区分协议栈中的多个套接字的号码牌
-
2.2 连接服务器
-
连接是什么意思?
-
当套接字创建完成时,服务器上的协议栈和客户端一样, 只创建套接字是不知道应该和谁进行通信。
-
客户端需要把想要连接的服务器的 IP 地址和端口号等信息告知协议栈 ,这是连接的目的之一
-
客户端向服务器传达开始通信的请求,也是连接操作的目的之一
-
连接实际上是通信双方交换控制信息, 在套接字中记录这些必要信息并准备数据收发的一连串操作
-
当执行数据收发操作时, 我们还需要一块用来临时存放要收发的数据的内存空间, 这块内存空间称为缓冲区, 它也是在连接操作的过程中分配的 。分配缓冲区
-
-
通信操作中使用的控制信息分为两类
-
头部中记录的信息:记录和交换控制信息的
-
套接字(协议栈中的内存空间)中记录的信息:控制协议栈操作的。包括:应用程序传递来的信息、从通信对象接收到的信息、收发数据操作的执行状态等信息
-
-
连接操作的实际过程
-
connect( < 描述符 >, < 服务器 IP 地址和端口号 >, …)
-
会把服务器的 IP 地址和端口号传递给协议栈中的 TCP 模块 ,TCP 模块会 和服务器的TCP模块交换控制信息,交换包括以下几个步骤
-
客户端先创建一个包含表示开始数据收发操作的控制信息的头部 ,然后将头部中的控制位的 SYN 比特设置为 1, 大家可以认为它表示连接 ,还需要设置适当的序号和窗口大小
-
当 TCP 头部创建好之后, 接下来 TCP 模块会将信息传递给 IP 模块并委托它进行发送 到服务器
-
服务器上的 IP 模块会将接收到的数据传递给 TCP 模块
-
服务器的 TCP 模块根据 TCP 头部中的信息找到端口号对应的套接字
-
当找到对应的套接字之后, 套接字中会写入相应的信息, 并将状态改为正在连接
-
上述操作完成后, 服务器的 TCP 模块会返回响应, 这个过程和客户端一样 。在返回响应时还需要将 ACK 控制位设为1,双方在通信时必须相互确认网络包是否已经送达
-
客户端会检查TCP 头部的信息确认连接服务器的操作是否成功 ,如果 SYN 为 1 则表 示连接成功, 这时会向套接字中写入服务器的 IP 地址、 端口号等信息, 同时还会将状态改为连接完毕
-
最后客户端也需要将 ACK 比特设置为 1 并发回服务器, 告诉服务器刚才的响应包已经收到
-
经过以上步骤,套接字就已经进入随时可以收发数据的状态了
-
-
2.3 收发数据
-
数据收发操作是从应用程序调用 write 将要发送的数据交给协议栈 ,协议栈并不是一收到数据就马上发送出去, 而是会将数据存放在内部的发送缓冲区中 ,为什么放到缓冲区中到一定长度再发送?
-
应用程序发送给协议栈的数据长度是由应用程序决定的,也可能几字节或者一行数据,如果一收到数据就马上发送出去, 就可能会发送大量的小包, 导致网络效率下降
-
-
积累到多少字节发送?
-
MTU 表示一个网络包的最大长度 ,以太网中是1500字节,去除头部后是MSS(一个网络包所能容纳的 TCP 数据的最大长度 )
-
第二个要素是时间:当应用程序发送数据的频率不高的时候, 如果每次都等到长度接近 MSS 时再发送, 可能会因为等待时间太长而造成发 送延迟 ,为此, 协议栈的内部有一个计时器, 当经过一定时间之后,就会把网络包发送出去
-
这两个要素之间相互矛盾,应用程序在发送数据时可以指定一些选项
-
-
发送缓冲区中的数据超过 MSS 的长度 就要以MSS为单位进行拆分,分成不同的网络包发送出去
-
实际发送过程中,序号并不是从 1 开始的, 而是需要用随机数计算出一个初始值 ,为了防攻击
-
发送的实际过程
-
客户端计算出序号初始值,并发送给服务器
-
服务器会通过这个初始值计算出 ACK 号并返回给客户端 ,同时服务器也要计算出往客户端通信这一侧的序号初始值,并返回给客户端
-
客户端需要根据服务器发来的序列号计算出ACK返给服务器
-
-
通过“序号”和“ACK 号”可以确认接收方是否收到了网络包。
-
根据网络包平均往返时间调整 ACK 号等待时间
-
TCP 采用了动态调整等待时间的方法, 这个等待时间是根据 ACK 号返回所需的时间来判断的。 具体来说, TCP 会在发送数据的过程中持续测量 ACK 号的返回时间, 如果 ACK 号返回变慢, 则相应延长等待时间; 相对地, 如果 ACK 号马上就能返回, 则相应缩短等待时间 。
-
-
每发送一个包就等待一个 ACK 号 效率是很低的。为此采用滑动窗口方式来管理数据发送和 ACK 号的操作:就是在发送一个包之后, 不等待 ACK 号返回, 而是直接发送后续的一系列包
-
采用滑动窗口这种方式,可能会出现发送包的频率超过接收方处理能力的情况
-
如果数据到达的速率比处理这些数据并传递给应用程序的速率还要快, 那么接收缓冲区中的数据就会越堆越多, 最后就会溢出,缓冲区溢出之后, 后面的数据就进不来了, 因此接收方就收不到后面的包了,接收方需要告诉发送方自己最多能接收多少数据,然后发送方根据这个值对数据发送操作进行控制, 这就是滑动窗口方式的基本思路
-
能够接收的最大数据量称为窗口大小 ,一般和接收方的缓冲区大小一样
-
-
什么时候需要更新窗口大小呢?
-
是接收方从缓冲区中取出数据传递给应用程序的时候。 这个操作是接收方应用程序发出请求时才会进行的, 而发送方不知道什么时候会进行这样的操作, 因此当接收方将数据传递给应用程序, 导致接收缓冲区剩余容量增加时, 就需要告知发送方, 这就是更新窗口大小的时机
-
-
什么时候发送ACK号?
-
当接收方收到数据之后就发送ACK号
-
-
接收方在发送 ACK 号和窗口更新时, 并不会马上把包发送出去, 而是会等待一段时间, 在这个过程中很有可能会出现其他的通知操作,这样就可以把两种通知合并在一个包里面发送了
-
接收HTTP响应消息
-
会调用read函数进行接收,协议栈尝试从接收缓冲区中取出数据并传递给应用程序, 但这个时候请求消息刚刚发送出去, 响应消息可能还没返回。 响应消息的返回还需要等待一段时间, 因此这时接收缓冲区中并没有数据 ,这时协议栈会从接收缓冲区中取出数据并传递给应用程序的工作暂时挂起 ,等服务器返回的响应消息到达之后再继续执行接收操作
-
-
接收数据的过程总结
-
首先, 协议栈会检查收到的数据块和 TCP 头部的内容, 判断是否有数据丢失, 如果没有问题则返回 ACK 号。 然后,协议栈将数据块暂存到接收缓冲区中, 并将数据块按顺序连接起来还原出原始的数据, 最后将数据交给应用程序。 具体来说, 协议栈会将接收到的数据复制到应用程序指定的内存地址中, 然后将控制流程交回应用程序。将数据交给应用程序之后, 协议栈还需要找到合适的时机向发送方发送窗口更新
-
2.4 从服务器断开并删除套接字
-
收发数据结束的时间点应该是应用程序判断所有数据都已经发送完毕的时候,协议栈在设计上允许任何一方先发起断开过程,以服务器断开连接过程为例:
-
服务器一方的应用程序会调用 Socket 库的 close 程序
-
然后, 服务器的协议栈会生成包含断开信息的 TCP 头部, 具体来说就是将控制位中的 FIN 比特设为 1
-
然后协议栈会委托 IP 模块向客户端发送数据 ,同时服务器的套接字中也会记录下断开操作的相关信息
-
当客户端收到服务器发来的 FIN 为 1 的 TCP 头部时,客户端的协议栈会将自己的套接字标记为进入断开操作状态
-
告知服务器已收到 FIN 为 1 的包, 客户端会向服务器返回一个 ACK 号
-
这些操作完成后, 协议栈就可以等待应用程序来取数据了
-
当调用read收到全部的数据后,客户端应用程序会调用 close 来结束数据收发操作,这时客户端的协议栈也会和服务器一样, 生成一个 FIN 比特为 1 的 TCP 包, 然后委托 IP 模块发送给服务器 ,一段时间之后, 服务器就会返回ACK 号,至此客户端和服务器的通信就全部结束了
-
-
当通信结束后,过一段时间就会删除套接字,过一段时间的目的是为了防止误操作
-
如果最后客户端返回的 ACK 号丢失了,服务器没有接收到 ACK 号, 可能会重发一次 FIN,如果这时客户端的套接字已经删除了,套接字对应的端口号就会被释放出来 ,如果别的应用程序要创建套接字, 新套接字碰巧又被分配了同一个端口号,这样就会误删新的套接字
-
2.5 IP 与以太网的包收发操作
-
包是由头部和数据两部分构成的 ,头部包含目的地址等控制信息,包发往目的地址的过程
-
发送方的网络设备会负责创建包, 创建包的过程就是生成含有正确控制信息的头部, 然后再附加上要发送的数据
-
包会发往最近的网络转发设备
-
当到达最近的转发设备之后, 转发设备会根据头部中的信息判断接下来应该发往哪里
-
经过多个转发设备的接力之后, 包最终就会到达接收方的网络设备
-
-
集线器是按照以太网规则传输包的设备, 而路由器是按照 IP规则传输包的设备 。
-
路由器:根据目标地址找到下一个路由
-
集线器:在子网中将一个数据包传给下一个路由
-
-
发送方要把访问的服务器的 IP 地址写入 IP 头部中 ,IP 协议就可以根据这一地址查找包的传输方向, 从而找 到下一个路由器的位置 ,接下来, IP 协议会委托以太网协议将包传输过去 ,这时, IP 协议会查找下一个路由器的以太网地址( MAC 地址), 并将这个地址写入 MAC 头部中。 这样一来, 以太网协议就知道要将这个包发到哪一个路由器上了。
-
包收发操作的起点是 TCP 模块委托 IP 模块发送包的操作 ,这个委托的过程就是 TCP 模块在数据块的前面加上 TCP头部, 然后整个传递给 IP 模块, 这部分就是网络包的内容
-
收到委托后, IP 模块会将包的内容当作一整块数据, 在前面加上包含控制信息的头部,IP 模块会添加 IP 头部和 MAC 头部这两种头部
-
IP 头部中包含 IP 协议规定的、 根据 IP 地址将包发往目的地所需的控制信息 ,最重要的信息是IP地址,是TCP模块告知的。ip头部还需要标明发送方的 IP 地址 ,如果只有一块网卡,发送方的地址就是本机地址,如果有多块网卡怎么判断呢?
-
通过 route print 命令显示路由表
-
把目的地 IP 地址与路由表左侧的 Network Destination 栏进行比较 ,IP 模块根据路由表 Gateway 栏的内容判断应该把包发送给谁。
-
-
MAC 头部包含通过以太网的局域网将包传输至最近的路由器 所需的控制信息
-
MAC 头部的开头是接收方和发送方的 MAC 地址,地址为 48 比特
-
第 3 个以太类型字段和 IP 头部中的协议号类似,表示后面内容的类型。 以太网包的内容可以是 IP、 ARP等协议的包
-
通过 ARP 查询接收方的 MAC 地址,把查询到的结果放在ARP 缓存的内存空间 。为了保证值都是有效的在经过一段时间后把ARP 缓存的内存空间 中的所有值删除
-
-
-
无论要收发的包是控制包还是数据包,IP 对各种类型的包的收发操作都是相同的
-
以太网在判断网络包目的地时和 TCP/IP 的方式不同,需要根据MAC地址判断
-
以太网是一种为多台计算机能够彼此自由和廉价地相互通信而设计的通信技术
-
IP 生成的网络包只是存放在内存中的一串数字信息, 没有办法直接发送给对方。 因此, 我们需要将数字信息转换为电或光信号, 才能在网线上传输 ,负责执行这一操作的就是网卡
-
网卡的初始化过程?
-
打开计算机启动操作系统的时候, 网卡驱动程序会对硬件进行初始化操作, 然后硬件才进入可以使用的状态 。操作包括硬件错误检查、 初始设置等步骤 ,还需要在控制以太网收发操作的MAC模块中设置MAC地址
-
网卡的 ROM 中保存着全世界唯一的 MAC 地址 ,将这个值读出之后就可以对 MAC 模块进行设置 MAC 模块就知道自己对应的 MAC 地址了
-
-
网卡是如何将包转换成电信号并发送到网线中的 ?
-
网卡驱动从 IP 模块获取包之后, 会将其复制到网卡内的缓冲区中, 然后向MAC 模块发送发送包的命令。 接下来就轮到 MAC 模块进行工作了。
-
MAC 模块会将包从缓冲区中取出, 并在开头加上报头和起始帧分界符, 在末尾加上用于检测错误的帧校验序列
-
报头是一串像 10101010…这样 1 和 0 交替出现的比特序列, 长度为 56比特, 它的作用是确定包的读取时机
-
-
加上报头、 起始帧分界符和 FCS 之后, 我们就可以将包通过网线发送出去了, 发送信号的操作分为两种, 一种是使用集线器的半双工模式, 另一种是使用交换机的全双工 模式。
-
网卡的 MAC 模块生成通用信号,然后由 PHY(MAU)模块转换成可在网线中传输的格式,并通过网线发送出去
-
接收包的过程
-
PHY( MAU) 模 块 先 开 始 工 作, 然 后 再 轮 到 MAC 模 块 ,( MAU) 模块会将信号转换成通用格式并发送给 MAC 模块, MAC 模块再从头开始将信号转换为数字信息, 并存放到缓冲区中。 当到达信号的末尾时, 还需要检查 FCS。
-
如果 FCS 校验没有问题, 接下来就要看一下 MAC 头部中接收方MAC 地址与网卡在初始化时分配给自己的 MAC 地址是否一致, 以判断这个包是不是发给自己的 ,如果地址一致,将包放入缓冲区中 ,MAC 模块的工作就完成了, 接下来网卡会通知计算机收到了一个包。 通知计算机的操作会使用一个叫作中断的机制
-
网卡驱动被中断处理程序调用后, 会从网卡的缓冲区中取出收到的包,并通过 MAC 头部中的以太类型字段判断协议的类型
-
假如以太网类型是0080( 十六进制),网卡驱动会将其交给 TCP/IP 协议栈来进行处理
-
接下来就轮到 IP 模块先开始工作了, 第一步是检查 IP 头部, 确认格式是否正确。 如果格式没有问题, 下一步就是查看接收方 IP 地址,如果接收方IP地址和自己的地址一致就接受这个包,否则不对包进行转发,发生错误时,IP 模块会通过 ICMP 消息将错误告知发送方
-
这些包是经过分片的,IP 模块会将它们还原成原始的包 (分片重组)
-
到这里, IP 模块的工作就结束了, 接下来包会被交给 TCP 模块。 TCP模块会根据 IP 头部中的接收方和发送方 IP 地址, 以及 TCP 头部中的接收方和发送方端口号来查找对应的套接字
-
找到对应的套接字之后, 就可以根据套接字中记录的通信状态, 执行相应的操作了
-
2.6 UDP 协议的收发操作
-
不需要重发的数据用 UDP 发送更高效 。如果发送的数据很短, 用一个包就能装得下,就可用UDP传送
-
像 DNS 查询等交换控制信息的操作基本上都可以在一个包的大小范围内解决, 这种场景中就可以用 UDP 来代替TCP
-