很高兴碰见你~android
TCP这些东西,基本每一个程序猿都或多或少是掌握的了。虽然感受在实际开发中没有什么用武之处,但,面试他要问啊git
而最近你们伙过完年,也都在准备春招,我也同样。阅读了一些okHttp源码以后,又屁颠屁颠地跑回来从新把tcp、http这些东西给从新学了一遍。okHttp基本都是这些协议的实现,而理解源码的基础是,理解tcp、http。web
从新看了一遍tcp以后,我把这些东西给总结了下来,也就有了这篇文章。面试
计算机网络的知识特色就是:琐碎。靠背诵“面试八股文”估计没多久就忘了。TCP是计算机网络运输层的一个协议,因此首先要对计网分层结构以及运输层有必定的理解。而后是TCP的四个重点:面向链接、可靠传输原理、流量控制和拥塞控制,最后再补充一点粘包和拆包的知识。算法
考虑最简单的状况:两台主机之间的通讯。这个时候只须要一条网线把二者连起来,规定好彼此的硬件接口,如都用USB、电压10v、频率2.4GHz等,这一层就是物理层,这些规定就是物理层协议 。设计模式
咱们固然不知足于只有两台电脑链接,所以咱们可使用交换机把多个电脑链接起来,以下图:缓存
这样链接起来的网络,称为局域网,也能够称为以太网(以太网是局域网的一种)。在这个网络中,咱们须要标识每一个机器,这样才能够指定要和哪一个机器通讯。这个标识就是硬件地址MAC。硬件地址随机器的生产就被肯定,永久性惟一。在局域网中,咱们须要和另外的机器通讯时,只须要知道他的硬件地址,交换机就会把咱们的消息发送到对应的机器。服务器
这里咱们能够无论底层的网线接口如何发送,把物理层抽离,在他之上建立一个新的层次,这就是数据链路层 。微信
咱们依然不知足于局域网的规模,须要把全部的局域网联系起来,这个时候就须要用到路由器来链接两个局域网:网络
可是若是咱们仍是使用硬件地址来做为通讯对象的惟一标识,那么当网络规模愈来愈大,须要记住全部机器的硬件地址是不现实的;同时,一个网络对象可能会频繁更换设备,这个时候硬件地址表维护起来更加复杂。这里使用了一个新的地址来标记一个网络对象:IP地址 。
经过一个简单的寄信例子来理解IP地址。
我住在北京市,我朋友A住在上海市,我要给朋友A写信:
- 写完信,我会在信上写好我朋友A的地址,并放到北京市邮局(给信息附加目标IP地址,并发送给路由器)
- 邮局会帮我把信运输到上海市当地邮局(信息会通过路由传递到目标IP局域网的路由器)
- 上海市当地路由器会帮我把信交给朋友A(局域网内通讯)
所以,这里IP地址就是一个网络接入地址(朋友A的住址),我只须要知道目标IP地址,路由器就能够把消息给我带到。在局域网中,就能够动态维护一个MAC地址与IP地址的映射关系,根据目的IP地址就能够寻找到机器的MAC地址进行发送 。
这样咱们不需管理底层如何去选择机器,咱们只须要知道IP地址,就能够和咱们的目标进行通讯。这一层就是网络层。网络层的核心做用就是 提供主机之间的逻辑通讯 。这样,在网络中的全部主机,在逻辑上都链接起来了,上层只须要提供目标IP地址和数据,网络层就能够把消息发送到对应的主机。
一个主机有多个进程,进程之间进行不一样的网络通讯,如边和朋友开黑边和女友聊微信。个人手机同时和两个不一样机器进行通讯。那么当个人手机收到数据时,如何区分是微信的数据,仍是王者的数据?那么就必须在网络层之上再添加一层:运输层 :
运输层经过socket(套接字),将网络信息进行进一步的拆分,不一样的应用进程能够独立进行网络请求,互不干扰。这就是运输层的最本质特色:提供进程之间的逻辑通讯 。这里的进程能够是主机之间,也能够是同个主机,因此在android中,socket通讯也是进程通讯的一种方式。
如今不一样的机器上的应用进程之间能够独立通讯了,那么咱们就能够在计算机网络上开发出形形式式的应用:如web网页的http,文件传输ftp等等。这一层称为应用层。
应用层还能够进一步拆分出表示层、会话层,但他们的本质特色都没有改变:完成具体的业务需求 。和下面的四层相比,他们并非必须的,能够归属到应用层中。
最后对计网分层进行小结:
这里须要注意的是,分层并非在物理上的分层,而是逻辑上的分层。经过对底层逻辑的封装,使得上层的开发能够直接依赖底层的功能而无需理会具体的实现,简便了开发。
这种分层的思路,也就是责任链设计模式,经过层层封装,把不一样的职责独立起来,更加方便开发、维护等等。okHttp中的拦截器设计模式,也是这种责任链模式。
本文主要是讲解TCP,这里须要增长一些运输层的知识。
在运输层之下的网络层,是不知道该数据包属于哪一个进程,他只负责数据包的接收与发送。运输层则负责接收不一样进程的数据交给网络层,同时把网络层的数据拆分交给不一样的进程。从上往下汇聚到网络层,称为多路复用,从下往上拆分,称为多路拆分 。
运输层的表现,受网络层的限制。这很好理解,网络层是运输层的底层支持。因此运输层是没法决定本身带宽、时延等的上限。但能够基于网络层开发更多的特性:如可靠传输。网络层只负责尽力把数据包从一端发送到另外一端,而不保证数据能够到达且完整。
前面讲到,最简单的运输层协议,就是提供进程之间的独立通讯 ,但底层的实现,是socket之间的独立通讯 。在网络层中,IP地址是一个主机逻辑地址,而在运输层中,socket是一个进程的逻辑地址;固然,一个进程能够拥有多个socket。应用进程能够经过监听socket,来获取这个socket接受到的消息。
举个例子来理解socket。以下图
每个主机能够建立不少个socket来接收信息。如主机A的微信进程,想要发送给主机B的微信,那么他只须要发送给主机B的socketC,主机B的微信就会从socketC中取到消息。(固然实际的流程不是这样的,咱们的消息须要通过微信后台服务器,这里只是举例子)
同理,主机B的QQ,想要发送消息给主机A的QQ,那么只须要把消息发送给socketB,主机A的QQ就能够拿到消息了。
socket并非一个实实在在的东西,而是运输层抽象出来的一个对象。运输层增长了端口这个概念,来区分不一样的socket。端口能够理解为一个主机上有不少的网络通讯口,每一个端口都有一个端口号,端口的数量由运输层协议肯定。
不一样的运输层协议对socket有不一样的定义方式。在UDP协议中,使用目标IP+目标端口号来定义一个socket;在TCP中使用目标IP+目标端口号+源IP+源端口号来定义一个socket。咱们只须要在运输层报文的头部附加上这些信息,目标主机就会知道咱们要发送给哪一个socket,对应监听该socket的进程就可得到信息。
运输层的协议就是大名鼎鼎的TCP和UDP。其中,UDP是最精简的运输层协议,只实现了进程间的通讯;而TCP在UDP的基础上,实现了可靠传输、流量控制、拥塞控制、面向链接等等特性,同时也更加复杂。
固然除此以外,还有更多更优秀的运输层协议,但目前广为使用的,就是TCP和UDP。UDP在后面也会总结到。
TCP协议,表如今报文上,就是会在应用层传输下来的数据前附加上一个TCP首部,这个首部附加了TCP信息,先来总体看一下这个首部的结构:
这张图是来自我大学老师的课件, 很是好用,因此一直拿来学习。最下面部分表示了报文之间的关系,TCP数据部分就是应用层传下来的数据。
TCP首部固定长度是20字节,下面还有4字节是可选的。内容不少,但其中有一些咱们比较熟悉的:源端口,目标端口。嗯?socket不是还须要IP进行定位吗?IP地址在网络层被附加了。其余的内容后面都会慢慢讲解,做为一篇总结文章,这里放出查阅表,方便复习:
头部参数 | 字节数 | 做用 |
---|---|---|
源端口和目的端口字段 | 各占两字节 | socket是经过端口号和IP号来进行定义,这里表示发出消息的主机端口以及接收消息的目标主机端口 |
序号字段 | 4 字节 | TCP 链接中传送的数据流中的每个字节都编上一个序号。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。长度4字节,因此序号的范围是【0,2^32 - 1】 |
确认号字段 | 4字节 | 是指望收到对方的下一个报文段的数据的第一个字节的序号。 |
数据偏移(即首部长度) | 4位 | 指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。“数据偏移”的单位是 32 位(以 4 字节为计算单位) |
保留字段 | 6位 | 保留为从此使用,但目前应置为 0 |
紧急 URG | 1位 | 当 URG =1 时,代表紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(至关于高优先级的数据) |
确认 ACK | 1位 | 只有当 ACK=1 时确认号字段才有效。当 ACK = 0 时,确认号无效。当收到报文须要向发送方发送确认报时设置该标志位为1。 |
推送 PSH | 1位 | 接收 TCP 收到 PSH = 1 的报文段,就尽快地交付接收应用进程,而再也不等到整个缓存都填满了后再向上交付。 |
复位 RST | 1位 | 当 RST =1 时,代表 TCP 链接中出现严重差错(如因为主机崩溃或其余缘由),必须释放链接,而后再从新创建运输链接。 |
同步 SYN | 1位 | 同步 SYN = 1 表示这是一个链接请求或链接接受报文。 |
终止 FIN | 1位 | 用来释放一个链接。FIN = 1 代表此报文段的发送端的数据已发送完毕,并要求释放运输链接 |
窗口字段 | 2字节 | 发送方接收缓存区剩下的字节 数,注意单位是字节。 |
检验和 | 2字节 | 检验和字段检验的范围包括首部和数据这两部分。在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部。主要是检验报文是否发生了错误,如某个‘1’变成了‘0’。 |
紧急指针字段 | 2字节 | 指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面) |
选项字段 | 长度不定 | TCP 最初只规定了一种选项,即最大报文段长度 MSS。MSS 告诉对方 TCP:“个人缓存所能接收的报文段的数据字段的最大长度是 MSS 个字节。” |
填充字段 | 不定 | 这是为了使整个首部长度是 4 字节的整数倍。 |
选项字段中包含如下其余选项:
选项 | 做用 |
---|---|
窗口扩大选项 | 占 3 字节,其中有一个字节表示移位值 S。新的窗口值等于 TCP 首部中的窗口位数增大到 (16 + S),至关于把窗口值向左移动 S 位后得到实际的窗口大小 |
时间戳选项 | 占 10 字节,其中最主要的字段时间戳值字段(4 字节)和时间戳回送回答字段(4 字节),主要是用于计算数据报在网络中传输的往返时间。 |
选择确认选项 | 接收方收到了和前面的字节流不连续的两个字节块,须要告诉发送方目前已经接收到的数据报范围。每个段须要两个边界,一个边界须要4字节来表示,选项字段最长是40字节,因此最多能够表示4个已接收的字段。 |
讲完下面内容,再回来看这些字段就熟悉了。
TCP并非把应用层传输过来的数据直接加上首部而后发送给目标,而是把数据当作一个字节 流,给他们标上序号以后分部分发送。这就是TCP的 面向字节流 特性:
面向字节流的好处是无需一次存储过大的数据占用太多内存,坏处是没法知道这些字节表明的意义,例如应用层发送一个音频文件和一个文本文件,对于TCP来讲就是一串字节流,没有意义可言,这会致使粘包以及拆包问题,后面讲。
前面讲到,TCP是可靠传输协议,也就是,一个数据交给他,他确定能够完整无误地发送到目标地址,除非网络炸了。他实现的网络模型以下:
对于应用层来讲,他就是一个可靠传输的底层支持服务;而运输层底层采用了网络层的不可靠传输。虽然在网络层甚至数据链路层就可使用协议来保证数据传输的可靠性,但这样网络的设计会更加复杂、效率会随之下降。把数据传输的可靠性保证放在运输层,会更加合适。
可靠传输原理的重点总结一下有:滑动窗口、超时重传、累积确认、选择确认、连续ARQ 。
要实现可靠传输,最简便的方法就是:我发送一个数据包给你,而后你跟我回复收到,我继续发送下一个数据包。传输模型以下:
这种“一来一去”的方法来保证传输可靠就是中止等待协议(stop-and-wait)。不知道还记不记得前面TCP首部有一个ack字段,当他设置为1的时候,表示这个报文是一个确认收到报文。
而后再来考虑一种状况:丢包。网络环境不可靠,致使每一次发送的数据包可能会丢失,若是机器A发送了数据包丢失了,那么机器B永远接收不到数据,机器A永远在等待。解决这个问题的方法是:超时重传 。当机器A发出一个数据包时便开始计时,时间到还没收到确认回复,就能够认为是发生了丢包,便再次发送,也就是重传。
但重传会致使另外一种问题:若是原先的数据包并无丢失,只是在网络中待的时间比较久,这个时候机器B会受到两个数据包,那么机器B是如何辨别这两个数据包是属于同一份数据仍是不一样的数据?这就须要前面讲过的方法:给数据字节进行编号。这样接收方就能够根据数据的字节编号,得出这些数据是接下来的数据,仍是重传的数据。
在TCP首部有两个字段:序号和确认号,他们表示发送方数据第一个字节的编号,和接收方期待的下一份数据的第一个字节的编号。前面讲到TCP是面向字节流,可是他并非一个字节一个字节地发送,而是一次截取一整段。截取的长度受多种因素影响,如缓存区的数据大小、数据链路层限制的帧大小等。
中止等待协议已经能够知足可靠传输了,但有一个致命缺点:效率过低。发送方发送一个数据包以后便进入等待,这个期间并无干任何事,浪费了资源。解决的方法是:连续发送数据包。模型以下:
和中止等待最大的不一样就是,他会源源不断地发送,接收方源源不断收到数据以后,逐一进行确认回复。这样便极大地提升了效率。但一样,带来了一些额外的问题:
发送是否能够无限发送直到把缓冲区全部数据发送完?不能够。由于须要考虑接收方缓冲区以及读取数据的能力。若是发送太快致使接收方没法接受,那么只是会频繁进行重传,浪费了网络资源。因此发送方发送数据的范围,须要考虑到接收方缓冲区的状况。这就是TCP的流量控制 。解决方法是:滑动窗口 。基本模型以下:
在TCP的首部有一个窗口大小字段,他表示接收方的剩余缓冲区大小,让发送方能够调整本身的发送窗口大小。经过滑动窗口,就能够实现TCP的流量控制,不至于发送太快,致使太多的数据丢失。
连续ARQ带来的第二个问题是:网络中充斥着和发送数据包同样数据量的确认回复报文,由于每个发送数据包,必须得有一个确认回复。提升网络效率的方法是:累积确认 。接收方不须要逐个进行回复,而是累积到必定量的数据包以后,告诉发送方,在此数据包以前的数据全都收到。例如,收到 1234,接收方只须要告诉发送方我收到4了,那么发送方就知道1234都收到了。
第三个问题是:如何处理丢包状况。在中止等待协议中很简单,直接一个超时重传就解决了。但,连续ARQ中不太同样。例如:接收方收到了 123 567,六个字节,编号为4的字节丢失了。按照累积确认的思路,只能发送3的确认回复,567都必须丢掉,由于发送方会进行重传。这就是GBN(go-back-n) 思路。
可是咱们会发现,只须要重传4便可,这样不是很浪费资源,因此就有了:选择确认SACK 。在TCP报文的选项字段,能够设置已经收到的报文段,每个报文段须要两个边界来进行肯定。这样发送方,就能够根据这个选项字段只重传丢失的数据了。
到这里关于TCP的可靠传输原理就已经介绍的差很少。最后进行一个小结:
固然,这只是可靠传输的冰山一角,感兴趣能够再深刻去研究(和面试官聊天已经差很少了[狗头])。
拥塞控制考虑的是另一个问题:避免网络过度拥挤致使丢包严重,网络效率下降 。
拿现实的交通举例子:
高速公路同一时间可通行的汽车数量是必定的,当节假日时,就会发生严重的堵车。在TCP中,数据包超时,会进行重传,也就是会进来更多的汽车,这时候更堵,最后致使的结果就是:丢包-重传-丢包-重传。最后整个网络瘫痪了。
这里的拥塞控制和前面的流量控制不是一个东西,流量控制是拥塞控制的手段:为了不拥塞,必须对流量进行控制。拥塞控制目的是:限制每一个主机的发送的数据量,避免网络拥塞效率降低。就像广州等地,限制车牌号出行是一个道理。否则你们都堵在路上,谁都别想走。
拥塞控制的解决方法是流量控制,流量控制的实现是滑动窗口,因此拥塞控制最终也是经过限制发送方的滑动窗口大小来限制流量 。固然,拥塞控制的手段不仅是流量控制,致使拥塞的因素有:路由器缓存、带宽、处理器处理速度等等。提高硬件能力(把4车道改为8车道)是其中一个方法,但毕竟硬件提高是有瓶颈的,没办法不断提高,仍是须要从tcp自己来增长算法,解决拥塞。
拥塞控制的重点有4个:慢开始、快恢复、快重传、拥塞避免。这里依旧献祭出大学老师的ppt图片:
Y轴表示的是发送方窗口大小,X轴表示的是发送的轮次(不是字节编号)。
经过这个算法,就能够在很大程度上,避免网络拥挤。
除此以外,还可让路由器在缓存即将满的时候,告知发送方我快满了,而不是等到出现了超时再进行处理,这是主动队列管理AQM。此外还有不少方法,可是上面的算法是重点。
这一小节讲的就是无人不晓的TCP三次握手与四次挥手这些,通过前面的内容,这一小节其实已经很好理解。
TCP是面向链接的,那链接是什么?这里的链接并非实实在在的链接,而是通讯双方彼此之间的一个记录 。TCP是一个全双工通讯,也就是能够互相发送数据,因此双方都须要记录对方的信息。根据前面的可靠传输原理,TCP通讯双方须要为对方准备一个接收缓冲区能够接收对方的数据、记住对方的socket知道怎么发送数据、记住对方的缓冲区来调整本身的窗口大小等等,这些记录,就是一个链接。
在运输层小节中讲到,运输层双方通讯的地址是采用socket来定义的,TCP也不例外。TCP的每个链接只能有两个对象,也就是两个socket,而不能有三个。因此socket的定义须要源IP、源端口号、目标IP、目标端口号四个关键因素,才不会发生混乱。
假如TCP和UDP同样只采用目标IP+目标端口号来定义socket,那么就会出现多个发送方同时发送到同一个目标socket的状况。这个时候TCP没法区分这些数据是否来自不一样的发送方,就会致使出现错误。
既然是链接,就有两个关键要点:创建链接、断开链接。
创建链接的目的就是交换彼此的信息,而后记住对方的信息。因此双方都须要发送彼此的信息给对方:
但前面的可靠传输原理告诉咱们,数据在网络中传输是不可靠的,须要对方给予咱们一个确认回复,才能够保证消息正确到达。以下图:
机器B的确认收到和机器B信息能够进行合并,减小次数;并且发送机器B给机器A自己就表明了机器B已经收到了消息,因此最后的示例图是:
步骤以下:
三次消息的发送,称为三次握手。
断开链接和三次握手相似,直接上图:
机器A发送完数据以后,向机器B请求断开链接,自身进入FIN_WAIT_1状态,表示数据发送完成且已经发送FIN包(FIN标志位为1);
机器B收到FIN包以后,回复ack包表示已经收到,但此时机器B可能还有数据没发送完成,自身进入CLOSE_WAIT状态,表示对方已发送完成且请求关闭链接,自身发送完成以后能够关闭链接;
机器B数据发送完成以后,发送FIN包给机器B ,自身进入LAST_ACK状态,表示等待一个ACK包便可关闭链接;
机器A收到FIN包以后,知道机器B也发送完成了,回复一个ACK包,并进入TIME_WAIT状态
TIME_WAIT状态比较特殊。当机器A收到机器B的FIN包时,理想状态下,确实是能够直接关闭链接了;可是:
- 咱们知道网络是不稳定的,可能机器B 发送了一些数据还没到达(比FIN包慢);
- 同时回复的ACK包可能丢失了,机器B会重传FIN包;
若是此时机器A立刻关闭链接,会致使数据不完整、机器B没法释放链接等问题。因此此时机器A须要等待2个报文生存最大时长,确保网络中没有任何遗留报文了,再关闭链接
最后,机器A等待两个报文存活最大时长以后,机器B 接收到ACK报文以后,均关闭链接,进入CLASED状态
双方之间4次互相发送报文来断开链接的过程,就是四次挥手。
如今,对于为何握手是三次挥手是四次、必定要三次/四次吗、为何要停留2msl再关闭链接等等这些问题,就都解决了。
运输层协议除了TCP,还有大名鼎鼎的UDP。若是说TCP凭借他完善稳定的功能独树一帜,那UDP就是精简主义乱拳打死老师傅。
UDP只实现了运输层最少的功能:进程间通讯。对于应用层传下来的数据,UDP只是附加一个首部就直接交给网络层了。UDP的头部很是简单,只有三部分:
因此UDP的功能也只有两个:校验数据报是否发生错误、区分不一样的进程通讯。
但,TCP的功能虽然多,但同时也是要付出相对应的代价。例如面向链接的特性,在创建和断开链接的时候会有开销;拥塞控制的特性,会限制传输的上限等等。下面来罗列一下UDP的优缺点:
UDP适用于对传输模型须要应用层高度自定义、容许出现丢包、须要高效率的场景、须要广播;例如
咱们能够发现,运输层在传输数据的时候,并非把整个数据包加个首部直接发送过去,而是会拆分红多个报文分开发送;那他这样作缘由是什么?
有读者可能会想到:数据链路层限制了数据长度只能有1460。那数据链路层为何要这么限制?他的本质缘由就是:网络是不稳定的。若是报文太长,那么极有可能在传输通常的时候忽然中断了,这个时候就要整个数据重传,效率就下降了。把数据拆分红多个数据报,那么当某个数据报丢失,只须要重传该数据报便可。
那是否是拆分得越细越好?报文中数据字段长度过低,会使得首部的占比太大,这样首部就会成为网络传输最大的负担了。例如1000字节,每一个报文首部是40字节,若是拆分红10个报文,那么只须要传输400字节的首部;而若是拆分红1000个,那么须要传输40000字节的首部,效率就极大地下降了。
先看下图:
能够看出来,使用路由转发的好处是:提升网络的容错率,本质缘由依旧是网络是不稳定的 。即便坏掉几个路由器,网络依旧畅通。可是若是坏掉路由器6那就直接致使主机A和主机B没法通讯,因此要避免这种核心路由器的存在。
使用路由的好处还有:分流。若是一条线路太拥堵,能够从别的路线进行传输,提升效率。
在面向字节流那一小节讲过,TCP不懂这些数据流的意义,他只知道从应用层拿到数据流,切割成一份份报文,而后发送给目标对象。而若是应用层传输下来的是两个数据包,那么极有可能出现这种状况:
粘包与拆包都是应用层须要解决的问题,能够在每一个文件的最后附加上一些特殊的字节,如换行符;或者控制每一个报文只包含一个文件的数据,不足的用0补充等等。
TCP的面向链接特色可能会被恶意的人利用,对服务器进行攻击。
前面咱们知道,当咱们向一个主机发送syn包请求建立链接时,服务器会为咱们建立缓冲区等,而后向咱们返回syn+ack报文;若是咱们伪造IP和端口,向一个服务器进行海量的请求,会使得服务器建立了大量的建立一半的TCP链接,使得其没法正常响应用户的请求,致使服务器瘫痪。
解决的方法能够有限制IP的建立链接数、让建立一半的tcp链接在更短的时间内自行关闭、延缓接收缓冲区内存的分配等等。
咱们向服务器的每一次请求都须要建立一个TCP链接,服务器返回数据以后就会关闭链接;若是在短期内有大量的请求,那么频繁建立TCP链接关闭TCP链接是一个很浪费资源的行为。因此咱们可让TCP链接不要关闭,在这个期间进行请求,提升效率。
须要注意长链接维持时间、建立条件等,避免被恶意利用建立大量的长链接,消耗殆尽服务器的资源。
之前学习的时候以为这些东西好像没什么卵用,貌似就是用来考试的。事实上,在没应用到的时候,对这些知识很难有更深层次的认知,例如如今我看上面的总结,不少只是表面上的认知,不知道他背后表明的真正含义。
但当我学习的更加普遍、深刻,会对这些知识有愈来愈深入的认识。有那么几个瞬间以为:哦原来那个东西是这样运用,那个东西是这样的啊,原来学了是真的有用。
如今可能学了以后没有什么感受,可是当用到或者学到相关的应用时,会有一个顿悟感,会瞬间收获不少。
以为有帮助留个赞鼓励一下做者吧~
全文到此,原创不易,以为有帮助能够点赞收藏评论转发。
笔者才疏学浅,文章有错误或有不一样观点欢迎评论区交流。
如需转载请评论区或私信告知便可。另外欢迎光临笔者的我的博客:传送门