网络原理知识

这部分知识我主要是给大家介绍TCP/IP协议这里的关键协议

一、自定义协议

1.为什么要自定义协议?

因为不同的应用程序解决的业务场景是错综复杂的,比如遇到一些特例情况就需要自定义协议.因此,很难有一个通用的协议满足所有的业务需求.

这里说一下,业务是一个公司的命脉,在公司中业务远比技术要重要!!!

2.如何自定义协议

1)结合需求,分析清楚,请求响应(服务器/客户端之间)要传递哪些信息.

比如:点外卖

我们查看外卖列表

请求:外卖列表

你当前的位置,你的身份信息(根据你的身份信息进行食品的推荐)

响应:一个列表

列表中要有商家信息(名称,图片,距离,位置,简介)

再比如,我们要指定搜索某个店铺的名字

请求:店铺的名字/id

响应:一个列表,列表中要有店铺的信息(名字,图片,价格,简介,口味......)

2)明确传递的信息以什么样的格式进行组织

比如我们使用最简单最朴素的文本格式进行传输:

请求:用户id;地址\n

响应:商家名字;商家图片地址;商家简介;商家地址\n

我们怎么构造这个传输格式,服务器就怎么解析.但这个组织格式和我们的业务没有太大的关系(不用自定义了,可以按照某种指定的格式),所以说一些大佬就发明了一些格式的模板:
  1. XML:

标签化的组织形式,使用标签来表示键值对,以及树形结构

如:

<request>(开始标签)

</request>(结束标签)

这两个标签必须要成对出现,中间的内容可以使字符串也可以是数字,嵌套的放在标签的里面

如:

<request>

<userId>123456</userId>

<position>北京市......</position>

</request>

HTML就是XML的特殊形式

2.json

在2010年之前XML很流行,但之后发现它很啰嗦,所以就发明了json,出自js这个语言.

如:

请求:

{

userId:123456,

position:"北京市.......",

}

响应:

{

name:".......",

show:"很好吃",

}

这种格式明显比XML简单,所以json就逐渐取代了XML.

上述的XML和json是采用了文本的组织形式,有点是好观察,但是缺点是有很多多余的标签占据了过多的IO.效率上不去.

因此发明了protobuffer(谷歌)

3.protobuffer

这是一种二进制的数据传输形式.

特点:效率高,缺点:肉眼观察不了.

以及最为经典的传输数据组织形式:HTTP.(后面会讲)

二、传输层协议

传输层虽然是操作系统已经实现好了的,但程序员要网络编程调用的socket api实际上就是属于传输层的部分.还记得五元组吗?只关注起点和终点的特征在这里鲜明的体现着.

1.端口号

比如,mysql数据库的默认端口号就是3306.

端口号起到的作用是在一台计算机上明确的区分不同的应用程序.

要求:在一台主机上,一个端口号不能被多个进程所绑定.

端口号是传输层的概念,TCP和UDP中都包含原端口和目的端口.

构成:端口号是由2个字节16个bit位组成的,所以说端口号的范围:0--->65535.

以上信息需要记忆!!!

但是如果我们字节写程序并且设定端口号,需要从1024开始选取.

因为0->1023的端口号,被称为"知名端口号/具名端口号",这些端口号已经分配给了一些知名并且广泛使用的应用程序了

2.UDP

之前我们讲过也写过,UDP的性质是:无连接、不可靠传输、面向字节报、全双工.

1)UDP报文结构

这张是广大教科书上的UDO报文结构:

但实际上不应该长这个样子,这样只是为了排版方便.

实际上应该是这样:

报头和载荷的关系,就是相当于车厢和车头一样:载荷中装有完整的应用层数据报.

因为这是在传输层,所以说系统会接收到应用层的数据报,然后对它进行再封装,加上UDP报头然后传给网络层.

这里的的UDP报头里装有一些特定的属性,就携带了一些重要的信息,对于UDP来说,报头一共是8个字节,分成了4个部分(每个部分2个字节):

我们之前说过:进行一次网络通信,就要涉及到一次五元组:

原IP、目的IP、原端口、目的端口、协议类型

在UDP报头中原端口和目的端口各占两个字节,所以说范围才是0->65535.

而UDP报文长度是两个字节,换算单位就是64KB(其实也就是货车的"载重量",其实很小)

在我们如今的网络通信中,一个照片就几个MB,那我们要如何解决这个问题呢?

  1. 在应用层拆分成多个报进行传输.

  1. 不用UDP改用TCP进行数据的传输.(一般都是使用第二个)

还有一个就是校验和:其目的在与检验传输数据的是否正确.

在网络上,数据传输的过程中,时常发生一种叫做"比特翻转"的错误,也就是:1->0;0->1.

为了检验这种错误,就引入了校验和进行检验:就是针对要传输的数据,进行数学计算,得到一个比较短的结果,在传输之前和传输之后分别计算一次,如果数据内容一定,那么校验和也就一定.

生成校验和的算法有很多,其中比较知名的几个:
  1. CRC(就是进行简单的循环计算,万一结果同时变动了两个bit位,那么CRC也不会变,错误率高,几乎不用)

  1. MD5(有一系列的书序公式进行复杂的运算)
MD5的特点:

1)定长(不管数有多大,长度也是一定的)

2)冲突概率很小(哪怕只变动一bit位,MD5变化都会很大,解决了上述CRC的问题,准确率高)

3)不可逆(只能通过原始数据计算MD5,但很难通过MD5计算于是数据)

MD5的作用:

1)校验和

2)hash值

3)加密领域

  1. SHA1

3.TCP

TCP的性质是:有连接、可靠传输、面向字节流、全双工.在前面的代码编写中都曾验证过.

1)报文格式

首先是第一层:16位原/目的端口号和UDP一样.
4位首部长度:其实就是报头,只是翻译不一样(TCP报文=TCP报头(又称首部,英文header)+TCP载荷(英文payload/body)),因为TCP的报头可变的,不是像UDP一样是固定的.所以说我们整一个首部长度是为了确定选项部分的长度.但是,4个bit位是0->15,而前面的固定部分就有20字节了?如何表示呢?所以这里我们的首部长度的单位这个"位"不是1字节,而是4字节.(就是说实际上的结果应该是可见结果乘以4)

保留:英文resevered,意思就是现在不用,但是保留以后再用.此处的TCP保留6位是为了以后的扩容做准备.(解决了UDP只有64KB的这个缺点)

选项:英文option =>optional(可选的,可有可无的)此处的选项对于TCP报文的一些选项进行解释说明的,我们可以计算一下,一行是4个字节,而在选项前面的内容固定共有20个字节,因此首部长度-20字节,就是选项部分的长度.

剩下的之后再说.

2)TCP工作机制

TCP是一个复杂的协议,其中有很多个机制,这里我们主要讨论其中的10个核心机制.

1.应答机制

我们都知道TCP的性质其中有一条:可靠传输.这个可靠的意思不是一定能把消息传过去,是让你知道传没传过去.就是如果传输失败后会告知你传输失败.

而应答机制就是实现我们可靠传输的最为核心的一个机制

在这个情景中,"不可以!!!我在用"就是应答报文,也叫作ack(acknowledge的前三个字母)TCP实现可靠传输就是通过这个应答报文实现的.

如果情景再复杂一点:
但如果在网络上有可能会出现"后发先至"的情况.

如果出现以上两种情况,那么在互联网的沟通中,我们的交流就出现了语义的错乱了,此时语句就出现歧义了.

出现原因:
举个例子:(接亲)

新郎要把新娘迎回就需要组织一大串的车队,而这些车队有虽然在出发是按顺序出发的,但是速度有快又慢,而且走的路段也不同,那么到达新娘家的顺序也就可能和出发时的顺序相差很大了.

在网络通信中,那句"同学能把电脑借我"和"那能借我纸和笔用一下吗"可能走的都是不同路线,比如一个进过了路由器,而另一个走的交换机,而且二者的"路况"也不太一样,所以说到达的顺序也就不一样.

所以说,网络中传输信息"后发先至"的情况是客观存在的,不可避免,在信息传输中就需要考虑如何解决这种歧义.

解决方法:只要把传输的信息和应答报文都编号就可以了!!!

这里我们给传输信息取的名字1、2就被称为序号,而我们为了回答信息而取的应答1、应答2就是确认序号.

而这个序号和确认序号,就在报文结构中有所提及:

任何一条数据(包含应答报文),都是有序号的;确认序号,则是只有应答报文有.(普通报文包含应答报文无意义)

而这一条报文是不是应答报文取决于这个标志位:

而这个sck标志位为1则是应答报文,而如果为0就不是应答报文.

如果A向B发送一串信息,假如说有1000个字节,那么每个字节从1到1000进行编号,而报文中只写1就可以了,如果之后A又发送了一串1000个字节的消息,则这条详细从1001开始编号,并且报文中只需填写1001就可以了!!!

知道第一个字节序号+TCP报文长度,就可以知道每个字节的序号了!!!

出自<图解TCP/IP>很好的书,大家可以去看看.

表示的含义:
  1. 如果应答报文传输的应答报文是1001,则可以说明序号<1000的数据B已经收到了,同样,A向B传输1001-2000的数据,如果B向A传输的应答报文是2001,则可以说明B已经收到了1001-2000的数据了.

  1. A应当从1001继续发送数据.(B向A索要1001的数据)

小结:可靠传输是通过应答机制来实现的,通过应答报文,就可以清楚的确认数据的传输是否成功.引入的序号可以用于解决"后发先至"的问题.

2.超时重传

在确认应答的时候,我们只是讨论了顺利传输的情况.但是如果丢包了呢?

丢包的原因有两种:
  1. 发的数据丢了

  1. 返回的ack丢了

这两种情况都会被认定为丢包,如果过判断系统丢包了,就会引发TCP的重传机制.

就是重发一遍,但如果不是传输数据丢了,而是在路上还没到呢?TCP就引入了一个时间阈值,如果超过这个时间,就会重传.

咱们再来考虑一下第二个图:

1-1000这个数据主机B收到了两回,那如果这个数据是个转账数据呢?

解决方法:

TCP存在一个"接收缓冲区"(操作系统内核中的一段内存),当B的网卡读到数据之后,会先将数据存入"接收缓冲区",再从接收缓冲区中获取读到的数据.(就是类似"阻塞队列"之类的数据结构),然后在这个"接收缓冲区"中依照序号大小排序,并查重,删除相同内容后再进行B的读取,能够保证应用程序的read操作不读取到相同的内容.

相当于是接亲车队开到新娘家门口先进行等待,等所有人都到了后,排序回出发顺序再开进新娘家.

小结:由于超时和重排序机制的存在,只要发现ACK没有按时到达,就会重新传输数据,及时顺序乱了都没事,接收方可以很好的处理数据.(去重和重排序都依赖报头的序号)

总结:可靠传输是TCP传输机制的核心,是通过确认应答+超时重传来体现的,其中确认应答是传输顺利的情况,而超时重传是传输出现问题的情况,这两种机制互相配合共同支撑着TCP传输的可靠性.

3.连接管理(高频考点)

连接:英文是Connection,在网络通信中,连接是在A记录上B的IP与端口号,并且B记录上A的IP和端口号之后,将这部分信息保存在一个数据结构中,这个过程就叫做"建立连接",而连接(Connection)是这个过程的结果.

比如说结婚,不是举行完婚礼算是结婚,而是领证后算是结婚.

同时,A和B分别将储存在数据结构中的双方的IP和端口号,删除了,就叫做断开连接.

(1)建立连接(三次握手)

双方各自要记录双方信息,彼此之间要相互认同.

举个例子:

假如两个人(甲和乙)在打游戏,现在要求连麦,为了确认双方的耳机和麦克风都正常:

此时确认的内容:

甲:无

乙:已知甲的麦克风和乙的耳机都是正常的.

此时确认的内容:

甲:已知乙的麦克风和甲的耳机是正常的.由于之前甲的信息被回复,因此根据甲和乙之间默认的约定,甲也得知了甲的麦克风和乙的耳机都是正常的.

乙:已知甲的麦克风和乙的耳机都是正常的.

此时确认的内容:

甲:已知甲的麦克风和耳机都是正常的,并且乙的麦克风和耳机也都是正常的

乙:已知甲的麦克风和乙的耳机都是正常的.

此时确认的内容:

甲:已知甲的麦克风和耳机都是正常的,并且乙的麦克风和耳机也都是正常的.

乙:已知甲的麦克风和乙的耳机都是正常的,因为刚才乙的问题被回应,所以根据默认的约定,乙得知甲的耳机和乙的麦克风都是正常的.

这是双方都知道了双方的耳机和麦克风都是正常的,都具备着发送以及接收的能力,此时连接建立!!!

这里我们发现,其实图三和图二双方获取的内容都是一样的,所以可以把这两次信息交互合并为一次,那么:

这三次信息交互被称为"三次握手"

"三次握手"的三种作用:
  1. 双方建立对对方的认同.(保存对方的信息)

  1. 检验收信方和发信方的发送和接收能力都是否正常.

  1. 在握手的过程中,双方来协商一些重要的参数.

总结:所谓的三次握手,其实是四次信息交互,通信双方都要各自向对方发送一个"建立连接"的请求,同时还要回应对方一个ack,这里其实有四次信息交互,但是中间两次可以合并为一次交互,因此被称为"三次握手".

这里的SYN全称是synchronize,是"同步"的意思.这里指同步报文段.

就是这个,同步标志位.等于上面的"喂喂喂,听得到吗"属于同步报文段(SYN),而"听得到,那你听得到我说话吗?"既是同步报文段(SYN),也是应答报文(ACK),最后的"听得到"是应答报文(ACK).

还有

这里指的是TCP的状态,类似之前讲的线程状态,但是很复杂,需要认识几个常见的状态:
建立连接状态:
  1. LISTEN

服务器的状态,表示服务器已经准备就绪了,随时可以建立连接,相当于手机开机,信号良好,随时有人可以打电话.

  1. ESTABUSHED

指客户端和服务器都有了,连接建立完成,接下来就可以正常通信了,相当于电话打过去对方接通了,现在可以随时说话了!!!

接下来

这部分是描述了TCP和socket api之间的关系.(此处不关注)因为此处是C语言版本的.

(2)断开连接(四次挥手)

这里再举个例子:(一对情侣要分手)

为什么是四次挥手,而不是跟三次握手一样是三次挥手呢?中间的两次可不可以合并呢?

答案:不完全可以!!!

因为三次握手中间的两次可以合并是因为它俩是同一时机执行的,具体来说三次握手的这三次交互,是纯内核中完成(应用程序感知不到,也干预不了)服务器内核在收到syn之后,就会立即发送ack,之后也会立即发送syn.

而四次挥手则不是:

第一个fin的发起,不是由内核控制的,而是由应用程序,调用socket的close()方法(或者是进程退出),才会出发fin,而ack则是由内核控制的,在收到fin之后会立即发送ack.而第二个fin是由服务器的应用程序执行到了对应的close()方法,才会出发fin,内核和应用程序的close()之间会隔着一个时间(时间的长短由你的代码控制)

还记得上个博客写的服务器代码吗?

程序随着循环的结束而结束,而循环的结束随着这个break的触发而结束,而break由hasNext()判断为false就触发了,hasNext()判断为false是因为读到了EOF(文件结束标志),而EOF是因为内核收到了对方发来的fin数据报从而调用了socket.close方法.

循环结束后,就调用到了下面的那个close()方法,这时就是服务器给客户端发送了fin,就是最后的两次交互.

在上述代码中,相当于是循环一结束就立即close发起了fin,此时ack和fin之间的时间间隔就比较短.此时很有可能系统就把这两个包裹合并成了一个,但是如果间隔时间长了,比如在close之前干了别的事了,

就比如这样:

在结束之前睡了个觉,此时就无法合并成一个了.

小结:所以说我们通常把大多数发生的这四次信息交互,称为"四次挥手".少数的中间两次可以合成一次的情况就自动忽律不计了!!!
大概就是上面那一部分,这里也有两个特别重要的TCP状态:
  1. CLOSE_WAIT(等待关闭)

出现在被动发起断开连接的一方

这里要注意:建立连接一定是客户端主动发起请求,但是断开连接可能是客户端主动发起请求,也可能是服务器主动发起请求.

这个状态就是等待关闭,也就是等待调用close()方法关闭socket.

  1. TIME_WAIT

出现在主动发起断开连接的一方,假设是客户端主动发起断开连接,当客户端进入TIME_WAIT状态时,相当于四次挥手已经完成了,只差一步给服务器返回ack了.

此时这里的TIME_WAIT要保持一会儿当前的TCP连接不要立即就释放.(连接还没有断开)

另外,在三次握手和四次挥手的过程中也是存在超时重传的.

如果是最后一个ack丢包了,站在服务器的视角来看,服务器不知道是因为ack丢了,还是自己发的fin丢了,所以统一视为fin丢了,重新进行重传fin操作.

既然服务器可能进行fin的重传,客户端就需要能够针对这个重传的fin进行ack响应.很明显,如果客户端最后一个ack传完后就直接断开连接,这样ack就无法进行了,因此使用TIME_WAIT保留一段时间,是为了能够处理最后一个ack丢包的情况.能够在收到重传的fin后,进行ack的响应.

TIME_WAIT具体保持的时间是2MSL(指的是互联网上两个节点之间,数据传输消耗的最大时间为什么事2MSL? 其实就是传ack+回传fin的时间).

如果2MSL后,客户端还是没有接收到重传的fin,那么就认为上个ack正常到达了!!!

总结:TCP作为一个有连接的协议,就需要建立连接和断开连接,其中建立连接是三次握手,断开连接是四次挥手.终点理解三次握手的意义(3点),重点理解为什么是四次挥手,而不是三次?重点理解fin和ack的传输时机以及TIME_WAIT的意义和作用.

4.滑动窗口

确认应答、超时重传、连接管理都是给TCP的可靠性提供支持的.但是引入了可靠性就会牺牲传输效率.因此UDP没有可靠性所以说它的传输效率更高.但是TCP也在效率上面做了一些补救措施,其中滑动窗口就是其中一个,它降低了确认应答、等待ack消耗的时间.

我们要注意,可靠性与效率本身是矛盾的.(我们的TCP肯定是可靠性是最优先的)

IO操作花费的时间:
  1. 等待

  1. 数据传输(数据拷贝)

大多数的时间都是花在等上面.

这是我们确认应答的机制实现的功能,我们发现在每次发送报文的时候都需要等待ack花费了大量的时间.

而这就是我们的滑动窗口实现的功能:

本质上就是不等待的批量发送一组数据,然后使用一份时间来等待着一组数据的多个ack.

就比如说,我们现在想吃肉夹馍,煎饼.我们可以先去店铺点两份食物,然后一起等待出餐,这样比分批去点两份食物更加节省时间.

我们把不需要等待,最大能够发送的数据的最大的量称为"窗口大小".

滑动窗口原理:

滑动窗口不是发完四条然后再一起等待,而是向上图一样,发24条然后等待回应,收到一条回应然后窗口向后"滑动"一条.(如上面发了1001-5000的数据,然后等待接收2001的ack,在接收到2001的ack后发送5001-6000的数据报,以此类推).

也就是说,前提是接收ack比发数据报要慢.而且发数据报和接收ack是相互独立的,而且发送下一条数据报的条件是接收到窗口第一条数据报的ack.就像向前滑动了一个窗口一样,所以被称为"滑动窗口".

但如果再上述情况下,丢包了怎么办呢?

丢包大概分为两类:
  1. ack丢了

根据ack的机制,我们发现这种情况不需要过多处理!!!

因为我们发现,根据应答序号的定义,如上图,如果1001的ack丢包了,只要我们的2001ack能够顺利到达,就说明1-1000以及1001-2000的数据也就顺利到达了,也就不需要考虑之前的1001ack丢包的情况了.

  1. 数据丢了

如上图,1-1000的数据和1001的ack都顺利到达了,但是1001-2000的数据却丢包了,则是B给A的ack仍然是1001.(此时和A发送的数据报是什么序号的没什么关系了),大概的意思就是B主机在向A主机索要1001开头的数据报,当主机A再向主机B发送2001-3000、3001-4000、4001-5000、5001-6000、6001-7000主机B在传给主机A的ack全部都是1001,意思依然是告诉主机A 1001开头的数据报丢包了,让它重新传,此时主机A重传1001-2000的数据,主机B确认收到后才开始检验2001到7000的数据,发现也收到后,才会发送7001的ack代表之前的都收到了.

(以上重点理解)

上述重传数据的形式,起了个名字,叫做"快速重传".

这种快速重传可以说是超时重传在滑动窗口模式下的一个变形.

如果当前传输数据密集,则按照"快速重传"的方式进行重传;

如果当前传输数据稀疏,则按照之前的"超时重传"的方式进行重传.

5.流量控制

这是一种干预发送的窗口大小的机制,窗口越大,传输效率就越高(一份时间等的ack就越多),但是窗口不能无限大.

会遇上这几种情况:

  1. 完全不等ack,会影响TCP的可靠性.

  1. 窗口太大,会消过多的系统资源.

  1. 发送速度太快,接收方处理不过来.

也就是说,发送方的处理速度,不能超过接收方的处理能力.

所以说如何衡量接收方的处理能力,这时就用到了流量控制:

这里我们利用查看缓冲区的剩余大小来衡量接收方的处理能力.

每次A给B发送数据,B就需要算一下接收缓冲区的剩余空间还有多少,然后通过ack将这个值还给A,A再通过这个值来决定接下来发送的速率是多少.(窗口大小是多少)
我们还记得再TCP的报头结构中就有一个窗口大小:

报文是ack的时候才能存入16位窗口大小

这里的16位不是说窗口大小最大是64KB,在选项中有一个"窗口扩展因子"能够进一步扩大窗口大小.(就是说窗口扩展因子里写的是几,就将数据向右移几位,如64KB<<2=> 256KB)

由于接收缓冲区剩余空间都是在不断变化的,所以说,每次返回的ack的窗口大小都是在不断变化的,发送方也是在动态调整的.

这张图具体解释了流量控制的过程:

当窗口大小为零的时候,发送方A就会暂时暂停发送数据,但会定期发送一个窗口探测报文这个报文不携带业务数据,只是为了触发ack探查窗口大小.

6.拥塞控制

流量控制和拥塞控制共同决定发送方的窗口大小是多少.

拥塞控制描述的是传输过程中,中间节点的处理能力.

也就是说前面考虑A的发送速率只是考虑了B的处理能力,而没有考虑中间节点的的处理能力.

网络信息传输是一个"木桶效应",就是信息传输的效率取决于处理能力最差的交换机或者路由器.

那么我们在网络上通信的工程中,如何量化中间交换机或路由器的信息处理能力呢?

制作TCP的人采用了"实验"的方式来逐渐找到一个合适的窗口大小,来逐渐找到一个合适的发送速率.

拥塞窗口就是尝试以多大的窗口大小进行发送.

初始阶段由于窗口大小不是特别的大,所以以指数的形式进行增长.当增长速率达到阈值之后,指数增长就变成了线性增长(增长的前提是不丢包),接下来当传输过程中一但丢包了,说明此时发送的速率接近了极限了,此时就把窗口大小一下缩成很小(初始的窗口大小)的值(接下来继续重复刚才指数增长到线性增长的过程).

随着时间的推移逐渐到达了动态平衡的过程.

好处:这样既解决了问题,又课随着网络的动态变化而变化.

拥塞窗口和流量控制的窗口,共同决定了发送方实际的发送窗口.(拥塞控制和流量控制的较小值)

7.延时应答

这个也是提升效率的机制,作用就是让滑动窗口的窗口大一点,这样就可以让传输的速度快一点.

在接收方能够处理得了的前提下,尽可能的把窗口大小放大一点.(类似电脑的超频)

原理:收到数据之后,不是立即返回ack了,而是等一会儿再返回(延时).等待一会儿后,让应用程序再把接收缓存区中的数据消耗一会儿,这时接收缓存区的剩余部分就变大了,此时再返回ack.

实际上,采用延时应答的方式,就是在滑动窗口下,ack不再是每一条数据都返回了,比如如上图是隔一条数据返回一次ack.

实际上的剩余空间的处理大小,既取决于发送方的发送,又取决于接收方的处理.

8. 捎带应答

这也是提高效率的一种方式.在延时应答的基础上,引入了捎带应答.

一问一答形式是服务器客户端的经典表现形式:

捎带应答:我们已知"现在几点了"是客户端向服务器的请求,而"ack"是内核发出的立即返回的.而"11:42"是业务上的响应,本身他们俩是存在时间差的,但是由于TCP有延时应答,在A等待B的ack的时候,这是业务"11:42"也要发出了,而此时让业务捎上这个ack在一个报中一起派寄给A就可以了(其实就是让两个数据报合并为一个).

本来这两个报是有时间差的,但因为延时应答,让这两个报成为了同一时机,就合并了.(注意:三次握手的那个合并本身就是相同时机,三次握手是一定会合并的,而这个捎带应答是可能会合并,但和四次挥手很像)

9.面向字节流

面向字节流引入了一个麻烦事:粘包问题

因为接收缓冲区,其实就是把收到的多个数据都放在了一起,应用程序read的时候,读到哪里才算是一个完整的应用层数据报呢?

如果这里有三个TCP数据报:

aaaaaaa

bbbbbbb

ccccccc

因为TCP是按照字节流的读法,一次可以读一个字节,也可以读N个字节,所以说上述字节流,可能读到的是8个字节:aaaaaaab、bbbbbbbc、ccccccc读到的根本就不是传输者想要表达的意思.

所以说,在TCP层次,没有在socket api中告诉我们应该读几个字节全凭借程序员自己的代码.

解决方法:
约定号应用层协议规定好数据报与数据报之间的边界即可.
  1. 使用换行符分割数据报:

aaaaaaa\n

bbbbbbb\n

ccccccc\n

2.约定号每个报的长度.

10.TCP中的一些异常情况

在TCP通信中出现的异常情况一般有:

出现了这几种情况TCP都无法继续正常传输了!!!

  1. 程序崩溃了

  1. 电脑关机了(依照电脑的正常步骤进行的关机)

1和2 是一种情况,进程没了对应的TCP也就跟着没了,对应的文件描述符表也就没了,相当于socket.close(),此时内核会正常完成4次挥手.这是一种正常的情况.

3. 电脑掉电了

4. 网线断开了

3和4是一种情况,加入对方掉电了,这种情况显然就来不及4次挥手了.
假设是接收方掉电了:

发送方发送完数据后仍然在等待ack,但等不到,进入超时重传功能,但超过超时重传指定的次数,开始尝试复原TCP连接(传输复位报文段),这个重试也会失败.只能由发送方单方面宣布断开连接.

假设是发送方掉电了:

接收方发现一段时间间没有来数据了,接收方会先等,然后接收方会周期性的给发送方发送一个消息(这个消息叫做"心跳包"),确认一下对方是否还在工作.

讲到这里,TCP协议也就告一段落了.要想深入了解TCP,请去官方参考TCP文档:RFC 9293: Transmission Control Protocol (TCP) (ietf.org)

4. UDP和TCP的适用场景

TCP的优势在于,可靠传输,绝大多数场合都需要考虑可靠传输.

而UDP的优势在于,效率更高,如果默写场景对于性能要求更加苛刻(比如一个机房内,服务器与服务器之间的通信,网络带宽相对充裕,结构相对简单,丢包的可能性比较小)而且UDP有一个天然的优势,就是天然适应广播,就是在一个局域网中的各设备之间的信息交互.

IP地址中有一个特殊的IP,叫做"广播IP".通过UDP往广播IP上发送此时在此局域网中的所有设备都能接收到炬.(就比如说在家中的电视投屏功能,手机先要在局域网中进行广播寻找电视,需要到的电视会在手机的屏幕上出现一个电视列表,选中列表然后手机会通过局域网传输当前视频的地址)

这两个协议在日常生活中用到的很多,但并不是只有这两个协议.

就比如说,王者,使用的是TCP还是UDP呢:它即需要可靠性又需要高效率.当然有的传输层协议专门为了游戏而打造(因为TCP与UDP太极端了),所以研发了专门应对游戏环境的KCP

三、网络层

网络层要完成了两种功能:1.地址管理2.路径选择

  1. IP协议

涉及到的东西:TCP/IP协议栈(总而言之就是TCP+IP)

上图是IP协议的IPv4版本(v4版本的意思)

这个就是当前IP协议的版本:只有4和6两个版本(本博客只介绍IPv4)

描述了IP报头有多长(IP报头是可变长度)选项中有一个功能能够改变报头报文长度,但此处是4个字节

这里说是8位,但实际上只有4位是有效位,这四位只有一位可以是1,其余都得是0.

4位就表示了IP协议的四种状态/四种工作模式.(类似于一个怪的四种模式,开了这个就不能开那个)

四种形态:最小延迟、最大吞吐量、最高可能性、最小成本.

在实际的工作中就可以切换IP的模式来达到最优的效果.这都是站在技术角度上的瓶颈,但一般来说性能瓶颈都是业务角度带来的.

描述了一个IP数据报的长度.(头+载荷),这个长度减去IP报头长度,就能够得到一个完整的TCP/UDP数据报长度.

注意:这里的16位并不是以为着这个IP数据报最大只能运输64KB的数据!!!

确实有这个限制,但是IP自身支持对包的拆分和组装功能(就比如一张床,整件比较大运不过来,我就把它拆成零件运过来)

发送方:把100KB的数据,交给传输层进行封装,传输层交给网络层进行封装,网络层把这个100KB的数据包拆成64KB+36KB再将这两份数据交给数据链路层,由以太网封装成两个数据帧并发送.

接收方:数据链路层根据这两个数据帧进行分用等到两个IP数据包然后交给网络层,网络层针对这两个IP数据包进行解析,并把里面的载荷拼接成一个,交给传输层.

这几个字段都是用来辅助拆包/组包而提供的.

16位标识:同一个数据拆成的数据包标识是相同的.(名牌)

3位标志:结束标志.

13位片偏移:便是了多个包的先后顺序

一个数据包在网络上上能够传输的最大时间.(英文是TTL,这个时间单位不是秒,而是次数,一个包被构造出来,都有一个初始的TTL值,如32或者64,每经过一个交换机或者路由器,TTL就会-1,如果一直减到0了,就认为这个包永远也到不了,就遗弃了)

描述了当时的载荷部分内容是符合当前哪个协议的(TCP/UDP)

此处只需要针对首部进行校验,载荷部分(TCP/UDP数据报部分)已经有校验和了

然后就是IP协议中最重要的部分了:IP地址

IP地址本质上是32位的整数(给计算机看的),但是为了更好的让人们阅读,就把这32位的4个字节分割开,分割成4个部分,每个部分分别使用0-255的十进制进行表示,这种表示形式被称为点分十进制(给人看的).

那么,32位IP地址,世界上只能由42亿9千万的IP地址,但是只要和计算机有关系的都需要使用一个IP地址(空调,冰箱,服务器,计算机等),很显然已经不够我们现在使用的了,那么我们该如何解决这个问题呢?

解决措施:

  1. 动态分配IP地址(就是说比如说美国那边没在使用这时我们这边就可以使用同一个IP地址),但是仍然无法增加IP地址的使用总量,治标不治本.
  1. NAT网络地址转换,本质上就是使用一个IP代表一批设备,也能够大大提升IP地址的利用率.

在NAT的背景下,就把IP地址分成两个大类:

  1. 内网IP(私有IP) 分为这三种: '10.*' '172.16.*-172.31.*' '192.168.*'

  1. 外网IP(公网IP) 剩下的都是公网IP

NAT要求,公网IP必须是唯一的.

私网IP可以在可以在不同的局域网中重复出现.

如果某个私网中的设备想要访问公网中的设备,就需要对应的NAT设备(路由器),把IP地址进行映射,从而完成网络访问.

反之,公网的设备,无法直接访问私网的设备.不同局域网的私网的设备没法直接相互的访问.

使用cmd的ipconfig代码可以查看内网ip:

我们的笔记本电脑中的以192.168开头的都指的是局域网中的私有IP(内网IP)内网IP在不同局域网中是可以重复的,但在局域网内部是可以重复的.

要想进行主机与主机之间的通信,需要使用外网IP进行通信:

每台主机都有一个外网ip我们可以使用:iP地址查询--手机号码查询归属地 | 邮政编码查询 | iP地址归属地查询 | 身份证号码验证在线查询网 (ip138.com)

这个网址来查询我们这台主机的ip地址.

比如说我们外网IP是:114.252.000.000

我的内网IP是:192.168.1.180

现在我需要去访问目的IP为1.2.3.4的主机.

这是站在服务器的视角看到的我的主机的IP.

如果这是有复数个主机接入运营商路由器,这些主机的内网IP都会被替换成运营商路由器的外网IP114.252.000.000(当然,每个主机有自己的端口号进行区分).

此时,只要是这个电脑经过运营商路由器发给了服务器,服务器看到的源IP都是一样的.但如果是多个电脑同时访问同一个服务器,服务器的响应就会根据发来数据的源端口来进行区分.

因此,服务器能拿到的只是路由器的IP不能拿到我的内网ip,如果我的主机不主动和服务器进行联系,服务器也不知道我的端口.

NAT之间能够有效的解决IP地址不够用的情况,但是也带来了网络环境更加复杂的问题.

3.IPv6(从根本上解决了IP不够用的问题)

使用16个字节128位表示IP地址.就算给地球上每粒沙子一个外网IP都够用.

但是现在的IPv6的普及度还是极低的.

原因:要想使用IPv6需要更换路由器等一系列配件,费钱.

但是IPv6在我国普及度已经达到80%左右了!!!(中美贸易战是契机,虽然都已经普及了,但都还没有开启:随时严阵以待,切换IPv6)

2.地址管理

把一个地址分成了两部分,一部分是网络号,一部分是主机号:(比如有这么一个地址)

前半部分是网络号,后半部分是主机号.

网络号是:192.168.0

主机号是:10

一个典型的局域网环境:

科普一下路由器:
路由器有WAN口和LAN口,WAN口通常是连接光猫的,而LAN口通常是连接主机的.LAN口和WAN口都是一个独立的局域网,相当于一个路由器有两个局域网.而路由器可以使信息在两个局域网中进行转发.

这是光猫:

这是路由器:

两个绿圈是两个局域网,而路由器的功能就是将两个局域网的信息可以互相沟通.

注意:

1.这些设备和路由器都是在一个局域网中的,这些局域网的网络号都是192.168.0,主机号都是不同的.(如果主机号相同,那么就没法上网了!!! )

2.相邻的局域网之间的网络号是不能相同的.

一个IP地址从哪到哪是网络号,从哪到哪是主机号:

这是子网掩码,子网掩码是一个32位的整数左侧都是1右侧都是0,1的部分就描述了IP有多少位是网络号.

一些特殊IP:

第一个咱们都已经见过了,就是主机号是0然后就是子网掩码了,也就是网络号了.

第二个主机地址都是1了也就是说变成了192.168.0.255,就成为了广播地址,使用UDP往这个地址发送整个局域网都能收到.

第三个我们在网络编程中也是常见的,127开头的都是本机环回.(常用测试)

另外,主机号为1,通常是"网关地址"(不绝对可以配置)网关就是局域网的出入口,也就是路由器的LAN口或者WAN口.

3.路由选择

就是路径规划,相当于使用地图导航一下,由于网络环境比较复杂,任何一个节点都无法感知到网络的全貌的,如果进行一个比较长的的路径转发,就比较麻烦.需要"一边走一边问".

路径选择就相当于是一个你要去哪,就边走便查.(每个路由器都会保存一定的周围设备的信息(路由表),每次一个IP数据报经过路由器,都需要匹配路由表,看看接下来咋走,如果路由表上有匹配项就按照它走,如果没有就朝着那个方向走,方向错不了)

还是之前那个TTL-1,如果减到0就丢弃这份数据.根据六度空间理论32左右的TTL就可以将信息发送给世界上任何一台主机上了!!!

四、数据链路层

这一层主要考虑两个节点之间的传输(通过光纤和网线进行连接两个设备)

这里的典型协议最为知名的就是"以太网".(比如我们的网线,也被称为"以太网线") 具体就是遵守以太网的网线

1.以太网数据帧

以太网数据帧=帧头+载荷+帧尾

载荷中包含完整的IP数据报

帧头中包含目的地址、源地址、类型

此处不是使用IP地址了,使用的是mac地址(也被称为物理地址,注意和IP地址完全独立,另一套地址体系,6个字节)

mac地址:6个字节构成,48位有效位.不是动态分配的,而是网卡出厂时就已经设置好了

有线网卡的mac地址:

无线网卡的mac地址:

既然已经有了IP地址为什么还需要mac地址呢?IP地址/mac地址可不可以自己就搞定一切呢?

答案是可以!!!主要是负责网络层和数据链路层的是两拨人,所以每一层所遵循的协议也不一样,逐步演化为了两种地址相互配合的局面.

所以说,当下IP地址和mac地址是如何相互配合的呢?

IP地址来描述整个传输过程的起点和终点,mac地址用来描述相邻两个节点的起点和终点.

就比如:我想要从北京到上海.

通过网络层的数据规划,我们选取了从北京 -> 天津 -> 上海这条路线:

  1. 北京 -> 天津

源IP:北京

目的IP:上海

源mac:北京

目的mac:天津

中间选取了乘坐长途汽车.

  1. 天津 -> 上海

源IP:北京

目的IP:上海

源mac:天津

目的mac:上海

中间选取了乘坐飞机.

IP描述的是全程的起终点,而mac描述的是当前任务的起终点.

类型:

0800指的是普通的以太网数据帧,载荷部分就是一个完整的IP数据报.

0806载荷部分是一个ARP报文

8035载荷部分是一个RARP报文

通过RAP协议可以在交换机/路由器中建立出一个表,这相当于一个hash表,能够建立出IP和mac之间的映射关系.(相当于是知道mac就可以知道IP,知道IP也可以知道mac)

2. 认识MTU

MTU是一个数据链路层的数据帧,能够承载数据的最大长度

载荷具体多长,和使用的物理介质、数据链路层使用的协议有很大的关系,比如以太网数据帧MTU 1500(字节)

正是这个MTU引起了IP协议的分包组包

五、DNS

DNS也被称为"域名解析系统",是当前互联网的基石.也就是网址,类似"www.baidu.com"这样的.就是IP地址太过拗口,也不好记,所以开发了DNS.

举个例子:

打开cmd ,ping一个网址,比如说"www.baidu.com"

就可以使域名和IP地址对应上.

之前使用的是hosts文件(需要手动设置IP地址和域名的键值对,类似hash表),现在就是使用DNS服务器(自动联网查询服务器中的键值对,这个服务器在你的电脑中)

当前我们要求,域名要保证唯一,域名分为一级域名(.com)、二级域名(baidu)、三级域名(www.)有一级不重复就可以.

手动设置DNS可以百度搜一下,一搜一大堆.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值