概述
这一节了解一下协议栈和网卡,主要是一些概念的介绍。操作系统中的协议栈是如何处理数据发送请求的?简单分如下几大步骤: (1) 创建套接字(2) 连接服务器(3) 收发数据(4) 从服务器断开连接并删除套接字(5) IP 与以太网的包收发操作(6) 用 UDP 协议收发数据的操作。
TCPIP分层:浏览器、 邮件等一般应用程序收发数据时用 TCP;DNS 查询等收发较短的控制数据时用UDP。
在互联网上传送数据时, 数据会被切分成一个一个的网络包 , 而将网络包发送给通信对象的操作就是由 IP 来负责的。 此外, IP 中还包括 ICMP 协议和 ARP协议。 ICMP 用于告知网络包传送过程中产生的错误以及各种控制消息, ARP 用于根据 IP 地址查询相应的以太网 MAC 地址 。
网络包: 网络中的数据会被切分成几十字节到几千字节的小块, 每一个小数据块被称为一个包。
套接字的实体就是通信控制信息: 在协议栈内部有一块用于存放控制信息的内存空间, 这里记录了用于控制通信操作的控制信息, 例如通信对象的 IP 地址、 端口号、 通信操作的进行状态等。 本来套接字就只是一个概念而已, 并不存在实体, 如果一定要赋予它一个实体, 我们可以说这些控制信息就是套接字的实体,或者说存放控制信息的内存空间就是套接字的实体。
协议栈在执行操作时需要参阅这些控制信息 , 例如在发送数据时,需要看一看套接字中的通信对象 IP 地址和端口号, 以便向指定的 IP 地址和端口发送数据。 在发送数据之后, 协议栈需要等待对方返回收到数据的响应信息, 但数据也可能在中途丢失, 永远也等不到对方的响应。在这样的情况下, 我们不能一直等下去, 需要在等待一定时间之后重新发送丢失的数据, 这就需要协议栈能够知道执行发送数据操作后过了多长时间。 为此, 套接字中必须要记录是否已经收到响应, 以及发送数据后经过了多长时间, 才能根据这些信息按照需要执行重发操作。
套接字中记录了用于控制通信操作的各种控制信息, 协议栈则需要根据这些信息判断下一步的行动, 这就是套接字的作用。 协议栈是根据套接字中记录的控制信息来工作的。
调用 socket 时的操作:看一下浏览器通过 Socket 库向协议栈发出委托的一系
列操作,浏览器委托协议栈使用 TCP 协议来收发数据,首先是创建套接字的阶段 应用程序调用 socket 申请创建套接字, 协议栈根据应用程序的申请执行创建套接字的操作。创建: 在这个过程中, 协议栈首先会分配用于存放一个套接字所需的内存空间。 用于记录套接字控制信息的内存空间并不是一开始就存在的, 因此我们先要开辟出这样一块空间来, 这相当于为控制信息准备一个容器。然后往里面存入控制信息,套接字刚刚创建时, 数据收发操作还没有开始, 因此需要在套接字的内存空间中写入表示这一初始状态的控制信息。 接下来, 需要将表示这个套接字的描述符告知应用程序。 描述符相当于用来区分协议栈中的多个套接字的号码牌 。
连接服务器
连接:创建套接字之后, 应用程序(浏览器) 就会调用 connect, 随后协议栈会将本地的套接字与服务器的套接字进行连接。服务器这边又是怎样的情况呢? 服务器上也会创建套接字 服务器需要让客户端向服务器告知必要的信息, 比如“我想和你开始通信, 我的 IP 地址是 xxx.xxx.xxx.xxx, 端口号是 yyyy。连接实际上是通信双方交换控制信息, 在套接字中记录这些必要信息并准备数据收发的一连串操作, 像上面提到的客户端将 IP地址和端口号告知服务器这样的过程就属于交换控制信息的一个具体的例子。
控制信息, 就是用来控制数据收发操作所需的一些信息, IP地址和端口号就是典型的例子。
通信操作中使用的控制信息分为两类:(1) 头部中记录的信息(2) 套接字(协议栈中的内存空间) 中记录的信息
连接操作的实际过程 : 这个过程是从应用程序调用 Socket 库的 connect 开始, connect(< 描述符 >, < 服务器 IP 地址和端口号 >, …),上面的调用提供了服务器的 IP 地址和端口号, 这些信息会传递给协议栈中的 TCP 模块。 然后, TCP 模块会与该 IP 地址对应的对象, 也就是与服务器的 TCP 模块交换控制信息, 这一交互过程包括下面几个步骤。1 客户端先创建一个包含表示开始数据收发操作的控制信息的头部。头部包含很多字段,主要是发送方和接收方的端口号。 这样客户端(发送方) 的套接字就准确找到服务器(接收方) 的套接字, 也就是搞清楚了我应该连接哪个套接字。2 将头部中的控制位的 SYN 比特设置为 1它表示连接。总结:连接操作的第一步是在 TCP 模块处创建表示连接控制信息的头部。通过 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 并发回服务
器, 告诉服务器刚才的响应包已经收到。到此客户端的操作就已经完成。
收发数据
将 HTTP 请求消息交给协议栈 :数据收发操作是从应用程序调用 write 将要发送的数据交给协议栈开始的协议栈收到数据后执行发送操作,如下步骤: 首先, 应用程序在调用 write 时会指定发送数据的长度, 在协议栈看来, 要发送的数据就是一定长度的二进制字节序列而已。其次, 协议栈并不是一收到数据就马上发送出去, 而是会将数据存放在内部的发送缓冲区中, 并等待应用程序的下一段数据。
对较大的数据进行拆分 : HTTP 请求消息一般不会很长, 一个网络包就能装得下, 但如果其中要
提交表单数据, 长度就可能超过一个网络包所能容纳的数据量, 比如在博客或者论坛上发表一篇长文就属于这种情况。这种情况下, 发送缓冲区中的数据就会超过 MSS 的长度, 这时我们当然不需要继续等待后面的数据了。 发送缓冲区中的数据会被以 MSS 长度为单位进行拆分, 拆分出来的每块数据会被放进单独的网络包中。根据发送缓冲区中的数据拆分的情况, 当判断需要发送这些数据时, 就在每一块数据前面加上 TCP 头部, 并根据套接字中记录的控制信息标记发送方和接收方的端口号, 然后交给 IP 模块来执行发送数据的操作
MTU : 一个网络包的最大长度, 以太网中一般为 1500 字节。
MSS : 除去头部之后, 一个网络包所能容纳的 TCP 数据的最大长度。
使用 ACK 号确认网络包已收到 : 首先, 客户端在连接时需要计算出与从客户端到服务器方向通信相关的序号初始值,并将这个值发送给服务器。接下来, 服务器会通过这个初始值计算出 ACK 号并返回给客户端 。 初始值有可能在通信过程中丢失, 因此当服务器收到初始值后需要返回 ACK 号作为确认。 同时, 服务器也需要计算出与从服务器到客户端方向通信相关的序号初始值, 并将这个值发送给客户端。 接下来像刚才一样, 客户端也需要根据服务器发来的初始值计算出 ACK 号并返回给服务器 。 到此。序号和 ACK 号都已经准备完成了,接下来就可以进入数据收发阶段了。 数据收发操作本身是可以双向同时进行的, 但 Web 中是先由客户端向服务器发送请求, 序号也会跟随数据一起发送。 然后, 服务器收到数据后再返回 ACK 号。 从服务器向客户端发送数据的过程则正好相反。小结:通过“序号”和“ACK 号”可以确认接收方是否收到了网络包。
调整 ACK 号等待时间 : 根据网络包平均往返时间调整 ACK 号等待时间,首先是返回 ACK 号的等待时间(这个等待时间叫超时时间)当网络传输繁忙时就会发生拥塞, ACK 号的返回会变慢, 这时我们就必须将等待时间设置得稍微长一点, 否则可能会发生已经重传了包之后, 前面的 ACK 号才姗姗来迟的情况。 因此, TCP 采用了动态调整等待时间的方法, 这个等待时间是根据 ACK 号返回所需的时间来判断的。 具体来说, TCP 会在发送数据的过程中持续测量 ACK 号的返回时间, 如果 ACK 号返回变慢, 则相应延长等待时间; 相对地, 如果 ACK 号马上就能返回, 则相应缩短等待时间 。
接收 HTTP 响应消息 :首先, 浏览器在委托协议栈发送请求消息之后, 会调用 read 程序 来获取响应消息。 然后, 控制流程会通过 read 转移到协议栈 , 然后协议栈会执行接下来的操作。 和发送数据一样, 接收数据也需要将数据暂存到接收缓冲区中,操作过程如下:首先, 协议栈尝试从接收缓冲区中取出数据并传递给应用程序, 但这个时候请求消息刚刚发送出去, 响应消息可能还没返回。 响应消息的返回还需要等待一段时间, 因此这时接收缓冲区中并没有数据, 那么接收数据的操作也就无法继续。 这时, 协议栈会将应用程序的委托, 也就是从接收缓冲区中取出数据并传递给应用程序的工作暂时挂起 , 等服务器返回的响应消息到达之后再继续执行接收操作。简而言之,首先, 协议栈会检查收到的数据块和TCP 头部的内容, 判断是否有数据丢失, 如果没有问题则返回 ACK号。 然后,协议栈将数据块暂存到接收缓冲区中, 并将数据块按顺序连接起来还原出原始的数据, 最后将数据交给应用程序。
客户端与服务端断开连接: 以 Web 为例, 浏览器向 Web 服务器发送请求消息, Web 服务器再返回响应消息, 这时收发数据的过程就全部结束了, 服务器一方会发起断开过程。 当然, 可能也有一些程序是客户端发送完数据就结束了, 不用等服务器响应, 这时客户端会先发起断开过程。 这一判断是应用程序作出的, 协议栈在设计上允许任何一方先发起断开过程。以服务器一方发起断开过程为例,首先, 服务器一方的应用程序会调用 Socket 库的 close 程序。 然后, 服务器的协议栈会生成包含断开信息的 TCP 头部, 具体来说就是将控制位中的 FIN 比特设为 1。 接下来,协议栈会委托 IP 模块向客户端发送数据。同时,服务器的套接字中也会记录下断开操作的相关信息。接下来轮到客户端了。 当收到服务器发来的 FIN 为 1 的 TCP 头部时,客户端的协议栈会将自己的套接字标记为进入断开操作状态。然后, 为了告知服务器已收到 FIN 为 1 的包, 客户端会向服务器返回一个 ACK号。 这些操作完成后, 协议栈就可以等待应用程序来取数据了。
过了一会儿, 应用程序就会调用 read 来读取数据 。 这时, 协议栈不会向应用程序传递数据 , 而是会告知应用程序(浏览器) 来自服务器的数据已经全部收到了。 根据规则, 服务器返回请求之后, Web 通信操作就全部结束了,收到服务器返回的所有数据客户端应用程序会调用 close 来结束数据收发操作, 这时客户端的协议栈也会和服务器一样, 生成一个 FIN 比特为1 的 TCP 包, 然后委托 IP 模块发送给服务器。 一段时间之后, 服务器就会返回 ACK 号,到此, 客户端和服务器的通信就全部结束了。
删除套接字: 和服务器的通信结束之后, 用来通信的套接字也就不会再使用了, 这时我们就可以删除这个套接字了。 不过, 套接字并不会立即被删除, 而是会等待一段时间之后再被删除。
包的基本知识 : TCP 模块在执行连接、 收发、 断开等各阶段操作时, 都需要委托 IP 模块将数据封装成包发送给通信对象。 下面介绍一下 IP 模块是如何将包发送给对方的 网络包的一些基本知识。 首先, 包是由头部和数据两部分构成的。 头部包含目的地址等控制信息, 大家可以把它理解为快递包裹的面单; 头部后面就是委托方要发送给对方的数据, 也就相当于快递包裹里的货物。
首先, 发送方的网络设备会负责创建包, 创建包的过程就是生成含有正确控制信息的头部, 然后再附加上要发送的数据。 接下来, 包会被发往最近的网络转发设备。 当到达最近的转发设备之后, 转发设备会根据头部中的信息判断接下来应该发往哪里。 这个过程需要用到一张表, 这张
表里面记录了每一个地址对应的发送方向, 也就是按照头部里记录的目的地址在表里进行查询, 并根据查到的信息判断接下来应该发往哪个方向。接下来, 包在向目的地移动的过程中, 又会到达下一个转发设备, 然后又会按照同样的方式被发往下一个转发设备。 就这样, 经过多个转发设备的接力之后, 包最终就会到达接收方的网络设备。 当然, 发送方向接收方发送一个包, 接收方可能也会向发送方返回一个包, 此时的发送方到了接下来的某个时刻就会变成接收方。 因此, 我们不需要把发送方和接收方明确区分开来, 在这里我们把发送方和接收方统称为终端节点。
路由器和集线器的作用
(1) 路由器根据目标地址判断下一个路由器的位置
(2) 集线器在子网中将网络包传输到下一个路由
TCP/IP 包包含如下两个头部:(a) MAC 头部(用于以太网协议);(b) IP 头部(用于 IP 协议)
这两个头部分别具有不同的作用。 首先, 发送方将包的目的地, 也就是要访问的服务器的 IP 地址写入 IP 头部中。这样一来, 我们就知道这个包应该发往哪里, IP 协议就可以根据这一地址查找包的传输方向, 从而找到下一个路由器的位置, 方向, 从而找到下一个路由器的位置, 也就是图 2.16 中的路由器 R1。 接下来, IP协议会委托以太网协议将包传输过去。 这时, IP 协议会查找下一个路由器的以太网地址(MAC 地址) , 并将这个地址写入 MAC 头部中。 这样一来, 以太网协议就知道要将这个包发到哪一个路由器上了。
包收发操作 : 包收发操作的起点是 TCP 模块委托 IP 模块发送包的操作,这个委托的过程就是 TCP 模块在数据块的前面加上TCP 头部, 然后整个传递给 IP 模块, 这部分就是网络包的内容。 与此
同时, TCP 模块还需要指定通信对象的 IP 地址, 也就是需要写清楚“将什么内容发给谁”。收到委托后, IP 模块会将包的内容当作一整块数据, 在前面加上包含控制信息的头部。 IP 头部中包含 IP 协议规定的、 根据 IP 地址将包发往目的地所需的控制信息; MAC 头部包含通过以太网的局域网将包传输至最近的路由器所需的控制信息.加上这两个头部之后, 一个包就封装好了, 这些就是 IP 模块负责的工作。接下来, 封装好的包会被交给网络硬件,网络硬件可能是插在计算机主板上的板卡, 也可能是笔记本电脑上的 PCMCIA 卡, 或者是计算机主板上集成的芯片, 不同形态的硬件名字也不一样, 将它们统称为网卡传递给网卡的网络包是由一连串 0 和 1 组成的数字信息, 网卡会将这些数字信息转换为电信号或光信号, 并通过网线(或光纤) 发送出去, 然后这些信号就会到达集线器、 路由器等转发设备, 再由转发设备一步一步地送达接收方。
包送达对方之后, 对方会作出响应。 返回的包也会通过转发设备发送回来, 然后我们需要接收这个包。 接收的过程和发送的过程是相反的, 信息先以电信号的形式从网线传输进来, 然后由网卡将其转换为数字信息并传递给 IP 模块。 接下来, IP 模块会将 MAC头部和 IP 头部后面的内容, 也就是 TCP 头部加上数据块, 传递给 TCP模块。
生成以太网用的 MAC 头部 : 生成了 IP 头部之后, 接下来 IP 模块还需要在 IP 头部的前面加上 MAC头部,MAC 头部是以太网使用的头部, 它包含了接收方和发送方的 MAC 地址等信息。MAC 头部的开头是接收方和发送方的 MAC 地址, 大家可以认为它们和 IP 头部中的接收方和发送方 IP 地址的功能差不多, 只不过 IP 地址的长度为 32 比特, 而 MAC 地址为 48 比特。
ARP 查询目标路由器的 MAC 地址 : 使用 ARP(Address Resolution Protocol, 地址解析协议) 就是利用广播对所有设备提问: “×× 这个 IP 地址是谁的? 请把你的 MAC 地址告诉我。 ”然后就会有人回答: “这个 IP 地址是我的, 我的 MAC 地址是 ××××。 ”如果对方和自己处于同一个子网中, 那么通过上面的操作就可以得到对方的 MAC 地址 。 然后, 我们将这个 MAC 地址写入 MAC 头部,MAC 头部就完成了。
最后
以上就是鲜艳舞蹈为你收集整理的网络笔记--协议栈和网卡的全部内容,希望文章能够帮你解决网络笔记--协议栈和网卡所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复