TCP通信协议
TCP是面向连接的、可靠的、基于字节流的传输层通信协议:
面向连接:一定是一对一才能连接,不能像UDP协议可以一个主机同时向多个主机发送消息,即一对多是无法做到的可靠的:无论的网络链路中出现了怎样的链路变化,TCP都可以保证一个报文一定能够到达接收端字节流:消息是没有边界的,所以无论消息有多大都可以进行传输。并且消息是有序的,当前一个消息没有收到的时候,即使它先收到了后面的字节已经收到,那么也不能扔给应用层去处理,同时对重复的报文会自动丢弃
TCP头部格式
序列号:在建立连接时由计算机生成的随机数作为其初始值,通过SYN包传给接收端主机,每发送一次数据,就累加一次该数据字节数的大小。用来解决网络包乱序问题确认应答号:指下一次期望收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题控制位:ACK:该位为1时,确认应答的字段变为有效,TCP规定除了最初建立连接时的SYN包之外该位必须设置为1RST:该位为1时,表示TCP连接中出现异常必须强制断开连接SYN:该位为1时,表示希望建立连接,并在其序列号的字段进行序列号初始值的设定FIN:该位为1时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换FIN位置为1的TCP段网络模型
谈一谈你对TCPIP四层模型,OSI七层模型的理解?OSI参考模型
OSI(OpenSystemInterconnect),即开放式系统互联。一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互连模型。ISO为了更好的使网络应用更为普及,推出了OSI参考模型。其含义就是推荐所有公司使用这个规范来控制网络。这样所有公司都有相同的规范,就能互联了。
TCPIP五层模型
TCPIP五层协议和OSI的七层协议对应关系如下:
TCP状态
CLOSED:表示初始状态LISTEN:表示服务器端的某个SOCKET处于监听状态,可以接受连接了SYNRCVD:表示接收到了SYN报文SYNSENT:表示客户端已发送SYN报文ESTABLISHED:表示连接已经建立了TIMEWAIT:表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了CLOSING:表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接CLOSEWAIT:表示在等待关闭
如何在Linux系统中查看TCP状态?
TCP的连接状态查看,在Linux可以通过netstatnapt命令查看:
TIMEWAIT
为什么需要TIMEWAIT状态?
主动发起关闭连接的一方,才会有TIMEWAIT状态。需要TIMEWAIT状态,主要是两个原因:防止具有相同四元组的旧数据包被收到保证被动关闭连接的一方能被正确的关闭,即保证最后的ACK能让被动关闭方接收,从而帮助其正常关闭
TIMEWAIT过多有什么危害?
如果服务器有处于TIMEWAIT状态的TCP,则说明是由服务器方主动发起的断开请求。过多的TIMEWAIT状态主要的危害有两种:第一是内存资源占用第二是对端口资源的占用,一个TCP连接至少消耗一个本地端口
第二个危害是会造成严重的后果的,要知道,端口资源也是有限的,一般可以开启的端口为32768~61000,也可以通过如下参数设置指定net。ipv4。iplocalportrange
如果发起连接一方的TIMEWAIT状态过多,占满了所有端口资源,则会导致无法创建新连接。
客户端受端口资源限制:客户端TIMEWAIT过多,就会导致端口资源被占用,因为端口就65536个,被占满就会导致无法创建新的连接
服务端受系统资源限制:由于一个四元组表示TCP连接,理论上服务端可以建立很多连接,服务端确实只监听一个端口但是会把连接扔给处理线程,所以理论上监听的端口可以继续监听。但是线程池处理不了那么多一直不断的连接了。所以当服务端出现大量TIMEWAIT时,系统资源被占满时,会导致处理不过来新的连接
如何优化TIMEWAIT?
这里给出优化TIMEWAIT的几个方式,都是有利有弊:打开net。ipv4。tcptwreuse和net。ipv4。tcptimestamps选项net。ipv4。tcpmaxtwbuckets程序中使用SOLINGER,应用强制使用RST关闭
为什么TIMEWAIT等待的时间是2MSL?
MSL是MaximumSegmentLifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为TCP报文基于是IP协议的,而IP头中有一个TTL字段,是IP数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。
MSL与TTL的区别:MSL的单位是时间,而TTL是经过路由跳数。所以MSL应该要大于等于TTL消耗为0的时间,以确保报文已被自然消亡。
TIMEWAIT等待2倍的MSL,比较合理的解释是:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待2倍的时间。
比如如果被动关闭方没有收到断开连接的最后的ACK报文,就会触发超时重发Fin报文,另一方接收到FIN后,会重发ACK给被动关闭方,一来一去正好2个MSL。
2MSL的时间是从客户端接收到FIN后发送ACK开始计时的。如果在TIMEWAIT时间内,因为客户端的ACK没有传输到服务端,客户端又接收到了服务端重发的FIN报文,那么2MSL时间将重新计时。
在Linux系统里2MSL默认是60秒,那么一个MSL也就是30秒。Linux系统停留在TIMEWAIT的时间为固定的60秒。
其定义在Linux内核代码里的名称为TCPTIMEWAITLEN:defineTCPTIMEWAITLEN(60HZ)howlongtowaittodestroyTIMEWAITstate,about60seconds
如果要修改TIMEWAIT的时间长度,只能修改Linux内核代码里TCPTIMEWAITLEN的值,并重新编译Linux内核。连接过程
TCP三次握手
开始客户端和服务器都处于CLOSED状态,然后服务端开始监听某个端口,进入LISTEN状态:第一次握手(SYN1,seqx),发送完毕后,客户端进入SYNSENT状态第二次握手(SYN1,ACK1,seqy,ACKnumx1),发送完毕后,服务器端进入SYNRCVD状态第三次握手(ACK1,ACKnumy1),发送完毕后,客户端进入ESTABLISHED状态,当服务器端接收到这个包时,也进入ESTABLISHED状态,TCP握手,即可以开始数据传输
假设一开始客户端和服务端都处于CLOSED的状态。然后先是服务端主动监听某个端口,处于LISTEN状态【第一个报文】:客户端会随机初始化序号(clientisn),将此序号置于TCP首部的序号字段中,同时把SYN标志位置为1,表示SYN报文。接着把第一个SYN报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于SYNSENT状态
【第二个报文】:服务端收到客户端的SYN报文后,首先服务端也随机初始化自己的序号(serverisn),将此序号填入TCP首部的序号字段中,其次把TCP首部的确认应答号字段填入clientisn1,接着把SYN和ACK标志位置为1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于SYNRCVD状态
【第三个报文】:客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文TCP首部ACK标志位置为1,其次确认应答号字段填入serverisn1,最后把报文发送给服务端,这次报文可以携带客户到服务器的数据,之后客户端处于ESTABLISHED状态
服务器收到客户端的应答报文后,也进入ESTABLISHED状态
第三次握手是否可以携带数据?
第三次握手是可以携带数据的,前两次握手是不可以携带数据的。一旦完成三次握手,双方都处于ESTABLISHED状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了。假设第三次握手的报文的seq是x1:如果有携带数据:下次客户端发送的报文,seq服务器发回的ACK号如果没有携带数据:第三次握手的报文不消耗seq,下次客户端发送的报文,seq序列号为x1
服务端SYNRECV流程
客户端SYNSEND流程
场景1:skskwritepending!0这个值默认是0的,那什么情况会导致不为0呢?答案是协议栈发送数据的函数遇到socket状态不是ESTABLISHED的时候,会对这个变量做操作,并等待一小会时间尝试发送数据。场景2:icskicskacceptqueue。rskqdeferaccept!0客户端先bind到一个端口和IP,然后setsockopt(TCPDEFERACCEPT),然后connect服务器,这个时候就会出现rskqdeferaccept1的情况,这时候内核会设置定时器等待数据一起在回复ACK包。场景3:icskicskack。pingpong!0pingpong这个属性实际上也是一个套接字选项,用来表明当前链接是否为交互数据流,如其值为1,则表明为交互数据流,会使用延迟确认机制。
为什么是三次握手?不是两次、四次?
TCP建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。不使用两次握手和四次握手的原因:两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数
接下来以三个方面分析三次握手的原因:三次握手才可以阻止重复历史连接的初始化(主要原因)三次握手才可以同步双方的初始序列号三次握手才可以避免资源浪费
原因一:避免历史连接
客户端连续发送多次SYN建立连接的报文,在网络拥堵情况下:一个旧SYN报文比最新的SYN报文早到达了服务端那么此时服务端就会回一个SYNACK报文给客户端客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送RST报文给服务端,表示中止这一次连接
如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:如果是历史连接(序列号过期或超时),则第三次握手发送的报文是RST报文,以此中止历史连接如果不是历史连接,则第三次发送的报文是ACK报文,通信双方就会成功建立连接
所以,TCP使用三次握手建立连接的最主要原因是防止历史连接初始化了连接。
原因二:同步双方初始序列号
TCP协议的通信双方,都必须维护一个序列号,序列号是可靠传输的一个关键因素,它的作用:接收方可以去除重复的数据接收方可以根据数据包的序列号按序接收可以标识发送出去的数据包中,哪些是已经被对方收到的
可见,序列号在TCP连接中占据着非常重要的作用,所以当客户端发送携带初始序列号的SYN报文的时候,需要服务端回一个ACK应答报文,表示客户端的SYN报文已被服务端成功接收,那当服务端发送初始序列号给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了三次握手。而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
原因三:避免资源浪费
如果只有两次握手,当客户端的SYN请求连接在网络中阻塞,客户端没有接收到ACK报文,就会重新发送SYN,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的ACK确认信号,所以每收到一个SYN就只能先主动建立一个连接,这会造成什么情况呢?如果客户端的SYN阻塞了,重复发送多次SYN报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
即两次握手会造成消息滞留情况下,服务器重复接受无用的连接请求SYN报文,而造成重复分配资源。TCP四次挥手第一次挥手:FIN1,sequ,发送完毕后客户端进入FINWAIT1状态第二次挥手:ACK1,seqv,acku1,发送完毕后服务器端进入CLOSEWAIT状态,客户端接收到后进入FINWAIT2状态第三次挥手:FIN1,ACK1,seqw,acku1,发送完毕后服务器端进入LASTACK状态,客户端接收到后进入TIMEWAIT状态第四次挥手:ACK1,sequ1,ackw1,客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入TIMEWAIT状态,等待了某个固定时间(两个最大段生命周期,2MSL,2MaximumSegmentLifetime)之后,没有收到服务器端的ACK,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入CLOSED状态。服务器端接收到这个确认包之后,关闭连接,进入CLOSED状态
四次挥手过程:客户端打算关闭连接,此时会发送一个TCP首部FIN标志位被置为1的报文,也即FIN报文,之后客户端进入FINWAIT1状态服务端收到该报文后,就向客户端发送ACK应答报文,接着服务端进入CLOSEDWAIT状态客户端收到服务端的ACK应答报文后,之后进入FINWAIT2状态等待服务端处理完数据后,也向客户端发送FIN报文,之后服务端进入LASTACK状态客户端收到服务端的FIN报文后,回一个ACK应答报文,之后进入TIMEWAIT状态服务器收到了ACK应答报文后,就进入了CLOSE状态,至此服务端已经完成连接的关闭客户端在经过2MSL一段时间后,自动进入CLOSE状态,至此客户端也完成连接的关闭
为什么挥手需要四次?
再来回顾下四次挥手双方发FIN包的过程,就能理解为什么需要四次了。关闭连接时,客户端向服务端发送FIN时,仅仅表示客户端不再发送数据了但是还能接收数据。服务器收到客户端的FIN报文时,先回一个ACK应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN报文给客户端来表示同意现在关闭连接。
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的ACK和FIN一般都会分开发送,从而比三次握手导致多了一次。TCP优化
正确有效的使用TCP参数可以提高TCP性能。以下将从三个角度来阐述提升TCP的策略,分别是:TCP三次握手优化
TCP四次挥手优化
TCP数据传输优化
常见问题TCP和UDP
TCP和UDP的区别?连接TCP是面向连接的传输层协议,传输数据前先要建立连接UDP是不需要连接,即刻传输数据服务对象TCP是一对一的两点服务,即一条连接只有两个端点UDP支持一对一、一对多、多对多的交互通信可靠性TCP是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达UDP是尽最大努力交付,不保证可靠交付数据拥塞控制、流量控制TCP有拥塞控制和流量控制机制,保证数据传输的安全性UDP则没有,即使网络非常拥堵了,也不会影响UDP的发送速率首部开销TCP首部长度较长,会有一定的开销,首部在没有使用选项字段时是20个字节,如果使用了选项字段则会变长的UDP首部只有8个字节,并且是固定不变的,开销较小传输方式TCP是流式传输,没有边界,但保证顺序和可靠UDP是一个包一个包的发送,是有边界的,但可能会丢包和乱序分片不同TCP的数据大小如果大于MSS大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装TCP数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片UDP的数据大小如果大于MTU大小,则会在IP层进行分片,目标主机收到后,在IP层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,则就需要重传所有的数据包,这样传输效率非常差,所以通常UDP的报文应该小于MTUISN
为什么客户端和服务端的初始序列号ISN是不相同的?
如果一个已经失效的连接被重用了,但是该旧连接的历史报文还残留在网络中,如果序列号相同,那么就无法分辨出该报文是不是历史报文,如果历史报文被新的连接接收了,则会产生数据错乱。所以,每次建立连接前重新初始化一个序列号主要是为了通信双方能够根据序号将不属于本连接的报文段丢弃。另一方面是为了安全性,防止黑客伪造的相同序列号的TCP报文被对方接收。
初始序列号ISN是如何随机产生的?
起始ISN是基于时钟的,每4毫秒1,转一圈要4。55个小时。RFC1948中提出了一个较好的初始化序列号ISN随机生成算法。
ISNMF(localhost,localport,remotehost,remoteport)M是一个计时器,这个计时器每隔4毫秒加1F是一个Hash算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值。要保证Hash算法不能被外部轻易推算得出,用MD5算法是一个比较好的选择UDP
总结TCP向上层提供面向连接的可靠服务,UDP向上层提供无连接不可靠服务UDP没有TCP传输可靠,但是可以在实时性要求搞的地方有所作为对数据准确性要求高,速度可以相对较慢的,可以选用TCPTCP数据可靠性
一句话:通过校验和、序列号、确认应答、超时重传、连接管理、流量控制、拥塞控制等机制来保证可靠性。
(1)校验和
在数据传输过程中,将发送的数据段都当做一个16位的整数,将这些整数加起来,并且前面的进位不能丢弃,补在最后,然后取反,得到校验和。
发送方:在发送数据之前计算校验和,并进行校验和的填充。接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方进行比较。
(2)序列号
TCP传输时将每个字节的数据都进行了编号,这就是序列号。序列号的作用不仅仅是应答作用,有了序列号能够将接收到的数据根据序列号进行排序,并且去掉重复的数据。
(3)确认应答
TCP传输过程中,每次接收方接收到数据后,都会对传输方进行确认应答,也就是发送ACK报文,这个ACK报文中带有对应的确认序列号,告诉发送方,接收了哪些数据,下一次数据从哪里传。
(4)超时重传
在进行TCP传输时,由于存在确认应答与序列号机制,也就是说发送方发送一部分数据后,都会等待接收方发送的ACK报文,并解析ACK报文,判断数据是否传输成功。如果发送方发送完数据后,迟迟都没有接收到接收方传来的ACK报文,那么就对刚刚发送的数据进行重发。
(5)连接管理
就是指三次握手、四次挥手的过程。
(6)流量控制
如果发送方的发送速度太快,会导致接收方的接收缓冲区填充满了,这时候继续传输数据,就会造成大量丢包,进而引起丢包重传等等一系列问题。TCP支持根据接收端的处理能力来决定发送端的发送速度,这就是流量控制机制。
具体实现方式:接收端将自己的接收缓冲区大小放入TCP首部的窗口大小字段中,通过ACK通知发送端。
(7)拥塞控制
TCP传输过程中一开始就发送大量数据,如果当时网络非常拥堵,可能会造成拥堵加剧。所以TCP引入了慢启动机制,在开始发送数据的时候,先发少量的数据探探路。TCP协议如何提高传输效率
一句话:TCP协议提高效率的方式有滑动窗口、快重传、延迟应答、捎带应答等。
(1)滑动窗口
如果每一个发送的数据段,都要收到ACK应答之后再发送下一个数据段,这样的话我们效率很低,大部分时间都用在了等待ACK应答上了。
为了提高效率我们可以一次发送多条数据,这样就能使等待时间大大减少,从而提高性能。窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。
(2)快重传
快重传也叫高速重发控制。
那么如果出现了丢包,需要进行重传。一般分为两种情况:
情况一:数据包已经抵达,ACK被丢了。这种情况下,部分ACK丢了并不影响,因为可以通过后续的ACK进行确认;
情况二:数据包直接丢了。发送端会连续收到多个相同的ACK确认,发送端立即将对应丢失的数据重传。
(3)延迟应答
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口大小可能比较小。假设接收端缓冲区为1M,一次收到了512K的数据;如果立刻应答,返回的窗口就是512K;但实际上可能处理端处理速度很快,10ms之内就把512K的数据从缓存区消费掉了;在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;如果接收端稍微等一会在应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;
窗口越大,网络吞吐量就越大,传输效率就越高;我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。
(4)捎带应答
在延迟应答的基础上,很多情况下,客户端服务器在应用层也是一发一收的。这时候常常采用捎带应答的方式来提高效率,而ACK响应常常伴随着数据报文共同传输。如:三次握手。TCP如何处理拥塞
网络拥塞现象是指到达通信网络中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象。拥塞控制是处理网络拥塞现象的一种机制。
拥塞控制的四个阶段:慢启动拥塞避免快速重传快速恢复Socket
基于TCP协议的客户端和服务器工作:
服务端和客户端初始化socket,得到文件描述符服务端调用bind,将绑定在IP地址和端口服务端调用listen,进行监听服务端调用accept,等待客户端连接客户端调用connect,向服务器端的地址和端口发起连接请求服务端accept返回用于传输的socket的文件描述符客户端调用write写入数据;服务端调用read读取数据客户端断开连接时,会调用close,那么服务端read读取数据的时候,就会读取到了EOF,待处理完数据后,服务端调用close,表示连接关闭listen时候参数backlog的意义?
Linux内核中会维护两个队列:
未完成连接队列(SYN队列):接收到一个SYN建立连接请求,处于SYNRCVD状态;
已完成连接队列(Accpet队列):已完成TCP三次握手过程,处于ESTABLISHED状态;
SYN队列与Accpet队列
intlisten(intsocketfd,intbacklog)
参数一socketfd为socketfd文件描述符
参数二backlog,这参数在历史内环版本有一定的变化
在早期Linux内核backlog是SYN队列大小,也就是未完成的队列大小。在Linux内核2。2之后,backlog变成accept队列,也就是已完成连接建立的队列长度,所以现在通常认为backlog是accept队列。但是上限值是内核参数somaxconn的大小,也就说accpet队列长度min(backlog,somaxconn)。
accept发送在三次握手的哪一步?
我们先看看客户端连接服务端时,发送了什么?
客户端的协议栈向服务器端发送了SYN包,并告诉服务器端当前发送序列号clientisn,客户端进入SYNCSENT状态
服务器端的协议栈收到这个包之后,和客户端进行ACK应答,应答的值为clientisn1,表示对SYN包clientisn的确认,同时服务器也发送一个SYN包,告诉客户端当前我的发送序列号为serverisn,服务器端进入SYNCRCVD状态
客户端协议栈收到ACK之后,使得应用程序从connect调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为ESTABLISHED,同时客户端协议栈也会对服务器端的SYN包进行应答,应答数据为serverisn1
应答包到达服务器端后,服务器端协议栈使得accept阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入ESTABLISHED状态
从上面的描述过程,我们可以得知客户端connect成功返回是在第二次握手,服务端accept成功返回是在三次握手成功之后。
客户端调用close了,连接是断开的流程是什么?
我们看看客户端主动调用了close,会发生什么?
客户端调用close,表明客户端没有数据需要发送了,则此时会向服务端发送FIN报文,进入FINWAIT1状态
服务端接收到了FIN报文,TCP协议栈会为FIN包插入一个文件结束符EOF到接收缓冲区中,应用程序可以通过read调用来感知这个FIN包。这个EOF会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为EOF表示在该连接上再无额外数据到达。此时服务端进入CLOSEWAIT状态
接着,当处理完数据后,自然就会读到EOF,于是也调用close关闭它的套接字,这会使得会发出一个FIN包,之后处于LASTACK状态
客户端接收到服务端的FIN包,并发送ACK确认包给服务端,此时客户端将进入TIMEWAIT状态
服务端收到ACK确认包后,就进入了最后的CLOSE状态
客户端进过2MSL时间之后,也进入CLOSE状态