什么是TCP网络分层?
按照从上至下分为应用层、传输层、网络层、数据链路层、物理层。
如果要从客户端发送数据到服务端,必然要经过上面五层。
- 应用层加一个头,表名是什么协议的,并且把数据包组装进去。
- 传输层提供端对端的通信,增加TCP的头部,里面包含端口号,TCP还需要建立连接,可靠的通信,所以还有序列化等。
- 网络层增加一个IP协议,因为TCP只是负责可靠的连接,但是需要找到机器呢,还需要IP到IP的,所以在IP协议中需要包含目标、源IP地址。
- 网络访问层,指的是硬件传输的,一般来说都是指的以太网,所以需要增加一个以太网的头部信息,包括以太网的相关信息、mac地址等等。
- 物理层经过光纤、双绞线等等介质,达到了我们的服务端。
服务端呢从下至上一层层剥离,
- 先到数据链路层把以太网的头剥离,根据mac地址判断包是否是发给自己的。
- 如果是发给自己,然后到网络层把IP报文解析,传送数据包,确定路由。
- 到传输层后,进行TCP报文解析,数据传输,可靠连接等等。
- 到达应用层之后,形成http报文到达服务端。
这样分层的好处:
- 各层独立,限制了依赖关系的范围,各层之间使用标准化的接口,各层不需要上下层是如何工作的,增加、修改应用协议不会影响传输的协议。
- 灵活性更好,比如路由器不需要应用层和传输层,分层之后路由器就可以只加载更少的几个协议层。
- 易于测试和维护,提高了可测性,可以独立的测试特定层,如果某一层有了更好的实现可以整体替换掉。
- 可以促进标准化,每一层的职责清楚,方便进行标准化。
TCP三次握手为什么是三次,不能是两次、四次?
- 首先第一次客户端发送一个SYN包给服务器端,询问服务器建立连接。SYN为1表示为同步报文段的标识,seq为一个随机值。
- 服务器收到后,回复一个SYN包和ACK包,其中seq为随机值,ack的值为原seq的值+1。
- 客户端收到后,检查ack的值是否正确,即第一次发送的seq+1就是正确,如果正确,客户端再次回复一个ACK包给服务器端,其中ack为seq的值加1。
从状态上面来讲:
- 开始客户端和服务器端都是closed状态。
- 客户端建立TCP主动打开状态,到服务器之后,服务器为listen状态。
- 当服务器第二次握手,回复客户端的时候,客户端为sys-sent状态。
- 客户端发起第三次握手,到服务器之后,服务器为syn-recd状态。 此时链接建立,客户端和服务器端都是established状态。
那为什么是三次握手呢?
三次握手,就是为了防止已经失效的连接请求报文,突然有效,然后传送到服务端,导致错误产生。
比如第一次客户端发送一个SYN包请求建立连接,但是由于网络原因并未及时到达服务端,但是为了连接健壮性一般都会重连,客户端会继续发送SYN包来请求连接,如果这次数据包是正常的,服务器收到回复SYN和ACK包给客户端。但是网络好了后,第一个包又突然正常到达服务端了,服务器以为是客户端发送的新的连接,如果没有客户端的第三次确认,那么服务端就会认为客户端发送了两次链接,而客户端认为自己只是创建了一次连接,造成状态不一致。
为什么不是四次呢? 因为没必要,三次就可以确认双方已经正常通信了。
因为服务器不仅保存一个客户端的信息,服务器如何保存这些客户端的信息呢?第一次握手的时候,服务器如何保存客户端的连接握手信息呢?
服务器有一个半连接队列,也就是SYN队列,第一次握手的时候,所有的信息都会保存在这个队列里面。
三次链接之后,最后一次握手来的时候,服务器就会判断在半连接队列里面,是否有当前客户端的信息,如果有则把信息拿出来,放入到新的队列中,这个新的队列就是全连接队列,也就是acept队列。如果没有,在这种情况下,服务器可能会向客户端发送RST(复位)包,表示连接被重置或终止。客户端接收到RST包后,会认为连接未能成功建立,可能会尝试重新发起连接请求。
如果,客户端不发起三次握手,那半连接队列里面,当前的节点无法移除,随着时间的推移,或者黑客的攻击请求,会造成半连接队列满额,没法接受新的请求了。
这两个队列,都是使用链表实现的。
为什么是四次挥手呢?
四次挥手的过程,其实是不区分客户端和服务器的,因为谁都可以发起断开链接的过程。我们就按照客户端主动断开链接吧。
- 客户端主动发送了一个FIN包,用来关闭客户端和服务端的数据传送,带一个seq过来。
- 服务端收到FIN包后,回复一个ACK包,并把ack回复为seq+1,再带一个seq序号回复给客户端。
- 此时服务端关闭和客户端的连接,再次发送一个FIN包,给客户端,并且把剩下的数据全部发送过去给客户端。
- 客户端收到之后,回复一个ack包,并且把seq和ack都回复过去给服务端。
- 断开链接。
第二次和第三次,如果在第二次的包还未发出去的时候,服务端立马调用了close函数,则第二次和第三次的包可能会合并发出去。
状态如下:
- 双方建立连接后,都处于established状态。
- 当客户端主动关闭,调用close之后,客户端进入fin-wait-1的状态。
- 当被动方服务端接收到FIN包之后,服务端进入close-wait状态。然后发送第三次挥手后,变为last-ack状态。
- 当服务端第三次挥手,发送包到客户端的时候,客户端进入fin-wait-2的状态。
- 客户端收到包后,立马发送第四次挥手,此时处于time-wait状态。
- 服务端收到第四次挥手后,进入closed状态。
- 客户端等待2ms后,然后置于closed状态。
那,这几个状态保存在哪呢?
是保存在TCB控制块,因为它伴随每一个链接的什么周期。
为什么客户端有一个time-wait状态呢?需要等待一段时间呢?
这是为了保证服务端可以接收到发送过去的ACK包,如果一旦立马关闭连接的话,一旦最后发送的ACK包丢失,则服务端会一直停留在最后的确认状态。服务端没有收到对应的ACK,会等待一段时间重发第三步的FIN包,那么客户端会响应这个,重发ACK包的,这是为了可靠的断开机制。
close-wait这个状态有什么用?
这个是被动方,比如服务端调用close不正常,然后被动关闭,或者是这个状态是让服务器发送还未传送完的数据。
那为什么是四次挥手呢?不是三次呢?
优化三次的话,一般指的是第二次和第三次合并,为什么不能合并呢?
因为如果第二次过来后,服务端还会有一些善后的事情可能需要处理,此时善后的事情可能超过tcp设定的超时时间,如果超过了,客户端会认为服务端没有收到,会继续重试,导致资源的浪费。
如果四次的话,第二次挥手是立即返回的,告诉客户端我已经收到断开的请求,然后我接着做善后的工作,客户端也不会重试机制,会一直等着服务端善后的消息。也就是服务端FIN包。
四次是最优的方案,五次就浪费了。
为什么SYN/FIN不包含数据,却要消费一个序列号?
因为凡是需要对端确认的,一定要消耗TCP报文序列号。
SYN握手确认,FIN需要挥手的确认,所以需要消耗一个序列号,因为这两个操作可能会由于网络的原因导致丢包,而服务端没有收到,可能客户端会重发,如果重发的时候又收到原有的包了,那么服务端可能会处理两次造成资源的浪费。
什么是半连接队列,什么是SYN Flood攻击?
客户端在第一次请求握手的时候,服务端会把客户端的信息存到一个半连接队列里面去,等到第三次握手的时候会从半连接队列里面拿出来放到全连接队列里面去。
半连接队列是一个预处理,可以提高链接建立的效率,也可以临时存储未完成的连接,确保在连接完成之前不会丢失已经收到的连接请求。
如果客户端大量伪造IP发送SYN包,客户端回复ACK+SYN去到一个未知的IP地址,久而久之势必会造成服务端大量的连接处于SYN_RCVD状态,半连接队列的大小也是有效的,如果半连接队列满了,就无法响应正常的请求了,会出现无法处理正常请求的情况。
我们可以防御,有以下手段:
- SYN Cookies,启用SYN Cookie,通过SYN Cookies,服务器可以推迟分配资源,直到客户端带有正确的cookie的ACK确认的时候再去分配资源。
- 增加链接队列的大小和降低超时时间,有助于服务器更好的处理链接请求。
- 负载均衡:使用负载均衡服务器来分发链接到多个服务器,有助于平衡负载均衡,防止单一服务器成为攻击目标。
- DDOS防火墙,使用专门设计用于防御分布式拒绝服务DDOS攻击的硬件设备或云服务器,这些设备可以检测和过滤大规模的攻击流量。
说说TCP快速打开(TFO)的原理。
TFO :TCP Fast Open,快速打开。
TFO是在原来TCP协议上的扩展协议,它的主要原理就是在发送一个SYN包的时候就可以传数据了,但是要求当前客户端已经完成过正常的三次握手了。快速打开分两个阶段,请求Fast Open Cookie和真正开始TCP Fast Open。
请求Fast Open Cookie阶段
- 客户端在发送SYN包的时候,在header中携带上cokkie的request,此时cookie肯定是空的。
- 服务端就知道你是通过cookie的方式进行传输,就生成cookie,并放在了header里面发给了客户端。
- 然后客户端把cookie存储到了本地。
TCP Fast Open
- 在下一次链接的时候,不单单只是请求SYN,还把之前设置的Cookie带上去,并把数据带上去。
- 服务端接收到cookie之后,校验合法性,如果合法就处理包,然后发response。
- 如果Cookie不合法,就丢弃包,然后走正常的三次握手。
TFO优势
一个最显著的优点就是利用握手去除一个RTT,就是往返的时间。 如上图:
- 左边为正常请求,右边为带cookie的TFO请求,第一次请求的时候都是两次RTT。
- 第二次请求的时候,左边还需要进行三次握手后,再发起http get请求。
- 右边直接在第一次握手的时候就发起请求了,然后服务端校验之后直接发送response了,少了一次rtt。
除此之外,还能有效防止SYN的flood攻击。
TCP报文中的时间戳有什么作用?
在头部信息里面的选项里面,一般可以添加时间戳。时间戳选项有以下构成:
由类别、长度、发送时间戳和回显时间戳。
TCP时间戳一般来说解决两大问题:
- 计算往返的时延 RTT。
比如在发送的时候,由于网络抖动发生了重传,那么这个时延怎么计算?在t3的时候,是t3-t1,还是t3-t2?好像怎么计算都不太合适,因为没办法知道收到的ack是t1还是t2的。
如果启用了时间戳机制,就可以通过发送时间戳和回显时间戳来计算了,因为t1和t2的发送时间戳肯定不一样。
- 防止序列的回绕问题。
回绕,TCP的序列号用32bit表示,如果2^32字节的数据传输后,序列号就会溢出,溢出就要重新计算,从0开始,这就是回绕。也就是TCP的窗口经过窗口缩放可以最高到1GB,在高速网络中,序列号在很短的时间内就会重复使用。
- 假设每次发送的数据都是1GB,一起发送了6个数据包。在发送第二个数据包的时候,由于某些原因需要重传,并不是丢失,而是重传。
- 第二个原包在t7的时候,又收到了,也是1G:2G的数据,这就是迷途的数据包。所以没办法区分是迷途的t2还是t7,因为都是1G:2G的数据。
如果有时间戳的概念,就会区分出来,t2和t6的时间戳,肯定是不一样的。
超时重传时间如何计算?
TCP是具有超时重传机制的,如果间隔一段时间都没有等到数据包的回复,就会重传这个数据包,这个重传间隔也是超时重传时间,简称RTO。
重传的时间不太好设置
- 如果RTO设置的太小,服务端还没处理完请求,重传又来了,导致没必要的重传。
- 如果RTO设置的太大,这个包实际上已经丢了,还没到服务器,但是又等到了很久才重传。
所以如何设置?
经典方法
适用于RTT波动较小的情况,就是往返时延。
一个最简单的想法就是取平均值,比如第一次RTT为500ms,第二次RTT为800ms,那么第三次发送的时候,各让一步取平均值RTO为650ms。 经典算法引入了平滑往返时间,经过平滑后的RTT的值,没测量一次RTT就对SRTT做一次更新计算,公式如下:
α是平滑因子,建议值就是0.8-0.9.
- 当α趋近于1的时候,SRTT越接近上一次的SRTT值,与新的RTT值的关系越小,表现出来就是对短暂的时延变化不敏感。
- 当α趋近于0的时候,1-α就趋近于1,SRTT越解决新采样的RTT值,与旧的SRTT值关系越小,表现出来就是对时延变化更敏感,能够更快速的跟随时延的变化而变化。更加依赖于上一次时间。比较保险。
TCP的流量控制如何处理?
对于发送端和接收端来说,TCP需要把发送的数据放到发送缓冲区,接收的数据放到接收缓冲区。
而流量控制要做的事情,就是在通过接收缓存区的大小,控制发送端的发送,因为是服务端告诉客户端当前自己所能够处理的容量。如果服务端的接收缓存区满了,就不能继续发送了。
为了控制发送端的速率,接收端会告知客户端自己的接收窗口,也就是接收缓冲区空闲的部分。客户端每次发送报文的时候,服务端在ACK确认的时候,会把窗口的大小带上去。
接收的缓冲区,有两部分的数据:
- 接收窗口是多少,还未接收的数据,还有多少窗口可以接收。
- 缓存区已经接收的数据,但是还未处理的数据。
发送端的数据包的状态如下:
- 0-31为已经发送且已经确认的数据。
- 32-45为已经发送,但是还未确认的数据。
- 46-51为未发送,但是接收端可以接收的数据,也就是窗口大小,接收端有空间接收。
- 52- 为未发送的数据,当前时间点也不可以发送,接收端没有空间接收了。
如果是滑动窗口的话,滑动窗口的大小为32-51,也就是已发送未确认、未发送但可以接收这个区间的。
为什么是滑动窗口呢?因为这个区间并非是固定的,你可以不断的收到确认的数据,比如收到32字节确认了,那么窗口就是33-52,会不断的滑动。这个滑动窗口的大小,就是20个大小。
滑动窗口大小的确认,是由慢启动和拥塞控制,和拥塞控制门限值。一起来控制的。
但是可能会有几种情况:
发送端速度比较慢的情况。
也就是发送窗口包含了20个字节,但是还有一些是即将发送的,也就是服务端有窗口可以接收。
发送端速度比较快的情况。
如果发送窗口还是20的话,那么已发送但还未确认的消息会占满整个窗口,未发送的数据因为服务端没有空间接收,需要等消息确认后才可以移动发送。
如何理解keep-alive原理
如果一个TCP连接上去后,通信双方都不向对方发送数据,那么TCP连接就不会有任何数据交互。
假设应用程序服务端是一个web服务,端口为8080,客户端发送三次握手后故障宕机或者被踢掉网线,对于web服务器而言,下一个数据包将永远无法到来,但是它一无所知的。
TCP协议的设计者考虑到这种检测长时间死连接的需求,就设计了keepalive机制。
它的作用就是探测对端的连接是否有失效,也就是定时发送探测报文来探测对端是否存储,不过默认情况需要7200s(2小时)没有数据包交互才会发送keepalive探测包,但这个时间太久了,正常程序都受不了。
所以常用的做法一般是在应用层做心跳机制,也就是假设每2s去发送一个心跳包。
TCP中的端口号,聊聊。
端口号为port,口岸的意思。
端口号在分层里面处于传输层。且存在TCP头里面,分别有两个端口号,一个是源端口,一个目标端口,比如你访问百度,首先会为你起一个临时端口,就是源端口,然后目标端口就是百度的80端口。
端口号是两个字节整数来表示,一台主机最大允许65536个端口号,常用的比如80/6379dd .
熟知端口号的范围为0-1023.比如80/443/22等等
已登记的端口号范围在1024-49151之间,比如3306/6379等等。
临时端口号:在机器上建立网络连接,在你本机需要源端口号,一般源端口号都是临时端口,在49152-65525之间。
TCP和UDP的区别
TCP是一个面向连接的,需要三次握手,可靠的、基于字节流的传输层协议。
而UDP是一个面向无连接的传输层协议。两个都是传输层的协议。
面向连接
所谓的连接,指的是客户端和服务器的连接,在双方互相通信之前,TCP需要三次握手建立连接,而UDP没有相应建立连接的过程。
可靠性 tcp话了非常多的功夫保证链接的可靠,主要有:
1:TCP有状态,TCP会精确记录哪些数据发送了,哪些数据被对方接收了,哪些没有被接收到,从而保证数据包的按序到达,不允许半点差错。
2:TCP可控制,意识到包丢失了或者网络环境不佳,TCP会根据具体情况调整自己的行为,比如滑动窗口限流或者重发,控制自己的发送速度或者重发。
场景问题。
我们访问www.baidu.com 经历了什么?
当你从本地访问www.baidu.com时,整个过程可以分为以下步骤:
DNS解析:
本地DNS服务器查询:
本地DNS服务器接收到查询请求后,它会首先查看自身的缓存,如果有www.baidu.com的IP地址,就返回给本地系统。如果没有,本地DNS服务器会向根DNS服务器发起查询。
根DNS服务器查询:
本地DNS服务器向根DNS服务器查询时,根DNS服务器返回一个指向顶级域(.com)DNS服务器的IP地址。
顶级域DNS服务器查询:
本地DNS服务器向顶级域DNS服务器查询时,顶级域DNS服务器返回一个指向下一级域(baidu.com)DNS服务器的IP地址。
域名服务器查询:
本地DNS服务器向baidu.com的DNS服务器查询时,该服务器返回www.baidu.com的IP地址。
建立TCP连接:
本地系统使用HTTP协议向 www.baidu.com 的IP地址发起请求。为建立TCP连接,它会进行三次握手(SYN,SYN-ACK,ACK)。
HTTP请求:
一旦TCP连接建立,本地系统向服务器发送一个HTTP GET请求,请求获取 www.baidu.com 的网页内容。
服务器处理请求:
服务器接收到HTTP请求后,会根据请求的路径(/)返回相应的网页内容。这可能涉及到服务器端的应用逻辑、数据查询等操作。
服务器响应:
服务器将网页内容打包成HTTP响应,通过TCP连接发送回本地系统。
浏览器渲染:
本地系统的浏览器接收到服务器的HTTP响应后,开始解析HTML、CSS、JavaScript等资源,并进行页面渲染,最终呈现给用户。
整个过程涉及了DNS解析、建立TCP连接、HTTP请求和响应等步骤。这是一个简化的描述,实际过程可能还包括缓存、负载均衡、CDN等优化手段。
确认号多少问题?
AB两个主机之间家里了一个TCP连接、A主机发给B主机两个TCP报文后,大小分别是500和300,第一个报文的序列号为200,那么B主机接收两个报文后,返回的确认号是多少呢?
如题,如果你发送了两个报文,大小为500和300,你最开始的序列号为200,
那么在发送第一个报文的时候,占用的序列号就是200-(200+500-1),就是200-699之间的序列号范围。
第二次发送报文的序列号就是700-999之间,因为每次回复后的数据都会+1,所以B朱姐在接收两个报文后,确认号就是1000.
收到IP包解析之后,如何知道投递到上层的哪个协议?UDP或TCP
我们知道,先从应用层+HTTP协议报文头到传输层加TCP头到网络层加IP头到数据链路层加以太网头。
然后到服务端的时候,会一层层的拨开,等到拨开到网络层的时候,就拨开了IP头了。
而IP头里面,会有一个协议,8位的。
这个8为协议,就是定义了对应的协议。如果是ICMP协议的话,就是1,如果是TCP协议的话就是6,如果是UDP协议就是7.
应用程序如何提供自己的记录标识?
TCP提供的是字节流服务,而流都是没有边界的。这样收发双发都不保持记录的编辑的,应用程序如何提供自己的记录标识呢?
在应用程序中,使用自己约定的规则来表示消息的边界,比如redis中使用了回车+换行符 "\r\n" ,其他的消息也可以使用特定的特殊的标识。
如果说要你来设计一个QQ,在网络协议上你会如何考虑设计?
首先要登录,等了一般是采用TCP协议或者HTTP协议。然后发送消息,一般是采用UDP协议,内网传文件采用P2P技术了。
- 等了过程,客户端Client采用TCP协议向服务器server发送信息,然后用http协议下载信息,登录之后,再用一个TCP连接来保证在线状态,使用心跳维护长链接。
- 和好友发消息,客户端采用UDP协议,但是需要通过服务器转发,当然为了保证消息的可靠性,需要再上层协议保证可靠传输,如果发送失败,客户端会提示消息发送失败,并可以进行重新发送。
- 如果是两个客户端进行文件传输,一般是通过P2P的技术。
工具讲解
讲讲telnet的用法
检查端口是否打开,telnet的最大一个作用就是检测一个端口是否处于打开状态,使用命令就是telnet [domainnane or ip] [port]
.
[root@VM-4-9-centos ~]# telnet www.baidu.com 80
Trying 153.3.238.102...
Connected to www.baidu.com.
Escape character is '^]'.
[root@VM-4-9-centos ~]# telnet www.baidu.com 808
Trying 153.3.238.102...
讲讲netstat的用法
netstat命令主要用于显示各种网络相关的信息。
如:
- a 显示所有选项,默认不显示listen相关
- t 显示tcp相关选项
- u 显示udp相关选项
- n 拒绝显示别名,能显示数字全部显示数字
- l 仅仅列出在listen监听的服务状态
- p 显示建立相关连接的程序名
- r 显示路由信息,路由表
- e 显示扩展信息,例如uid等
- s 按照各个协议进行统计
讲一讲tcpdump的用法。
tcpdump是一个命令行的网络流量分析工具,是一个抓包的工具,在Linux下面可以抓取tcp的包。非常强大。
比如我们访问www.baidu.com 然后来抓包看看三次握手和四次挥手的过程 。
注意,需要先启动tcpdump,然后再起一个客户端进行curl。
先curl访问
[root@VM-4-9-centos ~]# curl www.baidu.com
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> </html>
在进行抓包 tcpdump
[root@VM-4-9-centos ~]# tcpdump -i any host www.baidu.com
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
12:34:40.451834 IP VM-4-9-centos.49064 > 180.101.50.188.http: Flags [P.], seq 4007169568:4007169573, ack 1349379685, win 229, length 5: HTTP
12:34:40.838802 IP 180.101.50.188.http > VM-4-9-centos.49064: Flags [.], ack 7, win 908, length 0
12:34:40.838842 IP VM-4-9-centos.49064 > 180.101.50.188.http: Flags [.], ack 29, win 229, length 0
12:34:40.838901 IP VM-4-9-centos.49064 > 180.101.50.188.http: Flags [F.], seq 7, ack 30, win 229, length 0
12:34:40.847959 IP VM-4-9-centos.49064 > 180.101.50.188.http: Flags [.], ack 30, win 229, options [nop,nop,sack 1 {29:30}], length 0
12:34:42.613850 IP VM-4-9-centos.55754 > 180.101.50.242.http: Flags [P.], seq 1:78, ack 1, win 229, length 77: HTTP: GET / HTTP/1.1
12:34:42.633659 IP VM-4-9-centos.55754 > 180.101.50.242.http: Flags [.], ack 2783, win 273, length 0
12:34:43.864222 IP 180.101.50.188.http > VM-4-9-centos.49064: Flags [R], seq 1349379714, win 0, length 0
12:34:45.646689 IP 180.101.50.242.http > VM-4-9-centos.55754: Flags [R], seq 3723550894, win 0, length 0
wireshark工具
通过tcpdump不太友好,wireshark是一个比较友好的Windows的工具。