《网络是怎样连接的》第二章 协议栈,网卡 (上)

2.1 创建套接字

2.1.1 协议栈的内部结构

网络控制软件 = 协议栈
网络硬件 = 网卡
在这里插入图片描述
不同的应用程序收发的数据内容不同,但收发数据的操作是共通的,协议栈的上半部分分为两部分,分别是TCP和UDP,像浏览器,邮件等一般的数据采用TCP收发数据,而像DNS查询等收发较短的控制数据时使用UDP

下面一半是用IP协议控制网络包收发操作的部分,在互联网上传送数据时,数据会被切分为一个一个的网络包,而将网络包发送给通信对象的操作是由IP来负责的,此外,IP中还包括ICMP协议ARP协议,ICMP协议用于告知网络包传送过程中产生的错误以及各种控制消息,ARP用于根据IP地址查询相应的以太网MAC地址

IP下面的网卡驱动程序负责控制网卡硬件,最下面的网卡负责完成实际的收发操作,也就是对网线中的信号执行发送和接收的操作

2.1.2 套接字的实体就是通信控制信息

在协议栈内部有一块用于存放控制信息的内存空间,这里记录了控制通信操作的控制信息,例如通信对象的IP地址,端口号,通信操作的进行状态等

套接字本来只是一个概念没有实体,如果非给它一个实体,那么套接字就可看作协议栈内部内存空间中控制通信操作的控制信息。协议栈在执行操作时需要先参阅这些控制信息,如发送数据时,需要看看套接字中的通信对象的IP地址和端口号。

在发送数据后,协议栈需要等待对方返回收到数据的响应信息,但数据也可能在中途丢失,需要等待一段时间后重新发送,这就需要协议栈知道执行发送数据后过了多长时间,为此,套接字中必须要记录是否收到响应,以及发送数据后经过了多长时间。

套接字中记录了用于控制通信操作的各种控制信息,协议栈则需要根据这些信息判断下一步的行动,这就是套接字的作用

2.1.3 调用socker时的操作

当创建套接字时,首先会分配一个套接字所需要的内存空间,然后向其中写入初始状态。然后需要将这个套接字的描述符告诉应用程序,描述符相当用于区分协议栈中的多个套接字的号码牌。

收到描述符后,应用程序在向协议栈进行收发数据委托时就需要这个描述符,由于套接字中记录了通信双方的信息及通信所处于怎样的状态,所以只要通过描述符确定了套接字,协议栈就能获得相关控制信息

在这里插入图片描述

2.2 连接服务器

2.2.1 连接的意思

创建套接字后,应用程序(浏览器)就会调用connect,随后协议栈会将本地的套接字与服务器的套接字进行连接,这里连接的意思是:通信双方交换控制信息,在套接字中记录这些必要信息并准备收发数据的一连串操作

客户端套接字的创建:套接字刚创建时,里面并没有任何数据,也不知道和谁通信。在这个状态下,即使应用程序要求发送数据,协议栈也不知道该发给谁,浏览器可以根据网址来查询服务器的IP地址,而且知道使用80号端口,但只有浏览器知道是不够的,当调用socket创建套接字时,这些信息并没有传递给协议栈,因此,我们需要将服务器IP地址和端口号等信息告知协议栈,这是连接操作的目的之一

服务器上套接字的创建:服务器上的协议栈和客户端一样,只创建套接字但不知道和谁通信。于是,我们需要让客户端向服务器告知必要的信息,比如“我想和你开始通信,我的IP地址是xxx.xxx.xxx.xxx,端口号是yyyy”,客户端向服务器传达开始通信的请求也是连接操作的目的之一

控制信息就是用来控制数据收发操作所需要的一些信息,IP地址和端口号就是典型的例子。当执行收发数据操作时,还需要一块用来临时存放要收发数据的内存空间,这块空间称为缓冲区,它也是在连接操作的过程中分配的

2.2.2 负责保存控制信息的头部

控制信息可以分为两类

第一类是客户端和服务器相互联络时交换的控制信息,这些信息不仅连接时需要,在通信的整个过程中也都需要。这些信息被添加在客户端与服务器之间传递的网络包的开头,为了避免不同的头部发生混淆,会记为TCP头部,以太网头部,IP头部

在这里插入图片描述

客户端和服务器在通信中会将必要的信息记录在头部并互相确认,头部非常重要,用来记录和交换控制信息

第二类控制信息就是保存在套接字中,用来控制协议栈操作的信息,应用程序传递来的信息以及从通信对象接收到的信息还有收发数据操作的执行状态都会保存在这里,协议栈会根据这些进行每一步的操作。套接字的控制信息和协议栈的程序本身是一体的

2.2.3 连接操作的实际过程

当应用程序调用Socket库的connect时,它提供了服务器的IP地址和端口号,这些信息会传递给协议栈中的TCP模块,然后TCP模块会与该IP地址对应的对象,即服务器的TCP模块交换控制信息

上述过程详细步骤为:
1,客户端先创建一个包含表示开始数据收发操作的控制信息的头部,到这里,客户端的套接字就准确找到了服务器的套接字。
2,将头部中的SYN比特设置为1(表示连接),此外还需要设置适当的序号和窗口大小。
3,当TCP头部创建好后,TCP模块会将信息传递给IP模块委托它进行发送,IP模块执行网络包发送任务后,网络包就会将通过网络到达服务器。
4,服务器上的IP模块将接收到的数据传递给TCP模块,服务器的TCP模块根据TCP头部中的信息找到端口号对应的套接字
5,套接字中写入相应的信息,并将状态改为正在连接
6,服务器的TCP模块返回响应,在TCP头部中设置发送方和接收方端口号及SYN比特,在返回响应还需将ACK设为1(表示已接收到相应包)。由于网络中经常出错,通信双方必须互相确认包是否送达
7,服务器TCP模块将头部传递给IP模块,并委托IP模块返回响应
8,包传递到客户端,通过IP模块到达TCP模块,并通过TCP头部的信息确认连接服务器的操作是否成功,如果SYN为1,则表示连接成功。
9,向套接字中写入服务器的IP地址,端口号等信息,同时将状态改为连接完毕
10,客户端将ACK设为1并发回服务器,告诉服务器刚才的响应包已经收到,当服务器收到这个响应包后,连接操作才算全部完成

2.3 收发数据

2.3.1 将HTTP请求消息交给协议栈

数据收发操作是从应用程序调用write将要发送的数据交给协议栈开始的,协议栈收到数据后执行发送操作,这一操作包含:
1,协议栈并不关心应用程序传来的数据是什么内容,应用程序在调用write时会指定发送数据的长度
2,协议栈并不是一收到数据就马上发送出去,而是会将数据存放在内部的发送缓冲区中,并等待应用程序的下一段程序。一次将多少数据交给协议栈是由应用程序决定的。如果一收到数据就立刻发送出去,就可能会发送大量的小包,导致网络效率降低,因此需要将数据积累到一定量时再发送出去,至于要积累多少数据才发送,是由操作系统决定的

在这里插入图片描述

积累多少数据才发送主要由两个元素决定:第一个判断要素是每个网络包能容纳的数据长度,协议栈会根据一个叫MTU 最大传输单元的参数判断,MTU是包含头部的总长度,用MTU减头部长度就是有效数据长度 MSS,当应用程序收到的数据长度超过或接近MSS时再发送出去,就可以避免发送大量小包
另一个判断的要素是时间,当应用程序发送数据的频率不高时,如果每次等待数据长度接近MSS时再发送,可能会因为等待时间太长而产生发送延迟,为此,协议栈中有一个计数器,当经过一段时间后,就会把网络包发送出去

这两个要素实际上互相矛盾,如果长度优先,那么网络无效率会提高,但可能因为等待填满缓冲区而产生延迟,相反,如果时间优先,那么延迟减小,但又会降低网络的效率。因此实际如何判断是由协议栈的开发者决定的,不同种类和版本的操作系统在相关操作上存在差异

2.3.2 对较大的数据进行拆分

HTTP消息一般不会很长,一个网络包就能装的下,但如果发送表单数据,长度可能超过一个网络包所能容纳的数据量,比如在博客上写一篇文章。在这种情况下,发送缓冲区中的数据就会超过MSS的长度,这时我们当然不需要等待后面的数据,发送缓冲区的数据会被以MSS长度为单位进行拆分,拆分出来的每块数据会被放到单独的网络包中

在这里插入图片描述

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

经过上面的操作,网络包已经装好数据并发往服务器了,但数据发送操作还没有结束。TCP具备确认对方是否成功接收到网络包,以及当对方没收到进行重发的功能

TCP模块在拆分数据时,会计算好每一块数据相当于从头开始的第几个字节,接下来在发送这一块数据时,将算好的字节数写入TCP头部中,序号字段就是用在这里的,且发送数据的长度也需要告诉接收方
如果上次接收到1460字节,那么接下来如果收到序号为1461的包,说明中间没有遗漏,接收方会将目前为止接收到的数据长度加起来,计算一共已经收到多少字节,将这个数值写入TCP头部的ACK号中发给发送方,这个返回ACK号称为确认响应,通过这样的方式,发送方就能确认对方到底接收了多少数据

在这里插入图片描述
通过这一机制,无论网络中发生任何错误,我们都可以发现并采用重传包的方法补救。网卡,集线器,路由器都没有错误补偿机制,一旦检测到错误就直接丢弃相应的包,应用程序也是一样,由于采用TCP传输,即便发生错误,最终接收方也能得到正确的数据

2.3.4 根据网络包平均往返时间调整ACK号等待时间

实际中网络的错误检测和补偿机制非常复杂。当网络传输繁忙就会发生拥塞,ACK号的返回会变慢,这时需要将等待时间增长,否则可能会发生已经重传了包后,前面的ACK号才姗姗来迟,这样的重传是多余的,如果出现大规模包的重传,那么本来拥塞的网络更雪上加霜。但是等待时间也不是越长越好,如果时间过长,那么出错时包的重传会出现很大的延迟,也会导致网络速度变慢

所以需要将等待时间设置为一个固定的值并不是一个好办法,因此TCP采用了动态调整等待时间的办法,这个等待时间的调整是根据ACK号返回所需要的时间来判断的。TCP会在发送数据的过程中持续测量ACK号的返回时间,如果ACK号返回变慢,则延长等待时间,如果ACK号能很快返回,则缩短返回时间

2.3.5 使用窗口有效管理ACK号

在这里插入图片描述
每发送一个包就等待一个ACK号的方式是最简单也是最容易理解的,但在等待ACK号的这段时间,什么都不做太过于浪费。为了减少这样的浪费,TCP采用滑动窗口方式来管理数据发送和ACK号的操作

所谓滑动窗口,就是在发送一个包后,不等待ACK号返回,而是直接发送后续一系列的包。虽然这样能减少等待ACK号时的时间浪费,但如果不等待返回ACK号就直接连续发送包,就有可能出现发送包的频率超过接收方处理能力的情况

当接收方的TCP收到包后,会先将数据存放到接收缓冲区中,然后接收方需要计算ACK号,将数据块组装起来还原成原本的数据并传递给应用程序。如果数据到达的速度比处理这些数据并传递给应用程序的速度还要快,那么接收缓冲区的数据就会越来越多,最后会溢出,后面的包就无法被接收

所以接收方需要告诉发送方自己最多能接收多少数据,然后发送方根据这个值对数据发送进行控制,这就是滑动窗口方式的基本思路。能够接收的最大数据量称为窗口大小,一般和接收方的缓冲区大小一致
在这里插入图片描述

2.3.6 ACK与窗口的合并

返回ACK号和更新窗口的时机这两个参数如果用两个单独的包发送,这样接收方给发送方的包就太多了,导致网络效率下降

因此,接收方在发送ACK号和窗口更新时,不会马上把包发过去,而是等待一段时间,将两种通知合并在一个包里发送。如在等待发送ACK号时正好需要更新窗口,这时可以将ACK号和窗口更新放在一起发送

2.3.7 接收HTTP响应消息

浏览器在委托协议栈发送请求消息后,调用read程序组件来获取响应消息,控制流程会通过read转移到协议栈,然后协议栈会执行接下来的操作。和发送数据一样,接收数据也需要将数据暂存在缓冲区中,协议栈尝试从接收缓冲区中取出数据并传递给应用程序

协议栈会先检查收到的数据块和TCP头部的内容,判断数据是否丢失,如果没问题将数据块按顺序连接起来还原成原始的数据,最后将数据交给应用程序。协议栈还需要找到合适的时机向发送方发送窗口更新

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

2.4.1 数据发送完后断开连接

收发数据结束的时间点应该是应用程序判断所有的数据都已发送完毕的时候。不同的应用程序选择不同的断开时机,以Web为例,浏览器向Web服务器发送请求消息,Web服务器再返回响应消息,这时收发数据结束,服务器断开连接。协议栈在设计上允许任何一方先发起断开过程

无论哪种情况,完成数据发送的一方会发起断开过程,服务器一方的应用程序会调用Socket库的close程序组件,然后,服务器的协议栈会生成包含断开信息的TCP头部,即将控制位的FIN比特设为11,接下来委托IP模块向客户端发送数据,同时服务器的套接字中也会记录断开操作的相关信息

当客户端收到来自服务器的FIN为1的TCP头部时,客户端的协议栈会将自己的套接字标记为进入断开操作状态。然后,为了告知服务器已经接收到FIN为1的包,客户端发送一个ACK号,完成这些后,协议栈可以等待应用程序来取数据,过一会应用程序调用read来读取数据

2.4.2 删除套接字

服务器的通信结束后,用来通信的套接字也就不会再使用了,但套接字不会立刻删除,而是等待几分钟后才删除。这样做的目的是防止误操作,FIN错误的跑到同端口新的套接字里,导致新连接断连

2.4.3 数据收发小结

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值