当前位置 博文首页 > WAIT状态的作用,TCP如何保证可靠传输,TCP连接中状态转化,滑动
?
TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制
- 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;
- 32位序号/32位确认号: 后面详细讲;
- 4位TCP报头长度: 表示该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部大长度是15 * 4 = 60
- 6位标志位:
- 16位窗口大小
- 16位校验和发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也 包含TCP数据部分.
- 16位紧急指针,标识哪部分数据是紧急数据;
- 40字节头部选项
URG: 紧急指针是否有效 ACK: 确认号是否有效 PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走 RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段 SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段 FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
三次握手过程
当客户端调用connect时,触发了连接请求,向服务器发送了SYN X包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN X包,调用accept函 数接收请求向客户端发送SYN K ,ACK X+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK X+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
三次握手足以建立连接,四次握手就有些多余
如果两次握手的话,就会出现已失效的请求报文段突然又传送到了服务端而产生连接的误判
例如:
有这样一种情况,当A发送一个消息给B,但是由于网络原因,消息被阻塞在了某个节点,然后阻塞的时间超出设定的时间,A会认为这个消息丢失了,然后重新发送消息。
当A和B通信完成后,这个被A认为失效的消息,到达了B
对于B而言,以为这是一个新的请求链接消息,就向A发送确认,
对于A而言,它认为没有给B再次发送消息(因为上次的通话已经结束)所有A不会理睬B的这个确认,但是B则会一直等待A的消息
四次挥手
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
失败时服务器并不会重传ack报文,而是直接发送RTS报文段,进入CLOSED状态。这样做的目的是为了防止SYN洪泛攻击。
如果四次挥手中最后一个ACK丢了,那么服务端会再次发送一个FIN。如果没有TIME_WAIT的话,那么客户端直接关闭,有可能重新与别的服务器建立连接,此时用的还是原来的端口和IP,那么之前的服务端重新发送的FIN又发给了客户端,此时客户端就懵逼了,我才刚建立连接,怎么就要分手?
总结
MSL-----报文最大生存周期
等待1个MSL时间是为了能够处理对端重传的FIN包进行ACK回复
等待2个MSL时间是为了让所有网络中延迟的报文都消失在网络中,不会对后续连接造成影响
由于主动关闭TCP连接的一方才会进入TIME_WAIT状态,一般情况服务器端不会出现TIME_WAIT状态,因为大多数情况都是客户端主动发起连接并主动关闭连接。但是某些服务如pop/smtp、ftp却是服务端收到客户端的QUIT命令后主动关闭连接,这就造成这类服务器上容易出现大量的TIME_WAIT状态的连接,而且并发量越大处于此种状态的连接越多。另外,对于被动关闭连接的服务在主动关闭客户端非法请求或清理长时间不活动的连接时(这种情况很可能是客户端程序忘记关闭连接)也会出现TIME_WAIT的状态。
方法一
C/C++中提供了一个接口,如果服务器重启时需要对端口号以及socket地址进行复用,从而避免了TIME_WAIT状态
方法二
通过修改Linux内核的方式解决该问题
在 /etc/sysctl.conf中加入
net.ipv4.tcp_tw_recycle = 1 (表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭)
net.ipv4.tcp_fin_timeout=30 (修改系統默认的 TIMEOUT 时间)
1. 确认应答机制
2. 超时重传机制
超时的时间如何确定?
理想的情况下, 找到一个小的时间, 保证 “确认应答一定能在这个时间内返回”. 但是这个时间的长短, 随着网络环境的不同, 是有差异的. 如果超时时间设的太长, 会影响整体的重传效率; 如果超时时间设的太短, 有可能会频繁发送重复的包;
TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个大超时时间.
3. 包序管理
TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉. 这时候我们利用序列号, 就可以很容易做到去重的效果.
序号+长度
一次性可以发送大量的数据(受限于协议字段中的窗口大小);然后等待回复
对每一个发送的数据段, 都要给一个ACK确认应答. 收到ACK后再发送下一个数据段. 这样做有一个比较大的缺点, 就是性能较差. 尤其是数据往返的时间较长的时候.既然这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时 间重叠在一起了).
服务端:接受数据,后沿往后走并回复客户端,前沿不能动,客户端拿走数据后,前沿往后走
客户端:发送第一条数据,窗口不动,等到接受到第一条数据的回复后,后沿向后走,根据回复的窗口大小,决定前沿是否向后动
情况一: 数据包已经抵达, ACK被丢了.
这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认;
只要后面有一次ack成功了,就代表前面的都接收到了
?
滑动窗口中数据的连续发送,尽力避免了因为ack丢失而导致的重传
确认回复中的ACK确认序号能够表示,这个序号之前的数据都已经接收到了
若前边的数据没有接受到,反而接收到了后边的数据,则不会对后边的数据进行ACK确认
总结:
当接收端接受数据的时候,若第一条数据没到,但是接受到了第二条数据,认为第一条数据可能丢失,立即向发送端连续三次发送重传请求;发送端连续接受到三条重传请求,则对这条数据进行重传
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应. 因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
接收端如何把窗口大小告诉发送端呢?
TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息;
16位数字大表示65535, 那么TCP窗口大就是65535字节么?
实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位;
总结
TCP引入?慢启动,快增长?机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;
通信初始双方协商窗口大小,窗口有可能很大,一次会发送很多数据,可能会因为网络原因导致大量丢包,导致重传,降低效率
发送端维护一个拥塞窗口,控制/限制发送端发送的数据最大大小,这个数字随着每次ack的确认回复快速增长,但是一旦出现包重传,则立即重新初始化
少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞; 当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降; 拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.
TCP拥塞控制这样的过程, 就好像 热恋的感觉
尽可能保证窗口大小(因为接受方,有可能很快就会把数据从缓冲区拿走)
如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小
一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输 效率;
那么所有的包都可以延迟应答么? 肯定也不是;
尽可能避免纯报头的确认回复
例如:
在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 “一发一收” 的. 意味着客户端给服务器说 了 “How are you”, 服务器也会给客户端回一个 “Fine, thank you”; 那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起回给客户端
对数据按照以字节为单位的流式传输
创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如:
写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节; 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次 read一个字节, 重复100次;
tcp产生粘包就是内核并没有对send要发送的数据进行明确的边界区分。
那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界
思考: 对于UDP协议来说, 是否也存在 “粘包问题” 呢?
本文选自于:http://www.voycn.com/article/wangluojichu2
?
cs