文章是对整个网络交互的总结(客户端部分),本篇会以客户端为侧重点,比较详细的讲解客户端发起网络请求到服务端返回数据这一个交互过程当中客户端主要作了哪些工做以及经历了什么。文内理论知识偏多,不会讲得特别深刻,可是看完有一个全面了解是足够的。html
在此以前我先提几个会在文章内有解释的问题,能够先思考一下,题目比较基础,懂的童鞋能够在文内看看写的是否正确,不是太懂的童鞋能够经过阅读本文掌握他们,我也是抱着加深本身的记忆来写这篇文章的:git
能够先尝试做答一下。固然文内的知识必定不止于回答这几个问题而已。github
首先咱们以web浏览器为例先来大概了解一下网络的全貌(固然移动应用也是相似的):web
到这里,一次网络的交互也到达了终点。上面有不少词不理解不要紧,接下来文内都会有讲解,而后咱们就开始尽可能往详细的讲每一个环节。面试
因而咱们先来解释网址究竟是什么?网址,准确的来讲应该叫URL(Uniform Resource Locator,统一资源定位符)或者也能够理解为http开头的那个东西,下图上一张完整的URL图: windows
因此这个URL能够理解为:user使用https协议访问www.baidu.com这个服务器上/dir/目录下file.html这个文件数组
固然咱们上面说的是一个很是典型的URL,固然也有一些不同的,好比文章开头的第一个问题,咱们在此多加两个类型进行分析:浏览器
https://github.com/ChavezChen/
https://github.com/
https://github.com
https://github.com/ChavezChen/CWLateralSlide
复制代码
首先咱们在前面已经知道了域名以后的“/”后面这部分是表明要访问的文件路径缓存
在此以前,咱们先来说一讲HTTP协议究竟是怎么回事:首先,客户端会向服务器发送请求消息,请求消息中包含了“对什么”和“进行怎样的操做”两部分,其中至关于“对什么”的部分称为URI(Uniform Resource Identifer,统一资源标志符),“进行怎样的操做”的部分称为方法,方法表示须要让服务器完成怎样的工做,也就是咱们常见的“GET、POST、PUT、DELETE”等......咱们拿几个常见的来举例说明一下:bash
其余的一些方法咱们就不一一举例了,面试时候GET和POST的区别在网上有很是多的答案,想了解的能够自行搜索如下。
对URL进行解析以后,会根据解析出来的服务器和文件名信息生成HTTP请求消息,HTTP请求消息在格式上是有严格规定的,因此客户端都会按照规定的格式来生成请求消息:
一、<方法><空格><URI><空格><HTTP版本>
二、<头字段名>:<头字段值> 头字段名如:Content-Type,表示消息体的数据类型.等
三、...
四、...
五、<空行>
六、<消息体>
第1行为请求行,经过这一行大体了解请求内容。
2-5行成为消息头,每行包含一个头字段,用于表示请求消息的附加信息。消息头的行数一直
延伸到空行为止,好比iOS中能够经过AFN的requestSerializer来设置头信息
第6行消息体,包含客户端向服务器发送的数据。
复制代码
发送请求后会收到响应,这个后面在说,简单解释一下响应的各个状态码的含义:
1xx 告知请求的处理进度状况。临时。
2xx 成功
3xx 表示须要进一步操做,重定向
4xx 客户端/请求错误
5xx 服务器错误
复制代码
在消息发送以前,咱们还有一个工做须要完成,就是查询网址中服务器域名对应的IP地址,在委托操做系统发送消息时,必须提供的是IP地址而不是域名。
有童鞋确定会有疑问,既然还要去查询ip地址那我干脆在网址中不写域名写IP地址不就行了?实际上这样也能正常工做,可是就像你容易记名字难记电话号码同样,要记住一串IP地址也是很是困难的,所以相对IP地址来讲,网址中使用域名更好。
那有有童鞋要发问了,那干脆不用IP地址直接用域名来确认不就行了么?IP地址的长度是32bit,也就是4字节,而域名最短的也要几十个字节,换句话说,使用IP地址只要处理4字节的数字,而域名则要处理几十到255个字符,这增长了路由器的负担,传送数据也会花费更长时间。
在网络中,全部的设备都会分配一个地址,这个地址至关于现实中的“xx号xx室”,其中“号”是分配给整个子网的,称为网络号,“室”是分配给子网中的计算机的,称为主机号,这个地址总体称为IP地址。什么是子网呢?子网能够理解为用集线器链接起来的几台计算机,好比,一个房间有几台计算器,他们都链接到一个集线器上,那么他们就造成一个子网,而后多个子网在经过路由器链接起来就造成一个网络。而后你又想到上现实中,啥?没见过集线器这种东西,家里都是多台电脑用网线连到一个路由器上的啊!由于如今不少家用路由器中已经内置了集线器功能,因此你也能够把一个路由器链接的多台电脑,统称为一个子网。
经过IP地址咱们能够判断访问对象服务器的位置,从而将消息发送到服务器。消息传送的具体过程后面再讲,不过咱们先简单的了解一下:发送者发出的消息首先通过子网的集线器转发到发送者最近的路由器(家用的由于集线器和路由器二合一了,能够理解为从网线到了路由器上面),接下来路由器会根据消息的目的地判断下一个路由器的位置,将消息发到下一个路由器,不断重复以后消息就到了服务器最近的路由器,最终消息就被传送到了目的地。整个过程与现实中快递相似,通过一个一个中转站,到达离你最近的中转站,而后送到你房间。
而后咱们来看一看实际的IP地址,实际的IP地址(IPV4)是一串32比特的数字,按照8比特(一个字节)为一组分红4组,分别用十进制表示,而后再用圆点隔开。可是咱们要明确知道目的地,必须先知道网络号,肯定哪个子网,而后再经过主机号肯定哪一台计算机,好比一个网吧有100台计算机,首先咱们得经过网络号肯定究竟是哪一个网吧,而后再经过主机号肯定是哪台机器,可是IP地址有4段,到底哪几段表明网络号,哪几段表明主机号呢?
因而咱们须要另一些附加信息来确认IP地址的哪一个部分是网络号,哪一个部分是主机号,这个附加信息就是 子网掩码 子网掩码的格式和IP同样,其中每段所有为1的为网络号,为0的表示主机号,子网掩码的每一段要不所有为1要不所有为0,也就是十进制的255和0,用十进制的来表示子网掩码和IP地址的关系:
IP地址 : 10 . 1. 2.3 10 . 1. 2.3
子网掩码: 255.255.255.0 255.255. 0.0
第一个IP地址的网络号为10.1.2 主机号为3
第二个IP地址的网络号为12.1 主机号为2.3
总结:子网掩码的255对应的IP地址部分就是网络号,0对应的就是主机号
固然有两种特殊状况:IP地址的主机号为255表示向子网上的全部设备发送包,即“广播”。
IP地址的主机号为0表明整个子网,其余的状况一律视为某个子网的某一台主机。
复制代码
查询IP地址的方法很是简单,只要询问最近的DNS服务器“www.baidu.com”的IP地址是什么就能够了,DNS服务器会回答说“该域名的IP地址为xxx.xxx.xxx.xxx”。-----DNS:Domain Name System,域名服务系统
那客户端是经过什么向DNS查询IP的呢?咱们将负责域名解析这一操做的叫作解析器,解析器其实是一段程序,它包含在操做系统的Socket库中。
咱们先来简单了解如下Socket库。首先,库就是一堆通用程序组件的集合,iOS中的UIKIT也是一个库。Socket库也是一种库,其中包含的程序可让其余的应用程序调用操做系统的网络功能(还包含不少发送和接收数据的程序组件)。就像iOS使用AFN框架就能够发起网络请求相似的。
解析器的用法很是简单,只要写上解析器的程序名称(OC中称为一个方法或者一个函数)“gethostbyname”而后参数带上对应的域名就能够了,好比:
IP = gethostbyname("www.baidu.com");
复制代码
顺带一提,向DNS服务器发送查询消息是,咱们固然也须要知道DNS服务器的IP地址。这个地址是咱们事先就设置好的,windows电脑在IPV4\IPV6的属性里面设置,mac电脑在网络的高级里面设置。以下图:
解析器向DNS服务器发送的查询消息包含如下3种信息:
DNS服务器上事先就保存了前面这三种信息对应的记录数据,当收到客户端的查询消息时,DNS服务器会从已有的记录种查找域名、Class、类型所有匹配的记录并向客户端返回响应消息(IP地址)。
看起来挺简单的,可是实际上还有互联网中存在不可胜数的域名,将这些信息所有保存在一台DNS服务器中是不可能的,所以必定会出如今某台DNS查询不到信息的状况,因而咱们来看一看此时DNS服务器是如何工做的。
答案很简单,就是将信息分布到多台DNS服务器中,而后这些DNS相互配合接力,从而查询到须要的信息。首先DNS服务器中的全部信息都是按照域名以分层次的结构来保存的,可能不是很好理解,因而咱们先来简单介绍域名的层次,域名的层次越靠近右边层次越高,如:www.baidu.com,这个若是按照公司的的组织结构来讲,大概就是com公司的baidu部门的www。以域来讲顶层是com域,而后是baidu域,而后是www。com还有上一层域,咱们叫根域,根域通常省略,用.号代替如:“www.baidu.com.”。
因而根据域名的层次,咱们来分析DNS服务器的层次。按照一个原则:负责管理下级域的DNS服务器的IP地址保存在它的上级DNS服务器中。也就是说,负责管理www.baidu.com这个域的DNS服务器的IP地址保存在管理baidu.com这个DNS服务器中,而管理baidu.com域的DNS服务器IP地址保存在管理com域的服务器中。注意⚠️这里的IP地址是指DNS服务器的IP地址,不是域名的IP地址。这样咱们就能够经过上级DNS查询下级DNS的IP地址,也就能够向下级DNS服务器发送查询请求了。还有一项工做就是根域的DNS服务器信息保存在互联网中全部DNS服务器中,这样一来任何DNS服务器均可以查询根域的DNS服务器。
客户端会先访问离得最近的DNS服务器(也就是客户端在TCP/IP设置中填写的DNS服务器IP地址),若是这台DNS服务器中没有存放咱们须要的域名的IP地址,最近的这台DNS服务器会直接去根域的DNS查找,根域中也没有则向下查询,例如www.baidu.com,先向跟域查询,没找到则向com的DNS服务器查询,而后是baidu.com,一直向下知道查询到了为止。
可是每次都查询会很麻烦且耗时,因而DNS服务器有一个缓存功能,能够记住每次查询的域名,缓存中的信息为了防止有改变而查询到老的数据,会设置一个有效期,过时则会删除。固然客户端也能够在hosts配置域名对应的IP地址
因而经过DNS服务器查询IP地址的步骤为:首先在客户端的hosts配置中去取域名对应的IP地址,没取到则向最近的DNS服务器查询IP地址,查询时会先从DNS服务器缓存中取,没取到再到DNS服务器的记录表中查找,若是最近的DNS服务器没有找到,则直接到根域的DNS服务器查找,没找到再根据前面域的层级所述一级一级向下查找。
经过调用gethostbyname的程序组件(也就是解析器)去DNS获取IP地址以后,则进入了数据的收发(也就是传输)阶段,数据的收发大体总结为如下4个阶段:
用伪代码的形式来表述一下对应阶段的情形:
发送阶段
...
<内存地址> = gethostbyname("www.baidu.com"); // 向DNS查询IP地址
...
<描述符> = socket(<使用IPv4>, <流模式>, ...); // 一、建立套接字
...
connect(<描述符>, <服务器的IP地址以及端口号>, ...) // 二、链接
...
write(<描述符>, <发送数据>, <发送的数据长度>); // 三、发送
...
接收阶段
<接收数据长度> = read(<描述符>, <接收缓冲区>, ...); // 三、接收
...
close(<描述符>); // 四、断开
复制代码
首先了解一下协议栈是什么!协议栈与浏览器不一样,它是属于操做系统的部分,工做内容也是从表面上看不到的。先分析一下协议栈的结构:
再下来是操做系统内部,其中包括协议栈:
再下面就是网卡驱动程序负责控制网卡硬件,最下面的网卡则负责完成实际的收发操做,也就是对网线中的信号执行发送和接收操做。
在协议栈内部有一块用于存放控制信息的内存空间,这里记录了用于控制通讯操做的控制信息,例如通讯对象的IP地址、端口号、通讯操做的进行状态等。原本套接字就只是一个概念,并不存在实体,若是必定要赋予它一个实体,那么存放这些控制信息的内存空间就是套接字的实体。
协议栈在执行操做时须要参阅这些控制信息。例如,在发送数据时,须要看一看套接字中的通讯对象IP地址和端口号。或者在发送数据以后,在等待响应消息时,不能一直等,因而一段时间后须要从新发送丢失的数据。为此,套接字中必需要记录是否已经收发响应以及发送数据后通过了多长时间。
上面讲的太抽象了,可是咱们知道套接字就是一些控制信息,因此接下来来看看实际的套接字,在终端使用netstat命令显示套接字的内容
总结:套接字的实体就是通讯控制信息,协议栈须要根据这些信息判断下一步行动,这就是套接字的做用。
首先,应用程序会调用Socket库中的socket程序申请建立套接字,协议栈根据根据申请执行建立套接字操做。建立套接字时,首先分配一个套接字所需的内存空间,而后向写入初始状态。
接下来,须要将表示这个套接字的描述符告知应用程序。描述符至关于用来区分协议栈中多个套接字的号码牌,也能够理解为某个套接字的编号,在建立套接字时会返回这个描述符。也能够看一下第二节开头的伪代码找到描述符。
收到描述符后,应用程序在向协议栈进行收发数据委托时就要提供这个描述符,到这里,套接字就已经建立完成了。
首先咱们要明白“链接”是什么意思,咱们不可能说拿个网线连起来就是链接,由于网线一直都是链接的。那么“链接”究竟是什么意思呢?链接实际上就是通讯双方交换控制信息,在套接字中记录这些必要信息并准备数据收发的一连串操做。是否是感受很抽象?接下来详细说说“链接”究竟是什么意思!
套接字刚建立完成的时候,里面没有存听任何数据,也不知道通讯对象是谁,这时候就算应用程序要发消息,协议栈也不知道发给谁,所以咱们须要把IP地址和端口号等信息告诉协议栈,这是链接操做之一。
在服务器端也会建立套接字,服务器程序通常会在系统启动时就建立套接字,并等待客户端链接。可是服务器也不知道通讯对象是谁,因而咱们须要让客户端向服务器告知必要的信息,好比客户端的IP地址和端口号。可见客户端向服务器传达通讯的请求也是链接的操做之一。
此外,当执行数据收发操做时,咱们还须要一块用来临时存放要收发数据的缓冲区,它也是在链接操做的过程当中分配的。这些就是“链接”这个词代码的具体含义。
或者你也能够理解为:链接就是客户端和服务器确认好通讯双方身份的过程,其中IP地址确认通讯双方的地址,端口号确认IP地址对应的计算机中的哪个套接字。
以前咱们所说的控制信息,大概能够分为两类:
第一类是客户端和服务器联络时交换的控制信息。这些信息不只链接时须要,包括数据收发和断开链接在内,整个通讯过程都须要。那么这些控制信息会经过什么样的形式传输呢?它会放在TCP头部,一个数据包的表现形式大概是这样:
字段名称 | 长度(比特) | 含义 |
---|---|---|
发送方端口号 | 16 | 发送网络包的程序的端口号 |
接收方的端口号 | 16 | 网络包的接收方程序端口号 |
序号 | 32 | 发送方告知接收方该网络发送的数据至关于全部发送数据的第几个字节 |
ACK号 | 32 | 接收方告知发送方接收方已经收到全部数据的第几个字节。ACK是acknowledge的缩写 |
数据偏移量 | 4 | 表示数据部分的起始位置,也能够认为表示头部的长度 |
保留 | 6 | 该字段保留,如今未使用 |
控制位 | 6 | 该字段中的每一个比特分别表示如下通讯控制含义:ACK:表示接收数据序号(ACK号)字段有效,通常表示数据已经被接收方收到;SYN:发送方和接收方互相确认的序号,表示链接操做;FIN:表示断开链接;还有3个不经常使用先省略 |
窗口 | 16 | 接收方告知发送方窗口大小(即无须等待确承认一块儿发送的数据量)比较抽象没事,后面会讲解 |
校验和 | 16 | 用来检查是否出错 |
紧急指针 | 16 | 表示应紧急处理的数据位置 |
可选字段 | 可变长度 | 能够添加的字段,但除了链接通常不多使用 |
以上的控制信息咱们见得比较多且不多解释的应该就是控制位,由于咱们常常在TCP3次握手中看到,如今你知道他们是什么意思了么?
第二类就是保存在套接字中,用来控制协议栈操做的信息。以前已经讲过了这些信息保存在协议栈中的套接字内存空间内。应用程序传递来的信息以及从通讯对象接收到的信息都会保存在这里,还有收发操做的执行状态等信息也会保存在这里,协议栈会根据这些信息来执行每一步操做。建立套接字时建立的缓冲区就是临时保存数据的。很差理解能够再回头再看看咱们输入netstat命令时输出的结果。
这个过程是从应用程序调用Socket库中的connect开始。
connect(<描述符>, <服务器的IP地址以及端口号>, ...)
复制代码
首先,客户端先建立一个包含表示开始数据收发操做的控制信息头部,如上的TCP头部表格所示,包含了不少字段。咱们首先关注端口号,经过端口号,客户端的套接字就准确找到服务端的套接字,也就是搞清了哪两个套接字进行通讯。而后咱们将头部中控制位的SYN设置为1,能够认为这样就表明链接。此外还须要设置适当的序号(假设设置为X)和端口号,当TCP头部建立好后,TCP模块会将信息传递给IP模块委托它进行发送(由于这是链接的过程,数据块是没有实际数据的)。
而后包经过网络到达服务器,服务器的IP模块会将包传给服务器的TCP模块,而后TCP模块经过收到的包的TCP头部的信息找到端口号对应的套接字,而后套接字会写入相应的信息而且将状态设置为正在链接。而后服务器的TCP模块会返回响应消息,响应消息和客户端同样会将控制位的SYN设置为1,同时会将控制位的ACK也设置为1,并将头部字段的ACK号设置为X+1,而后再将头部的序号设置为Y。ACK号表示已经收到客户端发过来的数据包。
而后,网络包会返回到客户端,经过客户端的IP模块到达TCP模块,并经过TCP头部信息确认链接操做是否成功。若是控制位的SYN为1则表示链接成功,这时会向套接字中写入服务器的IP地址、端口号等信息,同时还会将状态改成链接完毕。到这里,客户端的操做就已经完成了。
但其实还剩下最后一个步骤,刚才服务器响应时控制位的ACK比特设置为1并将ACK号返回给客户端确认包已经收到,相应的客户端也须要将控制位的ACK比特设置为1并将ACK号设置为Y+1发回服务器,告诉刚刚服务器发的相应包已经收到,到这里链接操做才算完成。ACK号与控制位的ACK比特是两个东西,详情看如下上面TCP头部表格,关于ACK号和序号不是太理解的不要紧,在收发数据的时候会有详细的解释。
链接以后进入数据收发操做,数据收发操做是从应用程序调用Socket库中的write将要发送的数据交给协议栈开始的。
write(<描述符>, <发送数据>, <发送的数据长度>);
复制代码
首先,协议栈并不关心应用程序传来的数据是什么内容。其次,协议栈并非一收到数据就立刻发送出去,而是会将数据存放在内部的发送缓冲区中,并等待应用程序的下一段数据。由于一次性将多少数据委托给协议栈是应用程序自行决定的,协议栈并不控制这一行为,在这种状况下,若是立刻发送出去就可能会发送大量的小包,致使网络效率降低,所以须要在数据积累到必定量再发送,至于要积累多少数据再发送,不一样种类的版本和操做系统会有所不一样,可是能根据如下两要素来判断:
进行发送操做时会综合考虑以上两个要素以达到平衡。固然应用程序发送数据时也能够指定一些选项,如:直接发送不等待。
若是一个网络包的数据很是大,发送缓冲区的数据会被以MSS为长度进行拆分,拆分出来的每块数据会被单独放进单独的网络包内。
网络包发送出去以后,须要进行确认操做。---这个ACK和控制位的ACK是两个东西。
首先TCP模块在拆分数据时会先算好每一块数据至关于从头开始的第几个字节,接下来发送这个数据时,将算好的字节写在TCP头部的序号中,而后,发送数据的长度也要告知对方,不过这个在接收方能够本身算出来,使用接收到的数据的长度减去头的长度就能获得数据的长度。有了这两个数值咱们就能够知道发送的数据是从整个数据的第几个字节开始,长度是多少了。(序号的初始值是在链接的时候产生一个随机数告知对方的)
经过这些信息,接收方还能检测网络包有没有遗落。例如,上一次接收到第1000字节,那么接下来若是收到序号为1001的包,说明中间没有遗落;但若是收到的包序号为2000,则说明遗漏了1000个字节的数据。若是确认没有遗漏,接收方会将目前为止接收到的数据长度加起来,将这个数值写入TCP头部的ACK号中发送给对方,发送方就能确认接收方到底收到多少数据。例如:发送方发送的包序号为1,长度为1000,当接收方接收到这个数据以后,会返回一个ACK号为10001的包给发送方,发送方收到这个包就知道我以前发的哪一个包你已经确认收到了。
除了在客户端发送数据给服务端以外,服务端也会发送数据给客户端,所以在实际发送中会增长一种与以前相反的情形,服务端发送数据时也须要先计算一个序号,而后将序号的数据一块儿发送给客户端,客户端收到后计算ACK号返回给服务器。
TCP采用ACK号这种方式确认对方是否收到了数据,在获得对方的确认以前,发送过的包都会保存在发送缓冲区。若是对方没有返回某些包对应的ACK号,那么就从新发送这些包。
当客户端在委托协议栈发送请求消息以后,客户端会调用Socket库的read程序来接收相应消息。
<接收数据长度> = read(<描述符>, <接收缓冲区>, ...);
复制代码
而后会经过read转移到协议栈,而后协议栈会检查收到的数据和TCP头部的内容判断数据是否有丢失,若是没问题则返回ACK号给服务区确认收到数据,同时会把数据保存在接收缓冲区中,并将数据按照序号的顺序链接起来还原出原始数据,最后将数据返回给应用程序。
在返回相应消息以后,客户端还能够继续发起下一个请求,这样能够省去再次链接的操做,若是接下来没有请求发送,客户端会调用Socket库中的close程序发起断开操做,断开的操做顺序以下:
这就是断开连接所谓的四次挥手。断开以后就会删除套接字,通常来讲会等待几分钟再删除套接字,为何要等待几分钟呢?留给你们本身思考一下🤔。
在前面讲到TCP模块在执行链接、收发、断开等各阶段时,都须要委托IP模块将数据封包发送给通讯对象。接下来咱们要看看IP模块是如何将包发送给对方的。
以前在2.2.1已经说过,包是由头部和数据两部分构成,头部包含了目的地址等控制信息,在前面咱们讲了TCP头部的内容,从2.2.1的图上能够看到在TCP头的前面还有一个IP头部,在那张图里面将IP头部表述为以太网和IP的控制信息,实际上他们并非一个头而是两个不一样的头,分别为MAC头部与IP头部。而IP模块则负责给TCP委托过来的数据包添加MAC头以及IP头。
因而实际上一个数据包的正确表达形式是这样的:
IP模块接受TCP模块的委托负责包的收发工做,它会生成IP头部并附加在TCP头部前面。下表表示IP头部的字段
字段名称 | 长度(比特) | 含义 |
---|---|---|
版本号 | 4 | IP协议版本号,4或6 |
头部长度 | 4 | IP头部长度。可选字段可致使长度变化,所以这里须要指定长度 |
服务类型(ToS) | 8 | 表示包传输的优先级 |
总长度 | 16 | 表示IP消息的总长度 |
ID号 | 16 | 用于识别包的编号,通常为包的序列号。若是一个包被IP分片,则全部分片都拥有相同ID |
标识(Flag) | 3 | 该字段有3各比特,其中2各比特有效,分别表明是否容许分片以及当前包是否为分片包 |
分片偏移量 | 13 | 表示当前包的内容为整个IP消息的第几个字节开始的内容 |
生存时间(TTL) | 8 | 表示包生存的时间,这是为了不网络出现回环时一个包永远在网络中打转。每通过一个路由器,这个值就会减1,减到0时这个包就会被丢弃 |
协议号 | 8 | 协议号表示的协议类型:TCP:06;UDP:11;ICMP:01 |
头部校验和 | 16 | 用于检查错误,如今已不使用 |
发送方IP地址 | 32 | 网络包发送方的IP地址 |
接收方IP地址 | 32 | 网络包接收方的IP地址 |
可选字段 | 可变长度 | 能够添加的字段,通常不多使用 |
表中最重要的就是IP地址,接收方的IP表示这个包应该发到哪里去。这个地址是应用程序在执行链接操做时传给TCP模块而后再由TCP模块传给IP模块的。发送方的IP地址填写使用网卡中的IP地址。
生成IP头部以后,接下来IP模块还须要在IP头部前加上MAC头部。MAC头部字段:
字段名称 | 长度(比特) | 含义 |
---|---|---|
接收方MAC地址 | 48 | 网络包接收方的MAC地址,在局域网中使用这一地址来传输网络包 |
发送方的MAC地址 | 48 | 网络包发送方的MAC地址,接收方经过它来判断是谁发送了这个包 |
以太类型 | 16 | 使用的协议类型。通常在TCP/Ip通讯中只使用0800(IP协议)和0806(ARP协议--前面有讲这个哦,在2.1.1) |
IP头部的IP地址能够用于判断包发送到哪里。那MAC地址是用来干吗的呢?
MAC地址是在以太网中使用的,以太网在判断目的地时和TCP/IP的方式不一样,所以必须采用相匹配的方式才能在以太网中将包发送到目的地,MAC头部就是干这个用的。那什么是以太网呢?
以太网是一种为多台计算机可以彼此自由和廉价地相互通讯而设计的通讯技术,或者能够说以太网是互联网的一个子集,以太网能够理解为是一种局域网,只能链接附近的设备,这种网络的本质实际上就是一根网线。生活化一点,以太网就是把你家的电脑,笔记本链接到猫上,而后再经过猫链接到因特网上去,这样你才能和国外的朋友乔布斯聊天。所以,你家的电脑,笔记本和猫就组成了一个以太网。因此你知道为何接收方的IP地址不能在以太网使用了吗?由于这个IP地址是针对整个互联网的,而以太网只是互联网的一部分,因此这个IP地址在以太网不适用。
当一台计算机发送信号时,信号就会经过网线流过整个以太网,最终到达全部设备。不过咱们没法判断信号是发给谁的,所以要在信号开头加上接受者的地址,也就是MAC头部的接收方MAC地址。好比计算机发一个请求,这个请求首先要到达路由器,那么到达哪一个路由器呢?因而MAC地址就是告诉网卡消息应该到达这个路由器。相对的MAC头部发送方的MAC地址是从哪来的?这个MAC地址是在网卡生产时写入ROM里的,只要读取这个值就能够了。
那么问题又来了,接收方的MAC地址咱们又是怎么知道的?
这里就须要使用到以前所说的ARP地址解析协议。在以太网中,有一种叫作广播的方法,能够把包发给同一以太网中的全部设备。ARP就是用广播的方式实现的。ARP发送一个广播问“XX这个IP地址是谁的?请把MAC地址告诉我”,而后就有设备会回答“这个IP地址是个人,个人MAC地址是XXX”,因而你就把这个XXX写到MAC头部发送!固然为了节省每次都查询的时间,ARP也有相应的缓存,首先会从缓存中取,为了防止地址变换,缓存通常会在几分钟后删除。
那么问题又来了,ARP发广播时问“XX这个IP地址是谁的”中的IP地址是哪来的?这个IP地址必定不是咱们发送消息时填的服务器的IP地址吧,由于ARP查询的只是子网内的目的地MAC地址啊。
首先,发送方将包的目的地也就是要访问的服务器的IP地址写入IP头,协议栈IP模块有一张IP协议的表,在这个表中找到相匹配的条目的IP地址就能够了,而后经过这个IP地址经过ARP查询路由器的MAC地址写入MAC头部,网络包在传输过程当中会通过集线器,集线器是根据以太网协议工做的设备。为了判断包接下来应该向什么地方传输,集线器里有一张表(用于以太网协议的表),能够根据MAC头部记录的目的地查找对应的传输方向,将包发送到路由器,而后路由器中又有一张IP协议的表(路由表),可根据这张表以及IP头部中记录的目的地信息查找出接下来应该发往哪一个路由器(IP地址),接下来又经过ARP查询MAC地址.....一直进行下去....到达目的地
IP模块生成的网络包只是存放在内存中的一串数组信息,没有办法直接发送给对方,所以咱们须要将数字信息转换为电信号或光信号才能在网线上传输,这才是真正的数据收发过程,负责这一执行操做的是网卡,这个操做须要网卡驱动程序和网卡一块儿完成。
网卡驱动从IP模块获取包以后,会将其复制到网卡内的缓冲区,而后会传给网卡内部的MAC模块,MAC模块会在包的开头加上报头和起始帧分界符,在末尾加上用于检测错误的FCS(帧校验序列),网卡发出去的包就是这个形式:
发送包以后,咱们假设web服务器返回了一个网络包,到达了网卡,网卡首先会根据报头和起始帧分界符开始提取网络包的数据,并使用末尾的FCS来检查包传输过程当中是否有数据错误。而后根据MAC头部的以太类型来交给对应的协议栈,好比这里的以太类型会是0800(IP协议,这个在2.5.2MAC头的表里面咱们有说过的)。
接下来IP模块会进行工做,首先是检查IP头部确认格式是否正确,格式没问题下一步就是查看接收方的IP地址是不是本身的地址,若是不是本身的地址则会经过ICMP消息将错误告知发送方(ICMP在文章的2.1.1有简单说明)。
若是IP地址正确,这个包将会被接收下来,这时候还须要完成另外一项工做。IP协议有一个叫分片的功能(2.5.1的表格内的标识字段)。简单来讲,网线和局域网只能传输小包,所以须要将大的包切分红多个小包。若是包是通过分片的,IP模块会将其暂存在内部的内存空间中,等待IP头部中具备相同ID所谓包所有到达(借助分片偏移量字段),而后IP模块会将他们还原成原始的包,这个操做叫分片重组。
到这里IP模块工做就结束了,接下来包会交给TCP模块。TCP会根据IP头部中的接收方和发送方的IP地址,以及TCP头部中的接收方和发送方的端口号来查找对应的套接字(2.1.2有说明)。找到对应的套接字后,就能够根据套接字中记录的通讯状态,执行相应的操做了。例如,若是包的内容是应用程序数据,则设置ACK号返回确认接收的包(2.3有说明),并将数据放入缓冲区,等待应用程序来读取;若是是创建链接或者断开链接的控制包,则返回相应的响应控制包,并告知应用程序创建和断开链接(2.2.2有说明)。
而后响应包就到达了应用程序,这个过程也就告一段落了。
大多数的应用程序都相以前介绍的同样使用TCP协议来收发数据,固然也有例外,向NDS服务器查询IP地址的时候就是使用的UDP协议。下面简单介绍一下UDP协议。
先来理解如下为何TCP要设计得如此复杂?由于咱们须要将数据高效且可靠的发送给对方,为了实现可靠性,咱们就须要确认对方是否收到咱们发送的数据,若是没有收到还须要再发一遍。为了实现高效的传输,咱们要避免重发已经送达的包,而是只重发那些出错的或者未送达的包。TCP之因此复杂就是为了实现这一点。
UDP没有TCP的接收确认、窗口等机制,所以在收发数据以前也不须要交换控制信息,也就是说不须要创建和断开链接的步骤,只要在从应用程序获取的数据前面加上UDP头部,而后交给IP进行发送就能够了。接收也很简单,只要根据IP头部中接收方和发送方的IP地址,以及UDP头部中的端口号找到对应的套接字并将数据交给相应的应用程序便可,除此以外UDP协议没有其余功能了,遇到错误和丢包也一律无论。如下是UDP头部的控制信息:
字段名称 | 长度(比特) | 含义 |
---|---|---|
发送方端口号 | 16 | 网络包发送的端口号 |
接收方端口号 | 16 | 网络包接收的端口号 |
数据长度 | 16 | UDP头部后面数据的长度 |
校验和 | 16 | 用于校验错误 |
另一个常见的UDP使用场景就是发送音频和视频数据的时候。音视频数据必须在规定的时间送达,一旦晚了,就会错过播放的时机,就会形成声音和图像卡顿。一旦错过了播放时机,使用TCP重发包也是没用的,由于已经卡顿了,这是没法挽回的。此外,音视频数据中缺乏了某些包并不会产生严重问题2,只会产生一些失帧或者卡顿而已,通常均可以接受。因此使用UDP发送数据效率会更高。
至此,咱们探索网络是怎样链接的客户端部分说得差很少了,可能后面有时间会再总结一下网络包如何通过集线器、交换机、路由器等设备最终到达互联网,也可能会总结一下服务器端的接收状况(做为客户端开发也能够适当的了解一下)。本篇文章的内容,绝大部分来自同名书本《网络是怎样链接的》,想更加详细了解能够阅读此书。
附上文章前面几个问题的解答:
一、如何正确分析一个URL: 文内1.1
二、DNS如何工做的:文内1.3
三、IP地址、子网掩码:文内1.3.1
四、套接字、端口号:文内2.2; 文内2.1.2
五、Socket、socket:文内1.3.2 ;文内2.1.3
六、MAC地址:文内2.5.2
七、协议栈:文内2.1.1
复制代码
再留几个小问题给你们思考(书本上的题),文内都讲解过的:
一、表示网络包收件人的接收方IP地址是位于IP头部仍是TCP头部中呢?
二、端口号用来指定服务器程序的种类,它位于TCP头部仍是IP头部中呢?
三、对包是否正确送达进行确认的是TCP仍是IP?
四、根据IP地址查询MAC地址的机制叫什么?
复制代码
立刻就要放假回家过年了。最后祝你们假期愉快~新年快乐😄新年一块儿学习、一块儿成长、一块儿进步!