大家好,我是速冻鱼🐟,一条水系前端💦,喜欢花里胡哨💐,持续沙雕🌲,是隔壁寒草🌿的好兄弟。
欢迎小伙伴们加我微信:
sudongyuer
拉你进群,一起讨论,期待与大家共同成长🥂。
阅读本文 📖
1.您将了解到输入URL到浏览器展示内容的网络全貌
2.对计算机网络有个宏观的认识
3.用宏观的角度去了解计算机网络,构建计算机网络知识体系,鱼鱼将会一章章跟新
4.对基础知识充满了兴趣和爱好,培养自己对计算机的兴趣
前言 🌵
学习了很长时间的计算机网络,我发现计算机网络是非常有意思的一个课题,但是它的知识面广而深,通过学习我发现,很多书都是深入某一部分去讲解,未免让人失去兴趣,从URL到浏览器展示画面能够让我们全面的了解到网络的从零到一
知识点 📒
创建套接字
先来看看协议栈内部结构
应用程序使用的都是操作系统提供的上层抽象库Socket,Socket库是由操作系统提供的,里面分了很多程序组件,上文中提到的DNS解析发送数据包就是通过UDP模块发送的。
浏览器、邮件等一般应用程序收发数据时是使用的TCP;
DNS查询等收发较短的控制数据,和具有实效性数据时使用UDP。
IP协议控制网络包的收发操作,在互联网上传输数据时,数据会被切分成一个个的网络包,IP协议就负责传输它们。
IP中包括ICMP协议和ARP协议。
ICMP:用于告知网络包传输过程中是否产生错误,以及各种控制信息。
ARP:用于查询IP地址所对应的MAC地址,一般都是谁是XXXIP你的,类似广播的方式📢
IP下面就是网卡驱动程序,负责控制网卡硬件。网卡负责实际的收发操作。
协议栈,协议栈内部是根据套接字中记录的控制信息来工作的,协议栈内部有一块内存是保存控制信息的内存空间,这里记录了通信操作的控制信息,例如IP地址、端口号、通信操作的进行状态。
使用netstat可以查看套接字内容
套接字中的内容
控制信息就是套接字实体,或者说控制信息的内存空间就是套接字实体。
浏览器通过socket库像协议栈委托操作。
首先创建套接字,应用程序调用socket程序组件申请创建套接字。
然后就会在内存中开辟一块空间用来保存控制协议,这个就是套接字。
创建套接字时,首先先分配一个套接字所需的内存空间,然后向其中写入初始状态。
图中这个描述符就是用于计算机内部区分多个套接字的号码牌,端口号是用于网络中确定对方端口号的标识
连接服务器
创建好套接字之后,应用程序就会调用connect,随后协议栈就会将本地的套接字与服务器的套接字进行连接。
这里的连接实际就是值得客户端和服务器交换控制信息,记录一些收发数据需要的数据。
控制信息也分两类:
1.是客户端和服务器相互联络时交换的控制信息。这些信息不仅连接时需要,包括数据收发和断开连接操作在内,整个通信过程都需要。
tcp头部格式
2.控制信息还有一类,那就是保存在套接字中,用来控制协议栈操作的信息。
建立连接
创建好套接字以后,开始建立连接,应用程序调用Soket库中的connect开始
connect(<描述符>,<服务器IP地址和端口号>)
这些信息会交给TCP模块,TCP模块会和服务器的TCP模块开始交换这些信息,交互过程包含以下几个步骤。
1.首先客户端创建开始数据收发操作的控制信息的头部(重点是发送方和接受方的端口号),通过TCP头部中的发送方和接收方端口号就可以找到要连接的套接字。
2.TCP创建好了以后,TCP模块会将信息交给IP模块并委托它发送出去,IP模块执行网络包发送操作后,服务器IP模块收到了数据包后会讲数据包交给TCP模块,服务器的TCP模块会根据TCP头部中的信息找到端口号所对应的套接字,并写入相应的控制信息,并将状态改为正在连接。然后服务器TCP模块会返回响应以及SYN比特。返回响应时还要将ACK控制位设置为1,表示已经接收到了响应的网络包。ACK就是用于双方确定网络包是否送达的,客户端收到了这个网络包后会向套接字写入服务器IP地址和端口号等信息,还会将连接状态改为连接完毕。到这里客户端的操作就已经完成了,但其实还差一步就是发送一个响应包表示接受到了上次服务端发送的包,并将ACK为比特设置为1,服务端收到这个包后,连接操作才算全部完成!然后套接字就进入了随时可以发送收据的状态了,就相当于一根管子把两个套接字连接起来了。
收发数据
当连接建立好了以后,就可以开始愉快的收发数据了调用Scoket库中的write组件,write组件将要发送的数据交给协议栈,协议栈并不关心要发送的数据是什么内容,因为数据只是一定长度的二进制字节序列而已。
协议栈并不是收到数据就立即发送的,而是会将数据保存在内部的发送缓冲区
,并等待应用程序的下一段数据。为什么呢,因为应用程序一次将多少数据传递给协议栈是应用程序控制的,为了控制网络传输效率。
当数据积累到一定量时再发送出去,至于积累多少数据,不同的操作系统不同。但有几个要素来判断。
1.网络包能容纳的数据长度MTU
(最大传输单元),在以太网中一般是1500字节。MTU包含头部的总长度。MSS
(最大分段大小)就是除去头部一个网络包最大能容纳的数据长度。
白话来说,MTU就是整个网络包的大小包含各种头部(IP头->数据)
MSS就是纯数据的大小(TCP分段)
2.另一个要素是时间要素。当应用程序发送数据的频率不高时,如果每次都等到长度接近MSS时再发送,等待时间就会太长,导致发送延迟,这种情况即便缓冲区没有到达MSS,也应该果断发送出去,因此协议栈内部有一个定时器
,当经过一定时间后就会把网络包发送出去。(毫秒级别的)
⚠️当然这两者时矛盾的,协议栈也给应用程序流了控制发送时机的余地,应用程序在发送数据可以指定一些选项来控制,浏览器一般采用的是直接发送的选项。
对较大的数据进行拆分
HTTP请求消息一般不大,但是当我们提交表单数据的时候,数据的大小可能MSS,这时候我们就需要拆分数据包了,会按照MSS的长度拆分,拆分出来的数据会被放入单独的网络包。当需要发送这些数据时,就会在每一块数据前面加上TCP头部,并根据套接字中记录的控制信息标记发送方的端口号,然后交给IP模块来执行发送数据的操作。
一般应用程序的数据都比较大,因此TCP会按照网络包的大小对数据进行拆分。
这样我们的包就发送出去了,但数据发送的操作还没结束,TCP具备确认对方是否成功收到了网络包,以及到网络包未成功到达目标计算机时进行重发的功能,因为TCP还需要进行确认操作。
每个数据包发送的时候都会带上序列号和长度,通过这些信息就能够检查网络包是否有遗漏。
实际情况中序列号不是从一开始的,这样很容易被人猜出来发动攻击。还记得我们在建立连接时SYN设置为1的同时,还需要同时设置序列号字段,这个序列号就代码序号的初始值。
通过序号和ACK号就可以确认接受方是否收到了网络包
滑动窗口
如果我们发送一个包就等待一个ACK号,这种方式太浪费了。为了减少这样的浪费,TCP采用滑动窗口来管理数据的发送和ACK号的操作,所谓滑动窗口就是在发送一个数据包之后,不用等待ACK号的返回,而是直接发送后续的一系列包。这样等待ACK号的时间就能杯有效利用起来了。
当接收方的 TCP 收到包后,会先将数据存放到 接收缓冲区中。然后,接收方需要计算 ACK 号,将数据块组装起来还原 成原本的数据并传递给应用程序,如果这些操作还没完成下一个包就到了也不用担心,因为下一个包也会被暂存在接收缓冲区中。如果数据到达的 速率比处理这些数据并传递给应用程序的速率还要快,那么接收缓冲区中 的数据就会越堆越多,最后就会溢出。缓冲区溢出之后,后面的数据就进 不来了,因此接收方就收不到后面的包了,这就和中途出错的结果是一样 的,也就意味着超出了接收方处理能力
如果避免缓冲区溢出呢,那么接受方需要告诉发送方自己最能多接受多少数据,然后发送发方根据这个数据控制发送数据操作。这就是滑动窗口的基本思路。
接受方缓冲区能够接收的最大数据量为窗口大小
(它是 TCP 调优参数 中非常有名的一个)
通过ACK 与通知更新窗口的包的合并来减少数据包的频繁发送,可以将ACK包和通知窗口更新的数据放在一个数据包发送,也可以将多个ACK合并成最后一次ACK
到这里我们的数据就从客户端发送到了服务端,接下来还要等待WEB服务器返回响应消息。然后我们将返回的数据包存放在接受缓冲区,并将数据块按顺序连接起来还原出原始的数据,最后将数据交给应用程序。具体来说,协议栈会将接收到的数据复制到应用程序指定的内存地址中,然后将控制流程交回应用程序。将数据交给应用程序之后,协议栈还需要找到合适的时机向发送方发送窗 口更新。
总结 🍁
- 创建套接字
- 建立连接设置序列号
- 通过滑动窗口来控制发送方数据收发
- 将接收的数据存放在缓冲区
- 将缓冲区数据交给应用程序时更新窗口大小