为何要三次握手四次挥手

像TCP协议同样。它之因此这么复杂,那是由于它秉承的是“性恶论”。它自然认为网络环境是恶劣的,丢包、乱序、重传,拥塞都是常有的事情,一言不合就可能送达不了,于是要从算法层面来保证可靠性。面试

拥塞控制和流量控制的区别,流量控制是针对端的,拥塞控制是针对网络的。算法

包的序号。为何要给包编号呢?固然是为了解决乱序的问题。不编好号怎么确认哪一个应该先来,哪一个应该后到呢。编号是为了解决乱序问题。既然是社会老司机,作事固然要稳重,一件件来,面临再复杂的状况,也临危不乱。编程

还应该有的就是确认序号。发出去的包应该有确认,要否则我怎么知道对方有没有收到呢?若是没有收到就应该从新发送,直到送达。这个能够解决不丢包的问题。做为老司机,作事固然要靠谱,答应了就要作到,暂时作不到也要有个回复。缓存

TCP是靠谱的协议,可是这不能说明它面临的网络环境好。从IP层面来说,若是网络情况的确那么差,是没有任何可靠性保证的,而做为IP的上一层TCP也无能为力,惟一能作的就是更加努力,不断重传,经过各类算法保证。也就是说,对于TCP来说,IP层你丢不丢包,我管不着,可是我在个人层面上,会努力保证可靠性。服务器

接下来有一些状态位。例如SYN是发起一个链接,ACK是回复,RST是从新链接,FIN是结束链接等。TCP是面向链接的,于是双方要维护链接的状态,这些带状态位的包的发送,会引发双方的状态变动网络

窗口大小。TCP要作流量控制,通讯双方各声明一个窗口,标识本身当前可以的处理能力,别发送的太快,撑死我,也别发的太慢,饿死我。负载均衡

除了作流量控制之外,TCP还会作拥塞控制,对于真正的通路堵车不堵车,它无能为力,惟一能作的就是控制本身,也即控制发送的速度。不能改变世界,就改变本身嘛。tcp

全部的问题,首先都要先创建一个链接,因此咱们先来看链接维护问题:函数

为何序号要打乱????动画

为了维护这个链接,双方都要维护一个状态机,在链接创建的过程当中,双方的状态变化时序图就像这样

1.三次握手,第一次客户端发起主动链接的状态信息,seq,syn,syn=1表明发起链接

第二次,服务端发给应答,发送ack=来的seq+1,以及服务端的seq号

第三次,客户端给服务端发送ack=来的seq+1

只有第一次的话,不能创建链接,client不知道server是否能收到本身信息,而且client还不知道服务端的序列号。

只有两次的话,服务端不知道客户端是否收到了本身的seq号,因此也不敢冒然发信息,好比服务端确认了client端收到了本身的信息,才能创建链接。由于也有可能在二次握手以后,网络断了,因此服务端必须确认客户端知道了本身的序列号,才能够发送数据。“对于B来讲,这个应答包也是一入网络深似海,不知道能不能到达A。这个时候B天然不能认为链接是创建好了,由于应答包仍然会丢,会绕弯路,或者A已经挂了都有可能。”“A创建链接的时候,请求包重复发了几回,有的请求包绕了一大圈又回来了,B会认为这也是一个正常的的请求的话,所以创建了链接

为啥要有第三次握手,由于b不知道这个包是否是a的撩骚包,也许是a发送的多个撩骚包中的一个,这是个迟来的包,不必接受的。

“三次握手”的目的是“为了防止已失效的链接请求报文段忽然又传送到了服务端,于是产生错误”

B发送的应答可能会发送屡次,可是只要一次到达A,A就认为链接已经创建了,由于对于A来说,他的消息有去有回。A会给B发送应答之应答,而B也在等这个消息,才能确认链接的创建,只有等到了这个消息,对于B来说,才算它的消息有去有回。


好在大部分状况下,A和B创建了链接以后,A会立刻发送数据的,一旦A发送数据,则不少问题都获得了解决。例如A发给B的应答丢了,当A后续发送的数据到达的时候,B能够认为这个链接已经创建,或者B压根就挂了,A发送的数据,会报错,说B不可达,A就知道B出事情了。

固然你能够说A比较坏,就是不发数据,创建链接后空着。咱们在程序设计的时候,能够要求开启keepalive机制,即便没有真实的数据包,也有探活包。


 TCP 握手的异常状况

三次握手的正常发包和应答,以及双端的状态扭转咱们已经讲了,接下来就来看看在这三次握手的过程当中,出现的异常状况。

1. 客户端第一个「SYN」包丢了。

若是客户端第一个「SYN」包丢了,也就是服务端根本就不知道客户端曾经发过包,那么处理流程主要在客户端。

而在 TCP 协议中,某端的一组「请求-应答」中,在必定时间范围内,只要没有收到应答的「ACK」包,不管是请求包对方没有收到,仍是对方的应答包本身没有收到,均认为是丢包了,会触发超时重传机制。

因此此时会进入重传「SYN」包。根据《TCP/IP详解卷Ⅰ:协议》中的描述,此时会尝试三次,间隔时间分别是 5.8s、24s、48s,三次时间大约是 76s 左右,而大多数伯克利系统将创建一个新链接的最长时间,限制为 75s。

也就是说三次握手第一个「SYN」包丢了,会重传,总的尝试时间是 75s。


2. 服务端收到「SYN」并回复的「SYN,ACK」包丢了。

此时服务端已经收到了数据包并回复,若是这个回复的「SYN,ACK」包丢了,站在客户端的角度,会认为是最开始的那个「SYN」丢了,那么就继续重传,就是咱们前面说的「错误 1 流程」。

而对服务端而言,若是发送的「SYN,ACK」包丢了,在超时时间内没有收到客户端发来的「ACK」包,也会触发重传,此时服务端处于 SYN_RCVD 状态,会依次等待 3s、6s、12s 后,从新发送「SYN,ACK」包。

而这个「SYN,ACK」包的重传次数,不一样的操做系统下有不一样的配置,例如在 Linux 下能够经过 tcp_synack_retries 进行配置,默认值为 5。若是这个重试次数内,仍未收到「ACK」应答包,那么服务端会自动关闭这个链接。

同时因为客户端在没有收到「SYN,ACK」时,也会进行重传,当客户端重传的「SYN」收到后,会当即从新发送「SYN,ACK」包。


3. 客户端最后一次回复「SYN,ACK」的「ACK」包丢了。

若是最后一个「ACK」包丢了,服务端由于收不到「ACK」会走重传机制,而客户端此时进入 ESTABLISHED 状态。

多数状况下,客户端进入 ESTABLISHED 状态后,则认为链接已创建,会当即发送数据。可是服务端由于没有收到最后一个「ACK」包,依然处于 SYN-RCVD 状态。

那么这里的关键,就在于服务端在处于 SYN-RCVD 状态下,收到客户端的数据包后如何处理?

这也是比较有争议的地方,有些资料里会写到当服务端处于 SYN-RCVD 状态下,收到客户端的数据包后,会直接回复 RTS 包响应,表示服务端错误,并进入 CLOSE 状态。

可是这样的设定有些过于严格,试想一下,服务端还在经过三次握手阶段肯定对方是否真实存在,此时对方的数据已经发来了,那确定是存在的。

因此当服务端处于 SYN-RCVD 状态下时,接收到客户端真实发送来的数据包时,会认为链接已创建,并进入 ESTABLISHED 状态。


前面一直在说正常的异常逻辑,双方都还算友善,按规矩作事,出现异常主要也是由于网络等客观问题,接下来讲一个恶意的状况。

若是客户端是恶意的在发送「SYN」包后,并收到「SYN,ACK」后就不回复了,那么服务端此时处于一种半链接的状态,虽然服务端会经过 tcp_synack_retries配置重试的次数,不会无限等待下去,可是这也是有一个时间周期的。

若是短期内存在大量的这种恶意链接,对服务端来讲压力就会很大,这就是所谓的 SYN FLOOD 攻击


2.四次挥手

创建一个链接须要三次握手,而终止一个链接要通过 4次握手。这由 TCP的半关闭(halfclose)形成的。既然一个TCP链接是全双工(即数据在两个方向上能同时传递),所以每一个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个 FIN来终止这个方向链接。当一端收到一个 FIN,它必须通知应用层另外一端几经终止了那个方向的数据传送。
收到一个FIN只意味着在这一方向上没有数据流动。一个TCP链接在收到一个FIN后仍能 发送数据。所以须要两个方向都收到FIN。

我认为是由于tcp是全双工的,两边都有读端写端,我认为正常管道的关闭原则是写端先关闭,哈哈~~~

简单说就是不写了,不读而且确认下,不写了而且让别人确认下。

半关闭状态是A确认了B也关闭了B的读端,伤心欲绝~~~此时的A确认B再也不读他的数据了,因此才进入办关闭状态,由于和B还保持着半条链接,单工模式,B能够写给A呢。

 

A主动方:关闭写端,告诉B我不写了(我还能读)FIN=1

B被动方:关闭读端,向应用程序交付EOF,告诉A,既然你不写了,那我关闭读端了(我还能写)---ACK

B被动方也主动一次关闭写端:关闭写端,告诉A我不写了FIN=1

A被动方如今也被动关闭读端:完全关闭,告诉B我已经把读写都切断了


为啥四次握手呢,2/3能够合在一块儿不,我以为不能合在一块儿,是由于合在一块儿表明着B关闭读端,以后嘟嘟嘟,又关闭写端,万一A还有数据要读,就读不到了。给A留缓冲。好了,我没忽悠本身信

B回复确认以后,A仍是能够读的,B也能够写,B写完了以后,告诉A,不写了,


https://zhuanlan.zhihu.com/p/140434455    有空看下常见面试tcp的

https://www.zhihu.com/topic/19614019/hot   很是好,有动画


咱们知道,TCP协议是一种面向链接的、可靠的、基于字节流的运输层通讯协议,并且 TCP 是全双工模式。

对于初学者来讲,定义太枯燥、无味,其实意思就是你和你女友聊天是面向链接的,只有链接起来才能够通讯的。

可靠就是你发送的信息能够保证送达到对方,全双工意思就是你不只能够给你女友发消息,并且她也能够给你发信息。

为何非要进行 TCP 四次分手?咱们接着上回说到,你如今和第二个女孩子恋爱了,忽然有一天发现第一个女孩子是由于没有收到你的表白而错过了在一块儿的时机,那么你要和第二个女孩子分手。

那过程对应在 TCP 四次分手是怎么样子的?
 


为什要有 2MSL 等待延迟

对应这样一种状况,最后客户端发送的 ACK=1 给服务端的过程当中丢失了,服务端没收到,服务端怎么认为的?我已经发送完数据了,怎么客户端没回应我?是否是中途丢失了?

而后服务端再次发起断开链接的请求,一个来回就是 2MSL,这里的两个来回由那一个来回组成的?

客户端给服务端发送的 ACK=1 丢失,服务端等待 1MSL 没收到,而后从新发送消息须要 1MSL。

若是再次接收到服务端的消息,则重启 2MSL 计时器,发送确认请求。客户端只需等待 2MSL,若是没有再次收到服务端的消息,就说明服务端已经接收到本身确认消息;此时双方都关闭的链接,TCP 四次分手完毕。

若是双方创建链接,一方出问题

若是双方创建链接,一方出问题怎么办?为了防止出现上述恋爱故事中千年等一回的情况,已经创建链接,可是服务端一直等待接收,发送端出现问题一直不能发送。

因此设计一个保活的计时器,若是一方出现问题,另外一方过了这个计时器的时间,就发送试探报文,之后每隔 75 秒发送一次。若一连发送 10 个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭链接。



做者:Linux干货铺
连接:https://zhuanlan.zhihu.com/p/138676797
来源:知乎
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。


 

 

在创建Socket的时候,应该设置什么参数呢?Socket编程进行的是端到端的通讯,每每意识不到中间通过多少局域网,多少路由器,于是可以设置的参数,也只能是端到端协议之上网络层和传输层的。

在网络层,Socket函数须要指定究竟是IPv4仍是IPv6,分别对应设置为AF_INET和AF_INET6。另外,还要指定究竟是TCP仍是UDP。还记得我们前面讲过的,TCP协议是基于数据流的,因此设置为SOCK_STREAM,而UDP是基于数据报的,于是设置为SOCK_DGRAM。

 

TCP的服务端要先监听一个端口,通常是先调用bind函数,给这个Socket赋予一个IP地址和端口。为何须要端口呢?要知道,你写的是一个应用程序,当一个网络包来的时候,内核要经过TCP头里面的这个端口,来找到你这个应用程序,把包给你。为何要IP地址呢?有时候,一台机器会有多个网卡,也就会有多个IP地址,你能够选择监听全部的网卡,也能够选择监听一个网卡,这样,只有发给这个网卡的包,才会给你。

当服务端有了IP和端口号,就能够调用listen函数进行监听。在TCP的状态图里面,有一个listen状态,当调用这个函数以后,服务端就进入了这个状态,这个时候客户端就能够发起链接了。

在内核中,为每一个Socket维护两个队列。一个是已经创建了链接的队列,这时候链接三次握手已经完毕,处于established状态;一个是尚未彻底创建链接的队列,这个时候三次握手还没完成,处于syn_rcvd的状态。

接下来,服务端调用accept函数,拿出一个已经完成的链接进行处理。若是尚未完成,就要等着。

在服务端等待的时候,客户端能够经过connect函数发起链接。先在参数中指明要链接的IP地址和端口号,而后开始发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功,服务端的accept就会返回另外一个Socket。

这是一个常常考的知识点,就是监听的Socket和真正用来传数据的Socket是两个,一个叫做监听Socket,一个叫做已链接Socket

链接创建成功以后,双方开始经过read和write函数来读写数据,就像往一个文件流里面写东西同样。


在域名和IP的映射过程当中,给了应用基于域名作负载均衡的机会,能够是简单的负载均衡,也能够根据地址和运营商作全局的负载均衡。

~~~CDN的诞生

DNS的两项功能,第一是根据名称查到具体的地址,另一个是能够针对多个地址作负载均衡,并且能够在多个地址中选择一个距离你近的地方访问。

另外,有的运营商会把一些静态页面,缓存到本运营商的服务器内,这样用户请求的时候,就不用跨运营商进行访问,这样既加快了速度,也减小了运营商之间流量计算的成本。在域名解析的时候,不会将用户导向真正的网站,而是指向这个缓存的服务器。