TCP协议
TCP协议简介
传输控制协议(TCP,Transmission Control Protocol)是在网络分层中传输层的协议。为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个协议,具有以下特点:
1.面向连接
2.基于流的方式
3.可靠通信,确保传输数据的正确性
TCP面向字节流的含义
在创建一个TCP的socket时,同时会在内核中创建一个接收缓冲区和发送缓冲区,进行建立连接,对于这样一个连接,即可以读数据,也可以写数据,这是TCP的全双工特性。
由于有缓冲区的存在,TCP读取数据并不需要一一匹配。
例如:写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;
读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次
read一个字节, 重复100次
这就是TCP和UDP的不同了,UDP是面向数据报的,即发多少收多少,写入100个字节数据,就必须100个字节的读取;而TCP是面向字节流的,它根据不关心一次写入多少数据,也不关心一次读取多少数据。这就是TCP面向字节流的含义。
TCP粘包问题
粘包问题:这个包是应用层的包,而TCP是在传输层的,在传输层看来,TCP是一个一个报文按照序号放在缓冲区中;
在应用层看来,看到的只是一串连续的字节数据。而怎么进行读取,读取多大的字节,万一读取错误,就会出现粘包问题。
造成沾包的原因:
1.多次向发送缓冲区发送少量数据,接收方处理多个数据包
2.向接收缓冲区发送数据,而接收方来不及处理,进行堆积,最后一次性处理大量数据包
解决沾包问题:
必须明确两个包之间的间隔。
在包头的位置约定一个包总长度的字段,从而就可以知道了包的结束位置,还可以程序员自己规定使用明确的分隔符,只要能保证分隔符和正文数据不冲突即可。
TCP首部格式(报头)
首先TCP的报头的长度是除了数据之外的地方总和,而选项是不定长的,选项最多为40个字节,而除了选项之外的地方一共20个字节,所以TCP报头长度最多一共有20+40=60个字节
16位源端口号:发送方的端口号
16位目的端口号:接收方的端口号
32位序号:发送数据包中第一个字节的序列号
32位确认序号:数据包的确认序号,通常是序列号+1,表示在该序列号前的数据包都收到了。
4位首部长度:即该TCP报头的长度,单位是4字节,所表示最大的数为60字节,即TCP报头最大60字节
URG:和紧急指针一起使用,用于表明紧急指针是否有效
ACK:表明确认序号有效
PSH:提示接收端应用程序尽快从TCP接收缓冲区将数据拿走
RST:对方要求重新建立连接
SYN:请求建立连接
FIN:告诉对方,没有数据发送了,要关闭连接
16位窗口大小:用于表明发送方的接收缓冲区剩余空间的大小
16位校验和:用于检查该TCP报头是否出错
16位紧急指针:表明有紧急数据需要先进行处理
TCP可靠性的实现
TCP提供一种可靠,面向字节流的服务,为了这个可靠性,TCP协议有很多机制来维持这个可靠性。
首先应用数据被分割成TCP认为最适合发送的数据块,在进行发送,如果发送的数据特别多的话,就会在发送方中维护一个发送队列,依次发送。
ACK确认应答机制
当发送方发送了数据,在某段时间内,接收方是需要给发送方发送一个确认收到的TCP报文,可以理解成该TCP报文不携带任何数据信息,只是一个确认报文,当发送方收到了来自接收方的确认应答报文,才会在发送队列中将该数据删除,这样才说明该数据是发送成功的。如图,主机A向主机B发送的数据的序列号为1-1000(一共1000个),则主机B需要给主机A回复1000个确认应答报文,分别为2-1001,一般一个数据的确认序列号是比序列号多1的,表明在该确认序号之前的数据都已经收到。基于这样的确认应答机制,这样就保证了发送方和接受方之间的数据传输的可靠。因为在TCP报文中,ACK字段是表明确认序号是否有效的,所以这个机制也叫作ACK确认应答机制。
超时重传机制
在确认应答机制中,如果一个数据丢包了,发送方会根据确认序号(ACK确认应答机制中ACK报文的序号)而判断丢包而进行重发,这个重发机制是在一段特定的时间内进行的。但是如果超过某个时间间隔,发送方没有接受到来自接收方的确认应答,同样会认为该数据丢包了,进而进行重发,如图,主机A向主机B发送1-1000个数据,但是在特定的时间间隔,主机并没有接收到来自主机B的确认应答,则会进行重发,直到接受到确认应答,如果重发的次数过多,那么主机A则会怀疑是否是连接异常而导致重发失败,会检查连接的可靠性,当然了,主机A接收不到来自主机B的确认应答,可能是因为数据丢包,也可能是因为ACK确认应答丢失了,也会导致主机A重发数据,这样一来主机B就会收到大量的重复数据,那么主机B是怎么确定哪些是重复的呢?那么就要靠32位序号了,通过32位序号可以判断出来哪些是重复的,从而进行丢弃,所以32位序号具有去重的功能。
超过特定的时间间隔,就会进行重发,那么这个特定的时间间隔是怎么确定的呢?
Linux中,超时以500ms为一个单位进行控制, 每次判定超时重发的超时
时间都是500ms的整数倍.
如果重发一次之后, 仍然得不到应答, 等待 2乘以500ms 后再进行重传.
如果仍然得不到应答, 等待 4乘以500ms 进行重传. 依次类推, 以指数形式递增.
累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接
连接管理机制
1.建立连接(三次握手)
TCP建立连接需要三步,也就是常说的三次握手,以客户端向服务器发起连接说明。
①:客户端主动向服务器发送SYN报文请求,请求建立连接,客户端的状态设为SYN_SENT。
②:服务器收到了来自客户端的SYN请求,同意建立连接,对客户端的SYN进行ACK响应,并同带上服务器的SYN请求,并将服务器的状态设为SYN_RCVD。
③:客户端收到了来自服务器的ACK+SYN报文,对服务器再进行一个ACK确认,并将自己的状态设为ESTABUSHED,服务器收到了ACK,也会把自己的状态设为ESTABUSHED。
至此,客户端和服务器端TCP连接建立成功,可以进行数据传输了。
三次握手中,前两次的报文丢失,可以通过超时重传机制重新发送,也就是说,前两次是具有保证性的。而第三次报文如果丢失,就是造成客户端认为连接建立成功,而服务器认为连接未建立成功,这样发送数据是无效的。
在服务器给客户端发送ACK+SYN报文时,会启动TCP定时计时器,超过这个时间没有收到ACK响应,就会重新发送,超过一定重传次数后,服务器就会主动关闭连接。在此之前,如果客户端已经向服务器写入数据,则服务器
会发送RST报文给客户端,客户端就可知道连接建立异常。
2.传输数据(数据交互)
TCP是具有全双工特性的,所以可以同时进行双向数据发送。
TCP也是具有ACK确认应答机制的。
所以每当发送方发送一个报文,接收方都会发送一个确认报文。
3.关闭连接(四次挥手)
TCP关闭连接需要四步,也就是常说的四次挥手,以客户端向服务器关闭连接说明。
①:客户端主动向服务器发送FIN报文请求,请求关闭连接,并将自己的状态设为FIN_WAIT_1。
②:服务器收到了来自客户端的FIN报文请求,会给客户端发送一个ACK确认报文,并将自己的状态设为CLOSE_WAIT,客户端收到ACK响应,会将自己的状态设为FIN_WAIT_2。
③:服务器主动向客户端发送FIN报文请求,请求关闭连接,并将自己的状态设为LAST_ACK。
④:客户端收到了来自服务器的FIN报文请求,会给服务器发送一个ACK确认报文,并将自己的状态设为TIME_WAIT,经过TIME_WAIT时间过去,设为CLOSED,服务器收到ACK响应,将自己的状态设为CLOSED。
至此,客户端和服务器端TCP关闭连接成功。
四次挥手中,前三次的报文丢失,可以通过超时重传机制重新发送,也就是说,前三次是具有保证性的。而第四次报文如果丢失,会造成客户端认为连接关闭,而服务器认为连接未关闭,而导致服务器一直维护一个无用的连接,浪费服务器资源,是不可取的。
在四次挥手过程中,主动发起连接FIN请求的一方一定会进入TIME_WAIT状态,如果没有TIME_WAIT,就会导致上述的情况,在TIME_WAIT时间内,如果服务器没有接收到ACK响应,就会重新发送FIN报文。这样就保证了最后一次报文的可靠性,也保证了四次挥手的可靠性。
在连接过程中,关于客户端和服务器总的状态变化。
客户端:
CLOSED->SYN_SENT:客户端调用connect,发送SYN报文;
SYN_SENT->ESTABUSHED:connect调用成功(发送ACK)后进入ESTABUSHED状态
ESTABUSHED->FIN_WAIT_1:客户端主动发起FIN报文
FIN_WAIT_1->FIN_WAIT_2:客户端收到来自服务器的ACK响应,进入FIN_WAIT_2状态,等待服务器的FIN报文;
FIN_WAIT_2->TIME_WAIT:客户端收到来自服务器的FIN报文,进入TIME_WAIT,发出对服务器FIN的ACK报文;
TIME_WAIT->CLOSED:经过TIME_WAIT时间(2MSL,报文最大生存时间)后,彻底关闭。
服务器:
CLOSED->LISTEN:服务器调用listen后进入LISTEN状态,等待客户端连接;
LISTEN->SYN_RCVD:一旦监听到连接请求(SYN报文),就将该连接放入内核等待队列中,并向客户端发送SYN确认报文,进入SYN_RCVD状态;
SYN_RCVD->ESTABUSHED:服务器收到来自客户端的ACK响应,就进入ESTABUSHED状态,可以进行读写数据;
ESTABUSHED->CLOSE_WAIT:服务器收到客户端的FIN报文请求,对其响应并进入CLOSE_WAIT状态;
CLOSE_WAIT->LAST_ACK:进入CLOSE_WAIT后说明服务器准备关闭连接;当服务器向客户端发送FIN报文后,此时进入LAST_ACK状态,等待最后一个ACK响应的到来;
LAST_ACK->CLOSED:服务器收到了来自客户端的ACK响应,彻底关闭连接。
流量控制
接收端处理数据的速度是有限的。如果发送端发送的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,进而引起丢包重传等一系列连锁反应。
所以流量控制就是TCP可以根据接收端的处理能力,来决定发送端发送数据的速度。
在TCP报头中,有一个16位的窗口大小,这个窗口大小就是用于告诉发送方自己接收缓冲区剩余的大小,这个窗口数字越大,说明可接收能力越强,可以多发数据;反之,只能少发数据甚至不发数据。如图,主机A向主机B房数据,由于主机B的处理能力较慢,会导致窗口大小越来越小,最后为0,这个时候,主机A就不能继续发送数据了,要等主机B处理数据,但是也不是一味的等。如果主机A过了重发时间还没有收到窗口大小的更新通知,就会发送一个窗口探测的包,不携带任何数据,只是询问一下主机B的窗口大小目前是多少,然后再决定自己是否发送数据和发送数据的快慢。主机A为了保证这个数据包不进行丢失,所以会时不时发送一个窗口探测包。
窗口大小有多大?
16位所能表示的最大数字为65535,但是并不是说窗口大小最大为65535.TCP报头中选项还包括一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位。
拥塞控制
发送方在发送数据时,不仅要考虑对方的接收能力,还要考虑当前的网络状况,如果网络特别差,还一次性发送大量数据,此时网络中充斥着大量的数据,就会使网络越来越差,所以在不清楚网络的状况下,是不能贸然一次性发送大量数据的。
TCP引入慢启动机制,先发送少量的数据,探测一下当前的网络状况,再决定根据多大的速度发送数据。
引入一个窗口:拥塞窗口
刚开始发送的时候,定义拥塞窗口大小为1;
每次收到一个ACK应答,拥塞窗口加1;
每次发送数据包时,将拥塞窗口和接收端反馈的窗口大小作比较,取较小值作为世纪发送数据的大小。即
实际发送大小 = min(拥塞窗口,接收端的窗口大小);
如图显示,拥塞窗口的增长是指数级别的。刚开始很慢,但是增长速度非常快。
为了不增长那么快,引入一个阈值的概念,当拥塞窗口超过这个阈值时,不再根据指数方式增长,而是根据线性方式增长。
如图所示,慢启动开始,拥塞窗口为1,然后经过几次数据的传输,进行指数增长。当这个拥塞窗口超过阈值(图中显示为ssthresh=16)时,开始线性增长,但是还是一直在增长,此时网络的状况越来越不好,有可能出现丢包的情况,当拥塞窗口超过某个临界值时,网络拥塞了,出现大面积的丢包,此时发送端立马重置,重新开始慢启动,再重复上面的操作。而此时的阈值为网络拥塞时的拥塞窗口大小的一半(12),所以说这个阈值并不是一成不变的,根据网络状况和拥塞窗口而进行不断的变化。
总结:拥塞控制是TCP为了解决发送效率的问题,但是又要避免造成网络拥塞的一种折中方案
TCP提高性能的方法
TCP不仅要保证可靠性,而且还需要尽快的将数据交付给接收方,所以TCP有下面集中方案来提高性能。
滑动窗口
TCP基于确认应答机制,每一个报文都需要有应答,这样有一个大的缺点,就是性能较差,如果数据往返时间较长,那么必须要等到上一个ACK确认后,才能发送下一个数据,这样的发送是串行的,毫无疑问效率是不高的。
为了提高效率,TCP允许一次发送多条数据,就可以大大的提高性能。
为了实现这个功能:TCP引入滑动窗口的概念。
滑动窗口:是一种流量控制技术。TCP中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。
如上图所示,主机A可以一次性的发送四个数据段,并且不需要等待ACK(比如3001~4000的数据不需要等待确认序号为3000的ACK确认),当收到第一个ACK后,就可以发送第五个数据段了。
为了使这个滑动窗口起效,操作系统需要开辟一个发送缓冲区,来记录当前还有哪些数据已经应答,哪些还没有被应答。应答过的数据,需要从缓冲区中删除。
如上图,一行方格就为主机A的发送缓冲区,每一个方格代表一个数据段,1-1000代表已经发送出去的数据并且已经收到了ACK应答,可以从发送缓冲区里删除,1001-5000代表发送出去但还没有收到ACK应答的数据,5001-···代表空闲空间或者未被发送的数据。当主机A接收到了序号为2000的ACK确认时,代表1001-2000的数据段已经被接收到了,那么这个窗口就可以向右移动(如图③),以此类推,这个窗口就会一直向右移动。当窗口越大时,一次性可以发送的数据就越多,网络吞吐量就越大。
总结:滑动窗口是为了提升发送效率的。
快重传
当滑动窗口的数据丢包时,该怎么处理呢?
为了处理这个问题,TCP引入快重传机制。
当某一个数据段丢失时,发送端会一直收到相同序号的ACK报文,发送方根据判断序号相同,就会知道数据丢失,进行重传。
如图,主机A给主机B发送数据,但1001-2000的数据丢包了,那么主机B就会连续发送几个序号为1001的ACK确认报文,即使后面数据的确认序号并不是1001,也都会被改成1001,当主机A连续收到几个1001的确认报文时,就会意识到1001-2000的数据丢包了,就会进行重传,当主机B收到了1001-2000的报文,返回的ACK序号就为7001,因为前面的报文已经收到了,保存在接收方的接收缓冲区内。基于这样的机制,就保证了数据的不丢包问题。这种机制就叫做快重传,也叫作高速重传机制。
快重传和超时重传有什么区别?
快重传主要是解决在滑动窗口下数据的丢包问题,而且会一次发送很多数据,基于后面数据的ACK确认序号,才能清楚前面数据的丢包问题。
超时重传是发送方和接收方的数据传输可靠性的保证,超时重传并不会一次发很多数据。只要在一定时间内没有收到ACK应答,就会进行重发。
延迟应答
如果接收方收到了数据并很快处理完毕,如果这个时候返回ACK应答,这时候返回的窗口可能比较小。
假设接收端缓冲区为1M,一次收到了500K的数据,如果立刻应答,返回的窗口就为500K;而如果这个数据10ms就被处理了,如果再等一会再应答,比如等待100ms,这时候返回的窗口大小就为1M。
窗口越大,网络吞吐量就越大,传输效率就越高,延迟应答可以提高窗口大小,间接的提升了传输效率,所以是很有必要的。
捎带应答
在TCP中,每一个数据都需要ACK应答,而ACK报文又不携带任何有用信息,而此时如果接收端又恰巧想给发送端发送数据呢?
如果是确认应答机制的话,就需要发送端先发送ACK应答,再发送真实数据,比较费时间,而可以采用捎带应答的方式,将ACK确认添加在真实数据包中,就相当于ACK搭了一个顺风车,这样就节约时间,从而提高效率。
总结
TCP就是基于这么多的机制,窗口等等,来保证了它的可靠性,从而提升传输效率。而TCP只是传输层的一个协议,通过学习TCP,不仅知道了一些处理问题的机制,还感受到了整个网络的庞大,该学的还很多。
关于TCP的知识非常多,大家可以一起讨论一下,有什么说的不对的地方,还请多多指导。