承接上文 HTTP,数据经过应用层就到传输层,但数据到传输层之前需要先获得服务端的 IP 地址,这就涉及到 DNS 域名解析。
主机的真正地址是 IP ,问题是 IP 地址不方便人们记忆,就像你拿手机给张三打电话,难道你能瞬间说出张三电话号码么,手机里做一个名字跟电话的映射即可,想通话时直接从通讯录找到张三就可以找到对应的手机号,在网络请求时候也是需要映射的,而域名服务器 Domain Name System 就是干这个事的,深入讲 DNS 前先了解下域名。
我们在浏览器地址栏中输入的每一个地址都是一个域名,比如 www.baidu.com。域名是由.
和不同级别域的域名组成。通常我们在书写时会省略根域名,即域名结尾的.
,如 www.baidu.com.
。由于域名是老外发明的所以从左到右范围逐步变大且以.
分割。
DNS分层 由上到下域名之间相互包含跟嵌套,根域名服务器是关键,必须是众所周知的,找到了它,下面的各级域名服务器才能找到,否则域名解析就无从谈起了。我们看下请求 www.baidu.com 的 DNS 解析流程:
这样进行 DNS 的流程是OK的,但问题是全球数十亿的PC电脑,如果每个电脑请求上网都按照上面流程走一波,那上面的 DNS 核心解析系统瞬间爆炸!解决办法就是用缓存,很多大公司跟运营商都会搭建自己的 DNS 服务器来代替用户请求核心 DNS 系统,如果查到的话可以缓存查询记录,再次收到请求的号如果有缓存结果或者缓存未过期,则直接返回原来的缓存结果,知名的 Google 8.8.8.8 DNS 解析服务器,就是 Google 自建的 非权威域名服务器
。除了非权威域名服务器,我们经常看到的有浏览器缓存,操作系统缓存,比如 /etc/hosts文件等。
DNS域名解析
TCP 是一个是面向连接的、可靠的、基于字节流的、工作在传输层的数据传输服务
。用 TCP 传输数据能确保接收端接收的网络包是无损坏、无间隔、非冗余、有序。这里需注意 TCP 是一对一连接的。
TCP头部 + HTTP
下面的7~12是控制位,用来表示说明报文段的性质
TCP 只规定了一种选项,即TCP报文段最大长度 MSS,通常是1460字节,整个TCP报文段的长度 = 数据字段的长度 + TCP 首部的长度 。
TCP三次握手
closed
状态,然后服务端主动监听某个客户端端口,此时服务端处于listen
状态。seq = client_isn
,同时将 SYN = 1
表示这是 SYN
报文,接着把该 SYN
报文发给服务器,注意此时报文不包含引用层数据,客户端处于 syn-sent
状态。SYN
报文后也随机初始化个序号 seq = server_isn
,并且将确认序号 ack = client_isn + 1
,接着把 SYN = 1
跟 ACK = 1
,然后该报文发送给客户端,服务器处于 syn-rcvd
状态。ACK = 1
,确认应答号 ack = server_isn + 1
,然后把报文发送给服务器,本次报文可发送数据,同时客户端处于 established
状态。established
状态。这里你可能发现了客户端跟服务器的初始化序列号是各自随机的,原因是网络中的报文会重发、会延迟、也有可能丢失,为避免相互影响干脆各用各的为好。同时通过流程发现前两次握手是不带数据的,第三次可携带数据。
前面在HTTP时候就说过了,数据到TCP层跟IP层都会拆分发送,有人可能会问:既然IP会分帧,那为什么TCP层还分层呢?原因是如果TCP不分层,只用IP层分帧数据发送,如果有一帧出现丢失则会导致整个IP报文分帧全部重传。本质在于IP层没有重传机制而TCP层可以实现数据的超时重传、丢失重传。
信息传输大致流程
服务器一般用 netstat
查看 tcp,udp 的端口和进程等相关情况。netstat -tunlp | grep 端口号
-t (tcp) 仅显示tcp相关选项
-u (udp) 仅显示udp相关选项
-n 拒绝显示别名,能显示数字的全部转化为数字
-l 仅列出在Listen(监听)的服务状态
-p 显示建立相关链接的程序名
netstat样例
TCP是不区分客户端和服务端,连接的建立是双向
的过程。所以客户端要给服务器通讯的话两次握手是必须的。
如果还不太理解,我们用个生活常识说明下。晚上你在小区里散步,不远处看见一位漂亮妹子迎面而来,因为路灯有点暗不能100%确认,所以要通过招手的方式来确定对方是否认识自己。
1. 你首先向妹子 招手 syn。
2. 妹子看到你向自己招手后,向你点头 微笑 ack。
3. 她也需要确认一下你有没有可能你是在看别人呢,妹子也向你 招手 syn。
4. 你看到妹子 微笑ack 后确认了妹子成功辨认出了自己,进入 established 状态。
5. 妹子给你 招手 syn 了,你也 微笑 ack 回复,妹子收到后也进入 established 状态。
因为妹子连续进行了两个动作,先是点头微笑,然后再次招手,所以可以将这两个动作合成一个动作,招手的同时点头和微笑。于是这四个动作就简化成了三个动作。
你与妹子的相识
客户端建立连接时发送多次 SYN 报文,由于网络拥堵可能旧的 SYN 报文比新的 SYN 报文先到服务器,服务器不管新旧,收到就回复 SYN + ACK 给客户端,三次握手情况下客户端可以根据序列号或超时时间判断回复的连接是否是历史连接,如果是历史连接直接发送 RST 报文给服务端来终止连接。
TCP 协议的通信双方都在维护各自的序列号,且必须要让对方知道。只有通过三次握手才可以实现。
二次握手情况下,如果客户端的 SYN 阻塞导致重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。而三次握手发现无效链接可在第三次给服务器端发送终止指令。
TCP还设有一个保活计时器,服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
TCP 连接时会经过三次握手,在第一次握手后,服务端收到 SYN 报文 就会发出 ACK + SYN 报文 同时进入 SYN_RCVD 状态,如果有黑客伪造 n 个不同 IP 发出请求,会导致服务器的 SYN_RCVD 队列 爆满,最终无法对外提供服务。
解决方法:
客户端跟服务端都可以发出端口请求,TCP 断开连接是通过四次挥手方式。
TCP四次挥手
FIN = 1
,FIN报文段即使不携带数据,也要消耗一个序号,此时序列号seq = u
,u = 前面已经传输过来数据最后一个字节序号加1,客户端进入FIN-WAIT-1
状态。ACK=1
,应答确认好 ack=u+1
,并且带上自己的序列号seq=v
,此时服务端就进入了CLOSE-WAIT
状态。TCP 服务器通知高层的应用进程进入半闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个 CLOSE-WAIT
状态持续的时间。FIN-WAIT-2
状态,等待服务器发送连接释放报文,在这之前还需要接受服务器发送的最后的数据。FIN=1
,ack=u+1
,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为 seq=w
,此时服务器就进入了LAST-ACK
状态,等待客户端的确认。ACK=1
,ack=w+1
,seq=u+1
,此时客户端就进入了TIME-WAIT
状态。注意此时 TCP 连接还没有释放,必须经过最长报文段寿命 2MSL
的时间后,当客户端撤销相应的 TCB
后,才进入 CLOSED
状态。CLOSED
状态。同样撤销 TCB
后,就结束了这次的 TCP 连接,可以看到服务器结束TCP连接的时间要比客户端早一些。我们还以你跟妹子碰面交流为例,你俩彼此确认后交流几分钟后,你打算结束这个谈话,毕竟交流太久没老婆发现就凉了。
你跟妹子挥手离别
其实分析下整个关闭的流程就知道为什么必须是四次挥手而不是三次挥手了。
FIN
时,仅仅表示客户端不再发送数据出去了但是还是能接收数据。FIN
报文时,先回一个 ACK
应答报文,意思是不再接受数据了,但服务端可能还有数据需往外发送,等服务端不再发送数据时才发送 FIN
报文给客户端来表示同意现在关闭连接。这里注意服务端的 ACK
跟 FIN
是分开
发的。ACK
后,再给服务端发送 ACK
,最终客户端跟服务器都进入 close
状态。MSL 定义:
Maximum Segment Lifetime 报文最大生存时间,意思是网络传输的报文在网络上存在的最长时间,超过这个时间报文将被丢弃。而数据之所以可以被抛弃是因为TCP层的下面的IP层有个TTL来记录报文传输过程中经过的最大路由次数。
TIME_WAIT 定义:
TIME_WAIT 存在意义:
TIME_WAIT 发生场景:
避免 TIME_WAIT 过多:
UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法,它的协议很简单,头部只有八个字节:
UDP头部
UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,当报文发送之后,是无法得知其是否安全完整到达的。
UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。
发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此应用程序必须选择合适大小的报文
UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的
你可能经常被问到,TCP 和 UDP 为何可以共用同一端口?这是因为从网络层的角度来看,它是不知道端口这个概念的,TCP/UDP 都是包裹在 IP 协议内的,IP 协议只需要知道 IP 对应的硬件地址就可以把远端的网络包发送到目的主机上。
端口这个概念是由操作系统划分的。因为内核不可能把所有网络数据都发送给所有的进程,所以为了区分哪些数据该划分给哪些进程,便在传输层的协议中定义了端口。而TCP和UDP协议中的端口号占位都是16位,所以操作系统能绑定的端口也就只有65535个。
如果你查看 C 语言有关 Socket 编程中的 socket
跟 bind
函数你会发现,系统是以 协议 + ip + 端口来绑定端口的,所以不同协议相同的ip和端口也是可以绑定成功的。
为保证数据安全到达接受端,TCP引入了超时重传、快速重传、SACK、D-SACK。
以时间为基准,在发送数据时设置个定时器,如果期限内没收到接受者的ACK就会重新发送数据,一般数据包丢失或确认应答丢失会导致超时重传,这里先普及两个跟时间相关的参数跟一些规则。
RTO跟RTT RTT跟RTO之间的关系十分微妙。
所以离线情况下 RTO 稍微大于 RTT是最好的。具体规则有兴趣的可自行百度。
TCP有累计确认机制,当接收端收到比期望序号大的报文段时,便会重复发送最近一次确认的报文段的确认信号,我们称之为冗余ACK(duplicate ACK)。 如图所示,报文段1成功接收并被确认ACK 2,接收端的期待序号为2,当报文段2丢失,报文段3失序到来,与接收端的期望不匹配,接收端重复发送冗余ACK 2。
快速重传机制
发送端如果在超时重传定时器溢出之前,接收到连续的三个重复冗余ACK(其实是收到4个同样的ACK,第一个是正常的,后三个才是冗余的),发送端便知晓哪个报文段在传输过程中丢失了,于是重发该报文段,不需要等待超时重传定时器溢出,最后客户端收到 2,因为345已经回复过了,返回ACK6。
为啥是3次呢?
你要明白发送端即使按序发送,接收端也是会出现乱序的。乱序也会造成冗余ACK发送,那冗余ACK是乱序导致还是丢包导致呢?经过权衡把3次冗余ACK作为判定丢失的准则其本身就是估计值。
数据接收情况 A为发送端,B为接收端,A的待发报文段序号为 【N-1,N,N+1,N+2】,假设报文段N-1成功到达。
基于这样的概率,选定3次冗余ACK作为阈值也算是合理的。实际抓包时大多数的快速重传都会在大于3次冗余ACK后发生。
快速重传解决了超时问题,可是重传时是重传之前的一个,还是重传所有它是定不了的。
既然快速重传搞不定,就用 Selective Acknowledgment 选择性确认,原理也很简单,服务端给客户端回复的时候多加个字段SACK,SACK的内容就是告知发送端服务端收到了哪些。这样服务端可以根据收到的信息选择性发送丢失的包。
DSACK是在SACK的基础上做了一些扩展,主要用于对收到的重复报文进行了处理。DSACK同样使用了与SACK一样的报文格式。核心关注点是发送的时候出问题了还是回复的时候出问题了。
如果没有滑动窗口的机制:传输N份文件,就需要等待N次应答时间。
总的传输时间 = N份传输时间 + N份应答传输时间。
保证可靠性的前提下TCP 引入了窗口
概念,滑动窗口
可以让我们进一步提高传输效率。在窗口内的数据无需等待确认应答就可以继续发送数据。窗口的本质是OS开辟的一个缓存空间,然后进行批量传输,只要接收方没确认应答那么缓存中会一直存在。
总的传输时间 = N分数据传输时间叠加成一份时间,N份应答传输时间,重叠成一份时间
窗口大小为4000字节 窗口大小一般是接收方来决定的,接收方会告知发送方自己有多少缓存可接受数据,如果超过这个数据量接收方就无法接收了。
滑动窗口
在一的状态下发送方收到一个请求序列号2001的确认应答ACK,则2001前数据被标记为传输完毕,系统会进行窗口滑动变为二的样子。
滑动窗口控制
。流量控制
。这里的数据丢失其实跟前面说到的重传机制类似,主要分为两种
收到数据但ACK丢失
发送时丢失
前面说到的流量控制只是单纯的对于发送方跟接受方而已,但是我们要知道网络一般都是公用的,别的服务器也可以能将网络搞阻塞,因为阻塞导致重发,然后重发导致更阻塞,最后陷入恶性循环。
为了控制发送方的数据量避免数据阻塞整个网络,发送方维护着一个叫拥塞窗口
的东西,前面说到过发送窗口
跟接受窗口
,现在由于有了拥塞窗口,此时 发送窗口swnd = min(拥塞窗口cwnd,接受窗口rwnd)。拥塞窗口的大小是动态变化的,当网络没阻塞就会变大,网络中有阻塞就会变小。判断阻塞的依据就是如果发送方在指定时间内没收到数据那就是阻塞了。
拥塞控制主要通过慢开始
,快重传
,快恢复
和避免拥塞
来实现的。
TCP建立连接后系统有个慢启动的过程,意思就是一点一点的提高发送数据包的数量,慢启动的原则就是当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。有一个叫慢启动门限 slow start threshold 状态变量来充当最大值。
一般情况下 slow start threshold = 65535字节,系统进入拥塞避免算法后,每当收到一个 ACK 时,拥塞窗口就增加 1/拥塞窗口。拥塞避免算法存在的意义就是将慢开始的那种指数增长
变化为线程增长
。
进入拥塞避免算法后的数据随着不断增长最终会导致网络阻塞,最终引发丢包。然后会采用前面说到的超时重传
跟快速重传
。
快恢复与快重传配合使用,当发送方接收到连续三个重复确认请求,为了避免网络拥塞,执行快速重传(cwnd = cwnd/2 同时 ssthresh = cwnd ),执行快速恢复算法。