开始客户端处于 close 初始关闭状态
客户端:发送SYN=1(表示请求连接),seq=x(申明自己序号为x)
之后客户端处于 syn_sent 表示已经发送syn报文
开始服务器处于listen 监听状态,可以接受连接
服务器:响应SYN=1(表示可以连接),ACK=1 (响应确认连接),ack=x+1(前x收到,下次从x+1开始发送(下一次访问的序列号)),seq=y(声明自己序号为y)
之后服务器处于syn-rcvd 表示接受到了syn报文
客户端:发送ACK=1(确认连接), ack=y+1, seq=x+1 (前y个已经收到,下一次从y+1开始发送)
之后客户端和服务器都处于 established,established 表示已经连接了
为什么要三次握手,两次不行吗?
结论:
为了防止已经失效的连接请求突然再发到服务端,然后服务端一直等待,浪费资源。
模拟场景:
假设只有两次,客户端向服务器发送请求建立连接的报文,但是这个报文再传输过程中在某个网络节点滞留了很长时间,甚至到连接释放以后才到达服务端,导致其失效,那么这个失效的报文服务端收到之后误认为是一个新的请求建立连接的报文然后向客户端发送确认,这样在两次握手的前提下,一个新的连接就建立成功了,但是客户端不会理会这样的失效报文带来的确认,那么服务端就会一直在等待,浪费资源。
以客户端向服务器请求关闭为例(当然亦可以是服务器向客户端,请求关闭)
客服端开始处于 established状态 表示已经连接了
客户端发送:FIN=1(表示请求断开),seq=m
之后客户端处于fin-wait-1 表示等待对方的FIN报文
开始服务器处于 established 表示已连接了
服务器发送:ACK=1,ack=m+1 (前m个已经收到,下次m+1) seq=n
之后服务器处于close-wait等待关闭 客户端处于fin-wait-2 表示等待对方的FIN报文
服务器发送:FIN=1 ACK=1 ack=m+1 seq=w
之后服务器处于处于last-ack 等待对方ack报文
客户端发送ACK=1,ack=w+1,seq=m+1
客户端处于time-wait 表示收到了对方的报文,等待2msl 即可回到关闭状态
服务器处于close状态
成功的会2msl后客户端处于close状态
为什么需要四次挥手?
结论:
为了保证数据的完全传输。
解释:
TCP是全双工模式,是单方面断开连接,只有双方都断开才会释放本次连接。
那么当客户端请求断开连接,服务器收到立马回复确认,只是表明服务器知道客户端没有数据发送了,但是服务器本身还是可以继续发数据接收数据的。此时,客户端会处于fin-wait半关闭状态,也就意味着它不能发送数据但是依旧能接收数据。只有当服务端发送完数据之后也请求断开连接,客户端收到立马回复确认之后服务器才能断开,而这时,客户端等待2msl之后也断开。两端都关闭连接,全双工TCP连接真正释放。
注意:实际上也很有可能是三次挥手。因为如果服务端没有数据要发送的话就可以在客户端请求断开连接之后,服务端发送确认同时也请求断开连接,FIN和ACK同时发送,这时客户端接收并关闭连接,两端都关闭连接,本次连接释放。
为什么需要2msl?
两个理由:1)保证A发送的最后一个ACK报文段能够到达B。2)防止“已失效的连接请求报文段”出现在本连接中。
1)当A发送ACK到B(1msl),如果B没有收到会从发FIN(1msl)。故2msl内A没有收到FIN表示B已经收到ACK,反之没有收到,需要重发
2)A在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。
作用:防止过多的数据诸如网络,使网络负载过大,让网络能够承载现在的负荷,实现供求平衡
控制方法:
接受窗口RWin -- 根据接收方自身读取速度以及接收缓存的大小来设定(在缓冲区存在)
拥塞窗口CWin -- 发送端根据自己估计的网络拥塞成都设置的窗口值(假想的实际上不存在)
设置原则:
网络没有拥塞,拥塞窗口设置大一些保证更多的数据在网络条件好的时候发出去,
网络出现拥塞,拥塞窗口就小一些,以便当前网络上注入的数据少一些,
从而保证传输。
发送窗口的上限值 = Min[ CWin,RWin ]
发送端发送多少数据以接收窗口和拥塞窗口中较小的为基准
当 RWin < CWin 时,是接收端的接收能力限制发送窗口的最大值。
当 RWin > CWin时,则是网络的拥塞情况限制发送窗口的最大值。
拥塞控制具体实现:
1.慢启动:指数增长,对拥塞窗口进行增加,保证尽可能的传输数据,每次收到一个确认就将窗口增大,增加到阈值就进入拥塞避免。
2.拥塞避免:使用加法增大算法,每经过一个往返时间RTT才会将拥塞窗口+1而不是加倍,当增加到当前网络能够承受的最大值的时候就要进行乘法减小算法,将阈值变成当前最大窗口(网络能够负载的最大能力)的一半,然后再将拥塞窗口减到1,重新进行慢启动,拥塞避免的过程
3.快重传:收到三个重复确认,不用等到重传计时器到期就直接发送丢失的报文
4.快恢复:一旦发生丢失报文的情况,就悲观的认为当前网络已经到了负载能力最大的地步,就直接开始执行拥塞避免,而不用等到慢启动到达门限值之后再启动拥塞避免。
简述:就是给返回报文字段中添加一个流量窗口大小的字段,从而告知发送方,下一次发送数据的时候需要根据流量窗口的大小来发送数据,从而防止发送方发送过快而接收方根本读不过来。
实现流量控制的方法:
序号,确认,超时重传,滑动窗口等可靠性传输机制
滑动窗口:发送方的缓冲区一部分是窗口,那么窗口里放的数据是允许发送的数据和已发送但未确认的数据,一旦发送并确认,窗口就会向发送方方向挪动,以便发送下一个数据。
接收方的缓冲区同样有一部分是窗口,存放的是允许接收和已接收但未确认的数据,一旦接收并确认,接收窗口就会向发送方方向(相反于自身方向)滑动。
发送窗口满就停止:当接收方一直不确认,发送方一直发送允许发送的数据,发送窗口中就全部都是已发送但未确认的数据,发送窗口就会变满,此时就停止发送。
接收方接收能力弱:接收方来不及接收数据就会通知发送方缩小窗口。
乱序报文解决:当接收方接收到乱序的报文会先将它存在窗口中并启动超时重传,等待发送方发送缺少的数据,最后再进行重组。
丢失报文解决:发送过程中报文丢失,接收方无法确认,那发送方就会一直发送,直到发送方窗口变满就会重新把丢失的报文发一遍。
上述情况产生的问题:
RWin=0是因为接收方缓冲区满了,就是读取比较慢,等接收方读取后,接收方缓冲区就空下来,
这时候接收方向发送方发送RWin=400,但是很不巧这个报文段丢了,发送方一直在等待接收方说它空下来的消息,但是接收方不知道自己之前发送的丢了,所以也一直在等待发送方发送数据?
尴尬。。。怎么办?
别担心,持续计时器!!!!
只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器来预防发生上述问题。
工作原理:若持续计时器设置的时间到期就发送一个窗口探测报文段携带一个字节的数据。
就是说接收方给发送方说:你等下再发,我这窗口满了,我要是窗口空下来我就给你说。
发送方就一直等着也不是办法,所以就主动给接收方打个电话问一下:你看现在行不行?
接收方要是窗口空下
来就给发送方发送确认报文段也就是当前的窗口值:
如果确认报文段中窗口字段值为0则发送方就再设置持续计时器,
不是0就继续进行正常的发送和接收
当然如果老是0,发送方就不自己打电话问了,就设置一个周期,周期性发送探测
|
TCP |
UDP |
连接 |
面向连接 只有在确认通信对端存在时才会发送数据, 从而可以控制通信流量的浪费 |
面向无连接 |
传输 |
面向字节流 把TCP数据看成一连串无结构的字节流 |
面向报文 没有拥塞控制, 网络拥塞不会导致源主机发送速率降低,对实时应用有用 |
可靠性 |
可靠性传输,保证正确性和数据顺序 无差错,不丢失,不重复,按序到达 (三大机制:流量控制,三握四挥,拥塞控制) |
不可靠,不保证正确性和数据顺序 尽最大努力进行交付,不保证可靠性交付 |
连接 |
点对点 |
支持一对一,一对多,多对多交互通信 |
开销 |
首部开销:20字节 |
首部开销:8字节 |
逻辑通 信信道 |
全双工的可靠信道 |
不可靠信道 |
为什么要可靠?TCP在网络环境下要么无法提供正常的通信质量,要么成本过高,所以我们在保证通信的时延和质量的条件下尽量降低成本从而改造UDP
思想:尽量让UDP往TCP的特性靠,同时也具有自己传输快,传输量大的特点。
做重传机制(分为两种):
发送者发起重传:(接收者如果收到报文要向发送者发送确认)
对于发送者发起的方式,一般情况下接收者会发送一个消息包的确认。发送者维护一个
计时器并重传那些在某个确定的时间段里没有收到确认的消息包。这一类型的协议容易
引起发送者溢出,因为要确认每一个发送的消息包。这种溢出现象被称为发送者(或者
ACK)内爆。
接受者发起重传:(接收者通过序号检查有没有报文丢失,从而让发送者重传)
对于接收者发起的方式,通信双方的接收者负责错误检测。在这个方式里,序列号被用
于检测消息包丢失。当检测到消息包丢失,接收者请求发送者重传消息包。采用这种方
法,如果消息包没有到达任何一个接收者,发送者容易因NACK溢出。这会引起发送者
的负载过高和过多的重传。这种现像被称为NACK内爆。Ramakrishnan et al.提出可以
使用定时器来限制消息包重传,从而避免NACK内爆。
RUDP
三角平衡关系:实时通信中存在的三角平衡关系
尽力可靠:通信的接收方要求发送方的数据尽量完整到达,但业务本身的数据是可以允许缺失的。例如:音视频数据、幂等性状态数据。
无序可靠:通信的接收方要求发送方的数据必须完整到达,但可以不管到达先后顺序。例如:文件传输、白板书写、图形实时绘制数据、日志型追加数据等。
有序可靠:通信接收方要求发送方的数据必须按顺序完整到达。
RUDP根据这三类需求以及制约关系来确定自己的通信模型和机制,找到通信的平衡点。
也是根据TCP的思想,使用重传,拥塞控制,流量窗口,但具体实现完了再学习。。。。。
TCP短连接:
客户端想服务器发起连接请求,服务器接收到请求,然后双方建立连接。
客户端向服务器发送消息,服务器回应客户端,然后读写一次就完成了,这时候双方任何一个都可以发起close操作。不过一般都是客户端先发起close操作,原因是一般服务器除特殊情况外不会回复完客户端之后立即关闭连接。短连接一般只会在客户端/服务器之间传递一次读写操作
优点:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段
TCP长连接:
客户端向服务器发起连接请求,服务器接收请求,双方建立连接。客户端与服务器完成一次读写之后,他们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。
TCP保活:
为服务器应用提供,服务器应用需要知道客户主机是否崩溃。如果客户端已经消失,服务器就保留了一个半开放的连接,保活功能就是试图在服务器端检测到这种半开放的连接
如果一个给定的连接在两个小时内没有任何动作,则服务器就向客户发送一个探测报文段,客户主机必须处于以下四个状态之一:
1.客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。
2.客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。
3.客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。
4.客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探查的响应。
长连接和短连接的产生在于客户端和服务器采取的关闭策略
心跳包:
每隔一段时间(TCP默认2小时)固定发一次包(内容一般是很小的包或者只包含包头)给客户端,从而告诉服务器客户端还活着。只要send或者recv一下,结果为零,则为掉线
三、TCP的11种状态转换
CLOSED:起始点,不在连接状态。可以主动打开连接,或者等待对端的连接。
-->收到“被动打开”报文,进入LISTEN状态。
-->收到“主动打开”报文,进入SYN_SENT状态。
-->收到任何报文段,发送RST报文段。
-->收到其它任何报文段,发出差错报文。
LISTEN:被动打开,TCP正在等待对端的连接请求。
-->收到“发送数据”报文,发送SYN报文段,进入SYN_SENT状态。
-->收到任何SYN报文段,发送SYN+ACK报文段,进入SYN_RECEIVED状态。
-->收到任何其它报文段或者报文,发送差错报文。
SYN_SENT:主动打开,发送完一个连接请求后等待回复。
-->超时,进入CLOSED状态。
-->收到SYN报文段,发送SYN+ACK报文段,进入SYN_RECEIVED状态。
-->收到SYN+ACK报文段,发送ACK报文段,进入ESTABLISHED状态。
-->收到任何其它报文段或者报文,发送差错报文。
SYN_RECEIVED:被动打开,接受连接请求以后进行确认同时也向对端发送连接请求发送,等待对方的回复。
-->超时,发送RST报文段,进入CLOSED状态。
-->收到ACK报文段,进入ESTABLISHED状态。
-->收到"关闭"报文,发送FIN报文段,进入FIN_WAIT_1状态。
-->收到RST报文段,进入LISTEN状态。
-->收到任何其它报文段或者报文,发送差错报文。
ESTABLISHED:三次握手完毕,TCP连接建立完成,可以传输数据。
-->收到FIN报文段,进入CLOSED_WAIT状态。
-->收到“关闭”报文,发送FIN报文段,进入FIN_WAIT_1状态。
-->收到RST或SYN报文段,发出差错报文。
-->收到数据或ACK报文段,调用输入模块。
-->收到“发送”报文,调用输出模块。
FIN_WAIT_1:四次挥手开始,主动关闭,发送断开连接请求,等待对端确认。
-->收到FIN报文段,发送ACK报文段,进入CLOSING状态(同时关闭)。
-->收到FIN+ACK报文段,发送ACK报文段,进入FIN_WAIT状态(?)。
-->收到ACK报文段,进入FIN_WAIT_2状态。
-->收到任何其它报文段或者报文,发送差错报文。
FIN_WAIT_2:接收对方确认,但未接受对方的断开连接请求。
-->收到FIN报文段,发送ACK报文段,进入TIME_WAIT状态。
CLOSING:主动关闭的一方本希望收到对方的ACK却收到了对方的断开连接请求。
-->收到ACK报文段,进入TIME_WAIT状态。
-->收到任何其它报文段或者报文,发送差错报文。
TIME_WAIT:对方确认后发起断开连接请求,需要等待2MSL保证正常关闭。
-->超时,进入CLOSED状态。
-->收到任何其它报文段或者报文,发送差错报文。
CLOSE_WAIT:被动关闭,确认对端的连接终止请求,但是未向对端发送连接终止请求(可能数据没传完)。
-->收到"关闭"报文,发送FIN报文段,进入LAST_ACK状态。
-->收到任何其它报文段或者报文,发送差错报文。
LAST_ACK:数据传完,向对端发起断开连接请求后等待确认。
-->收到ACK报文段,进入CLOSED状态。
-->收到任何其它报文段或者报文,发送差错报文。
CLOSED:终点,不在连接状态。可以主动打开连接,或者等待对端的连接。
(1)TIME_WAIT状态(假设客户端主动,服务器端被动)
从状态图中我们可以发现,执行主动关闭的那端(最终重传ACK的那端)会经历这个状态。这个状态停留是的时间是2MSL(MSL:最长报文段生存时间,1~4分钟)。
TIME_WAIT状态的作用:
1、可靠地实现TCP的连接终止。
在终止TCP连接时有4个报文需要交换,其中最后一个ACK报文是由客户端发往服务器。假设这个ACK报文在网络中被丢弃了,那么服务器端收不到这个确认ACK,服务器端会向客户端再次发送FIN。这就是为什么TIME_WAIT状态持续2倍的最长报文段生存时间:1MSL时间留给最后的ACK确认报文段到达服务器端,1MSL时间留给服务器端再次发送的FIN(这一段摘抄自《unix系统编程手册》P1046,我不明白,超过2MSL连接就超时关闭了,再次发送FIN后,即客户端刚收到,2MSL时间也就到了,连接就关闭了,再次发送FIN的意义何在?,希望有知道的同学告诉我)。
2、确保老的重复的报文段在网络中过期失效,这样建立新的连接时将不再接受它们。
TCP协议采用的是出错重传,也就是会生成重复的报文,并且根据路由器的选择,这些重复的报文可能在连接终止后才到达,如果客户端/服务器端收到这个老的报文会把它误认为一个同一连接的新的报文,然后对这个报文进行处理,这样就会出现错误。从状态转换图我们可以看到从TIME_WAIT到连接终止,中间有2MSL,这个时间足以让老的重复的报文段过期失效。
TIME_WAIT会出现在服务器吗?
会的,TIME_WATI存在首先执行主动关闭的那端,比如爬虫服务器他本身其实就是客户端,完成爬取任务后,执行关闭,那么所有的TCP连接都会处于TIME_WAIT状态。
TIME_WAIT的危害:Linux分配给一个用户的文件句柄是有限的,如果系统中存在大量的TIME_WAIT状态,一旦达到句柄数上限,新的请求就无法被处理了,而且大量TIME_WAIT连接占用资源影响性能。