TCP相关知识

网络字节序是大端存储

三次握手

在这里插入图片描述

假定主机A运行的是TCP客户程序,而B运行TCP服务器程序。最初两端的TCP进程都处于CLOSED(关闭)状态。在本例中,A主动打开连接,而B被动打开连接。

一开始,B的TCP服务器进程先创建传输控制快TCB(存储了每一个连接中的一些重要信息,如,TCP连接表,指向发送和接收缓存的指针),准备接收客户进程的连接请求。然后服务器进程就处于LISTEN(收听) 状态,等待客户的连接请求。如有,即作出响应。

A的TCP客户进程也是首先创建传输控制模块TCB。然后,在打算建立TCP连接时,向B发出连接请求报文段,这时首部中的同步位SYN=1,同时选择一个初始序号seq = yTCP规定,SYN报文段(即SYN=1的报文段)不能携带数据,但要消耗掉一个序号。这时,TCP客户进程进入SYN-SENT(同步已发送) 状态。

B收到连接请求报文段后,如同意建立连接,则向A发送确认。在确认报文段中应把SYN位和ACK位都置1,确认号时ack = x + 1,同时也为自己选择一个初始序号seq = y。请注意,这个报文段也不能携带数据,但同样要消耗掉一个序号。这时TCP服务器进程进入了SYN-RCVD(同步收到) 状态。

TCP客户进程收到B的确认后,还要向B给出确认。确认报文段的ACK置1,确认号ack = y + 1,而自己的序号seq = x + 1。TCP的标准规定,ACK报文段可以携带数据,但如果不携带数据则不消耗序号,在这种情况下,下一个报文段的序号仍是seq = x + 1。这时,TCP连接已经建立,A进入ESTABLISHED(已建立连接) 状态。

当B收到A的确认后,也进入ESTABLISHED状态。

发送第一个SYN的一端将执行主动打开,接收这个SYN并发送下一个SYN的另一端执行被动打开。

在socket编程中,客户端执行connect()时,将触发三次握手

四次挥手

在这里插入图片描述

建立一个连接需要三次握手,而终止一个连接要经过四次挥手。这由TCP的半关闭造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

TCP连接的拆除需要发送四个包,因此称为四次挥手,客户端或服务器均可主动发起挥手动作。

刚开始双方都处于ESTABLISHED状态,假如是客户端先发起关闭请求。四次挥手的过程如下。

(1) 第一次挥手,客户端发送一个FIN报文,报文中会指定一个序列号,即发出连接释放报文段FIN=1,序号seq=u(等于前面已传送过的数据的最后一个字节的序号加1),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1状态,等待服务端的确认。请注意,TCP规定,FIN报文段即使不携带数据,它也消耗掉一个序号。

(2) 第二次挥手,服务端收到FIN之后,会发送ACK报文,且把客户端的序列号值+1作为ACK报文的序列号值,表明已经收到客户端的报文了,此时服务端处于CLOSE_WAIT状态。即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2) 状态,等待服务端发出的连接释放报文段。

(3) 第三次挥手,如果服务端也想断开连接了,和客户端的第一次挥手一样,发给FIN报文,且指定一个序列号,此时服务端处于LAST_ACK的状态。即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。

(4) 第四次挥手,客户端收到FIN之后,一样发送一个ACK报文作为应答,且把服务端的序列号值+1作为自己ACK报文的序列号值,此时客户端处于TIME_WAIT状态。需要过一阵子以确保服务端收到自己的ACK报文之后才会进入CLOSED状态,服务端收到ACK报文之后,就关闭连接了,处于CLOSED状态。即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。

收到一个FIN只意味着在这一方向上没有数据流动。客户端执行主动关闭并进入TIME_WAIT是正常的,服务端通常执行被动关闭,不会进入TIME_WAIT状态。

在socket编程中,任何一方执行close()操作即可产生挥手操作

如果三次握手的时候每次握手信息对方没有收到会怎么样?

  • 若第一次握手服务器未接收到客户端请求建立连接的数据包时,服务器不会进行任何相应的动作,而客户端由于在一段时间内没有收到服务器发来的确认报文,因此会等待一段时间后重新发送SYN同步报文,若仍然没有回应,则重复上述过程直到发送次数超过最大重传次数限制后,建立连接的系统调用会返回-1。
  • 若第二次握手客户端未接收到服务器回应的ACK报文时,客户端会采取第一次握手失败时的动作,这里不再重复,而服务器端此时将阻塞在accpet()系统调用处等待client再次发送ACK报文。
  • 若第三次握手服务器未接收到客户端发送过来的ACK报文,同样会采取类似于客户端的超时重传机制,若重传次数超过限制后,则accpet()返回-1,服务器端连接建立失败。但此时客户端认为自己已经连接成功了,因此开始向服务器端发送数据,但是服务器端的accept()系统调用已返回,此时没有在监听状态。因此服务器端接收到来自客户端发送来的数据时会发送RST报文给客户端,消除客户端单方面建立连接的状态。

为什么要进行三次握手?两次握手可以么?

三次握手的主要目的是确认自己和对方的发送和接收都是正常的,从而保证了双方能够进行可靠通信。若采用两次握手,当第二次握手后就建立连接的话,此时客户端知道服务器能够正常接收到自己发送的数据,而服务器并不知道客户端是否能够接收到自己发送的数据

网络往往是非理想状态的(存在丢包和延迟),当客户端发起创建连接的请求时,如果服务器直接创建了这个连接并返回包含SYN,ACK和Seq等内容的数据包给客户端,这个数据包因为网络传输的原因丢失了,丢失之后客户端就一直接收不到返回的数据包。由于客户端可能设置了一个超时时间,一段时间后就关闭了建立连接的请求,再重新发起新的请求,而服务器端是不知道的,如果没有第三次握手告诉服务器客户端能否收到服务器传输的数据的话,服务器端的端口就会一直开着,等到客户端因超时重新发出请求后,服务器就会重新开启一个端口连接。常此以往,这样的端口越来越多,就会造成服务器开销的浪费。

除此之外,还有一种异常情况,就是为了防止已经失效的连接请求报文段突然又传送到了服务器,因而产生错误。所谓已经失效的报文段是这样产生的。考虑一种正常情况,客户端发出连接请求,但因连接请求报文丢失而未收到确认。于是,客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。客户端总共发送了两个连接请求报文段,其中第一个丢失,第二个到达了服务器端,没有已失效的连接请求报文段。

现在假定出现一种异常状况,即客户端发出的第一个连接请求报文段并没有丢失,而是在某些网络节点长时间滞留了,以致延误到连接释放以后的某个时间才到达服务器端。本来这是一个早已失效的报文段,但服务器收到此失效的连接请求报文段后,就误认为是客户端又发出一次新的连接请求。于是就向客户端发出确认报文段,同意建立连接。假定不采用第三次握手,那么只要服务器发出确认,新的连接就建立了。

由于现在客户端并没有发出建立连接的请求,因此不会理睬服务器的确认,也不会向服务器发送数据。但服务器却以为新的连接已经建立,并一直等待客户端发来数据。服务器的许多资源就这样被浪费了。

第2次握手传回了ACK,为什么还要传回SYN?

ACK是为了告诉客户端发来的数据已经接收无误,而传回SYN是为了告诉客户端,服务端收到的消息确实是客户端发送的消息。

为什么要四次挥手?

释放TCP连接时之所以需要四次挥手,是因为FIN释放连接报文和ACK确认接收报文是分别在两次握手中传输的。当主动方在数据传送结束后发出连接释放的通知,由于被动方可能还有必要的数据要处理,所以会先返回ACK确认收到报文。当被动方也没有数据再发送的时候,则发出连接释放通知,对方确认后才完全关闭TCP连接。

CLOSE-WAIT和TIME-WAIT的状态和意义

在服务器收到客户端关闭连接的请求并告诉客户端自己已经成功收到该请求之后,服务器进入了CLOSE-WAIT状态,然而此时有可能服务端还有一些数据没有传输完成,因此不能立即关闭连接,而CLOSE-WAIT状态就是为了保证服务器在关闭连接之前将待发送的数据发送完成。

TIME-WAIT发生在第四次挥手,当客户端向服务端发送ACK确认报文后进入该状态,若取消该状态,即客户端在收到服务端的FIN报文后立即关闭连接,此时服务端相应的端口并没有关闭,若客户端在相同的端口立即建立新的连接,则有可能接收到上一次连接中残留的数据包,可能会导致不可预料的异常出现。除此之外,假设客户端最后一次发送的ACK包在传输的时候丢失了,由于TCP协议的超时重传机制,服务端将重发FIN报文,若客户端并没有维持TIME_WAIT状态而直接关闭的话,当收到服务端重新发送的FIN包时,客户端就会用RST包来相应服务端,这将会使得对方认为有错误发生,然后其实只是正常的关闭连接过程,并没有出现异常情况。

TIME_WAIT状态会导致什么问题,怎么解决?

考虑高并发短连接的业务场景,在高并发短连接的TCP服务器上,当服务器处理完请求后主动请求关闭连接,这样服务器上会有大量的连接处于TIME-WAIT状态,服务器维护每一个连接需要一个socket,也就是每个连接会占用一个文件描述符,而文件描述符的使用是有上限的,如果持续高并发,会导致一些正常的连接失败。

解决方案:修改配置或设置SO_REUSEADDR套接字,使得服务器处于TIME_WAIT状态下的端口能够快速回收和重用。

TIME_WAIT为什么是2MSL

当客户端发出最后的ACK确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完ACK确认报文之后,会设置一个时长为2MSL的计时器。MSL(Maximum Segment Lifetime),指一段TCP报文在传输过程中的最大生命周期。2MSL即是服务器端发出FIN报文和客户端发出的ACK确认报文所能保持有效的最大时长。

若服务器在1MSL内没有收到客户端发出的ACK确认报文,再次向客户端发出FIN报文。如果客户端在2MSL内收到了服务器再次发来的FIN报文,说明服务器由于一些原因并没有收到客户端发出的ACK确认报文。客户端将再次向服务器发出ACK确认报文,并重新开始2MSL的计时

若客户端在2MSL内没有再次收到服务器发送的FIN报文,则说明服务器正常接收到客户端ACK确认报文,客户端可以进入CLOSE阶段,即完成四次挥手。

所以客户端要经历2MSL时长的TIME_WAIT阶段,为的是确认服务器能否接收到客户端发出的ACK确认报文。

有很多TIME_WAIT状态如何解决

服务器可以设置SO_REUSEADDR套接字选项来通知内核,如果端口被占用,但TCP连接位于TIME_WAIT状态时可以重用端口。如果你的服务器程序停止后立刻重启,而新的套接字依旧希望使用同一个端口,此时SO_REUSEADDR选项就可以避免TIME_WAIT状态。

也可以采用长连接的方式减少TCP的连接与断开,在长连接的业务中往往不需要考虑TIME_WAIT状态,但其实在长连接的业务中并发量一般不会太高。

有很多CLOSE_WAIT怎么解决

  • 首先检查是不是自己的代码问题(看是否服务端程序忘记关闭连接),如果是,则修改代码。
  • 调整系统参数,包括句柄相关参数和TCP/IP的参数,一般一个CLOSE_WAIT会维持至少2个小时的时间,我们可以通过调整参数来缩短这个时间。

TCP和UDP的区别

类型是否面向连接传输可靠性传输形式传输效率所需资源应用场景首部字节
TCP可靠字节流文件传输,邮件传输20~60
UDP不可靠数据报文段即时通讯,域名转换8个字节

TCP协议中的计时器

TCP中有七种计时器,分别为:

  • 建立连接定时器:该定时器是在建立TCP连接的时候使用的,在TCP三次握手的过程中,发送方发送SYN时,会启动一个定时器(默认为3秒),若SYN包丢失了,那么3秒之后会重新发送SYN包,直到达到重传次数。
  • 重传定时器:该定时器主要用于TCP超时重传机制中,当TCP发送报文段时,就会创建特定报文的重传计时器,并可能出现两种情况。
    • 若在计时器截止之前发送方收到了接收方的ACK报文,则撤销该计时器。
    • 若在计时器截止时间内并没有收到接收方的ACK报文,则发送方重传报文,并将计时器复位。
  • 坚持计时器:我们知道TCP通过让接收方指明希望从发送方接收的数据字节数(窗口大小)来进行流量控制,当接收端的接收窗口满时,接收端会告诉发送端此时窗口已满,请停止发送数据。此时发送端和接收端的窗口大小均为0,直到窗口变为非0时,接收端将发送一个确认ACK告诉发送端可以再次发送数据,但是该报文有可能在传输时丢失。若该ACK报文丢失,则双方可能一直等待下去,为了避免这种死锁情况的发生,发送方使用了一个坚持计时器来周期性地向接收方发送探测报文段,以查看接收方窗口是否变大。
  • 延迟应答计时器:延迟应答也被称为捎带ACK,这个定时器是在延迟应答的时候使用的,为了提高网络传输的效率,当服务器接收到客户端的数据后,不是立即回ACK给客户端,而是等一段时间,这样如果服务端有数据需要发送给客户端的话,就可以把数据和ACK一起发送给客户端了。
  • 保活计时器:该定时器是在建立TCP连接时指定SO_KEEPLIVE时才会生效,当发送方和接收方长时间没有进行数据交互时,该定时器可以用于确定对端是否还活着。
  • FIN_WAIT_2定时器:当主动请求关闭的一方发送FIN报文给接收端并且收到其对FIN的确认ACK后进入FIN_WAIT_2状态。如果这个时候因为网络突然断掉,被动关闭的一端宕机等原因,导致请求方没有收到接收方发来的FIN,主动关闭的一方会一直等待。该定时器的作用就是为了避免这种情况的发生。当该定时器潮湿的时候,请求关闭方将不再等待,直接释放连接。
  • TIME_WAIT定时器:在TCP四次挥手中,发送方在最后一次挥手之后会进入TIME_WAIT状态,不直接进入CLOSE状态的主要原因是被动关闭方万一在超时时间内没有收到最后一个ACK,则会重发最后的FIN,2MSL(报文段的最大生存时间)等待时间保证了重发的FIN会被主动关闭的一端收到并且重新发送最后一个ACK。还有一个原因是在这2MSL的时间段内任何迟到的报文段都会被接收方丢弃,从而防止老的TCP连接的包在新的TCP连接里面出现。

TCP是如何保证可靠性的

  • 数据分块:应用数据被分割成TCP认为最适合发送的数据块。
  • 序列号和确认应答:TCP给发送的每一个包进行编号,在传输的过程中,每次接收方收到数据后,都会对传输放进行确认应答,即发送ACK报文,这个ACK报文当中带有对应的确认序列号,告诉发送方成功接收了哪些数据以及下一次的数据从哪里开始发。除此之外,接收方可以根据序列号对数据包进行排序,把有序数据传送给应用层,并丢弃重复的数据。
  • 校验和:TCP将保持它首部和数据部分的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到报文段的检验和有差错,TCP将丢弃这个报文段并且不确认收到此报文段。
  • 流量控制:TCP连接的双方都有一个固定大小的缓冲空间,发送方发送的数据量不能超过接收端缓冲区的大小。当接收方来不及处理发送方的数据,会提示发送方降低发送的速率,防止产生丢包。TCP通过滑动窗口协议来支持流量控制机制。
  • 拥塞控制:当网络某个结点发生拥塞时,减少数据的发送。
  • ARQ协议:为了实现可靠传输,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后,再发送下一个分组。
  • 超时重传:当TCP发出一个报文段后,它启动一个定时器,等待目的端确认收到这个报文段。如果超过某个时间还没有收到确认,将重发这个报文段。

UDP为什么是不可靠的?bind和connect对于UDP的作用是什么?

UDP只有一个SOCKET接收缓冲区,没有SOCKET发送缓冲区,即只要有数据就发送,不管对方是否可以正确接收。而在对方的SOCKET接收缓冲区满了之后,新来的数据报无法进入到SOCKET接收缓冲区,此数据报就会被丢弃,因此UDP不能保证数据能够达到目的地,此外,UDP也没有流量控制和重传机制,故UDP的数据传输是不可靠的。

和TCP建立连接时采用三次握手不同,UDP中调用connect只是把对端的IP和端口号记录下来,并且UDP可以多次调用connect来指定一个新的IP和端口号,或者断开旧的IP和端口号(通过设置connect函数的第二个参数)。和普通的UDP相比,调用connect的UDP会提升效率,并且在高并发服务中会增加系统稳定性。

当UDP的发送端调用bind函数时,就会将这个套接字指定一个端口,若不调用bind函数,系统内核会随机分配一个端口给该套接字。当手动绑定时,能够避免内核来执行这一操作,从而在一定程度上提高性能。

TCP的最大连接数限制

  • Client最大TCP连接数
    • Client在每次发起TCP连接请求时,如果自己并不指定端口的话,系统会随机选择一个本地端口(local port),该端口是独占的,不能和其他TCP连接共享。TCP端口的数据类型是unsigned short,因此本地端口个数最大只有65536,除了端口0不能使用外,其他端口在空闲时都可以正常使用,这样可用端口最多有65535个。
  • Server最大TCP连接数
    • Server通常固定在某个本地端口上监听,等待Client的连接请求。不考虑地址重用(Unix的SO_REUSEADDR选项)的情况下,即使server端有多个IP,本地监听端口也是独占的,因此server端TCP连接4元组中只有客户端的IP地址和端口号是可变的,因此最大TCP连接为客户端IP数 × × ×客户端port数,对IPV4,在不考虑IP地址分类的情况下,最大TCP连接数约为2的32次方(IP数) × × × 2的16次方(port数),也就是server端单机最大TCP连接数约为2的48次方。然而这只是理论上的单继最大连接数,在实际环境中,受到明文规定(一些IP地址和端口具有特殊含义,没有对外开放),机器资源,操作系统等限制,特别是server端,其最大并发TCP连接数远不能达到理论上限。对server端,通过增加内存,修改最大文件描述符个数等参数,单机最大并发TCP连接数超过10万是没有问题的。

SYN FLOOD是什么?

SYN FLOOD是典型的DOS(拒绝服务)攻击,其目的是通过消耗服务器所有可用资源使服务器无法处理合法请求。通过发送初始连接请求(SYN)数据包,攻击者能够压倒目标服务器机器上的所有可用端口,导致目标设备根本不响应合法请求。

SYN FLOOD攻击如何工作?

通过利用TCP连接的握手过程。

在正常情况下,TCP连接显示三个不同的进程以进行连接。

  • 首先,客户端向服务器发送SYN数据包,以便启动连接
  • 服务器响应SYN数据包,并发送SYN/ACK数据包,以确认通信
  • 最后,客户端返回ACK数据包以确认从服务器接收到的数据包。完成这个数据包发送和接收序列后,TCP连接打开,然后就可以发送和接收数据了。

为了创建拒绝服务,攻击者利用这样的漏洞,即在接收到初始SYN数据包之后,服务器将用一个或多个SYN/ACK数据包进行响应,并等待三次握手中的最后一步。

攻击者向目标服务器发送大量SYN数据包,通常会使用欺骗性的IP地址。然后,服务器响应每个连接请求,并留下开放端口准备好接收响应。

因为服务端不确定自己发给客户端的SYN-ACK消息或客户端反馈的ACK消息是否会丢失,所以会给每个待完成的半开连接状态设置一个定时器,如果超过时间还没有收到客户端的ACK消息,则重新发送一次SYN-ACK消息给客户端,直到重试超过一定次数时才会放弃。

服务端为了维持半开连接状态,需要分配内核资源维护半开连接。当攻击者伪造海量的虚假IP向服务器发送SYN包时,就形成了SYN FLOOD攻击。攻击者估计不响应ACK消息,导致服务端被大量注定不能完成的半开连接占据,直到资源耗尽,停止响应正常的连接请求。

如何解决

  • 直接的方法是提高TCP端口容量的同时减少半开连接的资源占用时间,然而该方法只是稍稍提高了防御能力。
  • 部署能够辨别恶意IP的路由器,将伪造IP地址的发送方发送的SYN消息过滤掉,该方案作用一般不是太大。

上面两种方法虽然在一定程度上能够提高服务器的防御能力,但是没有从根本上解决服务器资源消耗殆尽的问题,而以下几种方法的出发点都是在发送方发送确认回复后才开始分配传输资源,从而避免服务器资源消耗殆尽。

  • SYN Cache:该方法首先构造一个全局的Hash table,用来缓存系统当前所有的半开连接信息。在Hash Table中的每个桶的容量大小是有限制的,当桶满时,会主动丢掉早来的信息。当服务端收到一个SYN消息后,会通过一个映射函数生成一个响应的KEY值,使得当前半连接信息存入相应的桶中。当收到客户端正确的确认报文后,服务端才开始分配传输资源块,并将相应的半开连接信息从表中删除。和服务器传输资源相比,维护表的开销要小得多。
  • SYN Cookies:该方案原理和HTTP Cookies技术类似,服务端通过特定的算法将半开连接信息编码成序列号或者时间戳,用作服务端给客户端的消息编号,随SYN-ACK消息一同返回给连接发起方,这样在连接建立完成前服务端不保存任何信息,直到发送方发送ACK确认报文并且服务端成功验证编码信息后,服务端才开始分配传输资源。若请求方是攻击者,则不会向服务端发送ACK消息,由于未成功建立连接,因此服务端并没有花费任何额外的开销。

然后该方案也存在一些缺点,由于服务端并不保存半开连接状态。因此也就丧失了超时重传的能力,这在一定程度上降低了正常用户的连接成功率。此外,客户端发送给服务端的确认报文存在传输丢失的可能,当ACK确认报文丢失时,服务端和客户端会对连接的成功与否产生歧义,此时就需要上层应用采取相应的策略进行处理了。

  • SYN Proxy:在客户端和服务器之间部署一个代理服务器,类似于防火墙的作用。通过代理服务器与客户端进行建立连接的过程,之后代理服务器充当客户端将成功建立连接的客户端信息发送给服务器。这种方法基本不消耗服务器的资源,但是建立连接的时间变长了(总共需要6次握手)。

TCP报文包含哪些信息

TCP虽然是面向字节流的,但TCP传送的数据单元却是报文段。一个TCP报文段分为首部和数据两个部分,而TCP的全部功能都体现在它首部中各字段的作用

TCP报文段首部的前20个字节是固定的,后面有 4 n 4n 4n字节是根据需要而增加的选项(n是整数)。因此TCP首部的最小长度是20字节

在这里插入图片描述

首部固定部各字段的意义如下:

  • 源端口和目的端口:各占两个字节,分别写入源端口号和目的端口号。它用于多路复用/分解来自或送往上层应用的数据,其和IP数据报中的源IP和目的IP地址一同确定一条TCP连接。
  • 序号:占4字节。序号范围是 [ 0 , 2 32 − 1 ] [0,2^{32} - 1] [0,2321],共 2 32 2^{32} 232个序号。序号增加到 2 32 − 1 2^{32} - 1 2321后,下一个序号就又回到0。也就是说,序号使用 m o d 2 32 mod 2^{32} mod232运算。TCP是面向字节流的,在一个TCP连接中传递的字节流中的每一个字节都按顺序编号,整个要传送的字节流的起始序号必须在连接建立时设置。首部中的序号字段值则指的是本报文段所发送的数据的第一个字节的序号。例如,报文段的序号字段值是301,而携带的数据共有100字节。这就表明:本报文段的数据的第一个字节的序号是301,最后一个字节的序号是400,显然,下一个报文段(如果还有的话)的数据序号应当从401开始,即下一个报文段的序号字段值应为401。这个阻断的名称也叫做“报文段序号”。
  • 确认号:占4个字节,是期望收到对方下一个报文段的第一个数据字节的序号。例如,B正确收到了A发送过来的一个报文段,其序号字段值是501,而数据长度是200字节(501~700),这表明B正确收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701。
    • 若确认号 = N,则表明:到序号N-1为止的所有数据都已正确收到。
  • 数据偏移:占4位,它指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。这个字段实际上指出了TCP报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的。但应注意,数据偏移的单位是32位字(即以4字节长为计算单位)。由于4位二进制数能够表示的最大十进制数字是15,因此数据偏移的最大值是60字节,这也是TCP首部的最大长度(即选项长度不能超过40字节)。

TCP的拥塞控制算法

TCP进行拥塞控制的算法有四种,即慢开始,拥塞避免,快重传和快恢复

慢开始

发送方维持一个叫做拥塞窗口cwnd的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口。

发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就可以再增大一些,以便把更多的分组发送出去,这样就可以提高网络的利用率。但只要网络出现拥塞或有可能出现拥塞,就必须把拥塞窗口减小一些,以减少注入到网络中的分组数,以便缓解网络出现的拥塞。

慢开始算法的思路是这样的:当主机开始发送数据时,由于并不清楚网络的负荷情况,所以如果立即把大量数据字节注入到网络中,那么就有可能引起网络发生拥塞。一个较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。

慢开始的“慢”并不是指cwnd的增长速率慢,指的是在TCP开始发送报文段时先设置cwnd=1,使得发送方在开始时只发送一个报文段,然后再逐渐增大cwnd。在TCP的实际运行中,发送方只要收到一个对新报文段的确认,其拥塞窗口cwnd就立即加1,并可以立即发送新的报文段,而不需要等这个轮次中所有的确认都收到后再发送新的报文段

为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。慢开始门限ssthresh的用法如下:

  • 当cwnd<ssthresh时,使用上述的慢开始算法。
  • 当cwnd>ssthresh时,停止使用慢开始算法而改用拥塞避免算法
  • 当cwnd=ssthresh时,既可以使用慢开始算法,也可以使用拥塞避免算法

拥塞避免算法

拥塞避免算法的思路是让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就可以把发送方的拥塞窗口cwnd加1,而不是像慢开始阶段那样加倍增长。因此在拥塞避免阶段就有 “加法增大” 的特点。这表明在拥塞避免阶段,拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。

当网络拥塞发生时,让新的慢开始门限值变为发生拥塞时候的值的一半,并将拥塞窗口置为1,然后再次重复两种算法(慢开始和拥塞避免)

快重传

快重传算法可以让发送方尽早知道发生了个别报文段的丢失。快重传算法首先要求接收方不要等待自己发送数据时才进行捎带确认,而是立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认。假定发送方发送了Msg1~Msg4这4个报文,已知接收方收到了Msg1,Msg3和Msg4报文,此时因为接收到了失序的数据包,按照快重传的约定,接收方应立即向发送方发送Msg1的重复确认。于是在接收方收到Msg4报文的时候,向发送方发送的仍然时Msg1的重复确认。这样,发送方就收到了3次Msg1的重复确认,于是立即重传对方未收到的Msg报文。由于发送方尽早重传未被确认的报文段,因此,快重传算法可以提高网络的吞吐量。

快重传算法规定,发送方只要一连收到3个重复确认,就知道接收方确实没有收到报文段,因而应当立即进行重传,这样就不会出现超时

在这里插入图片描述

快恢复

快恢复算法是和快重传算法配合使用的,该算法主要有以下两个要点:

  • 当发送方连续收到三个重复确认,执行乘法减小,慢开始门限ssthresh值减半
  • 由于发送方可能认为网络现在没有拥塞,因此与慢开始不同,把cwnd值减半,然后执行拥塞避免算法,线性增大cwnd

在这里插入图片描述
当TCP连接进行初始化时,把拥塞窗口cwnd置为1,慢开始门限的初始值设置为16个报文段,即ssthresh=16,在执行慢开始算法时,发送方每收到一个对新报文段的确认ACK,就把拥塞窗口值加1,然后开始下一轮的传输,因此拥塞窗口cwnd随着传输轮次按指数规律增长,当拥塞窗口cwnd增长到慢开始门限ssthresh时,就改为执行拥塞避免算法,拥塞窗口按线性规律增长,使网络比较不容易出现拥塞。
当拥塞窗口cwnd = 24时,网络出现了超时,发送方判断为网络拥塞,于是调整门限值 s s t h r e s h = c w n d / 2 = 12 ssthresh = cwnd / 2 = 12 ssthresh=cwnd/2=12,同时设置拥塞窗口cwnd = 1,进入慢开始阶段。
按照慢开始算法,发送方每收到一个对新报文段的确认ACK,就把拥塞窗口值加1。当拥塞窗口cwnd = ssthresh = 12时(图中的点3),改为执行拥塞避免算法,拥塞窗口按线性规律增大。
当拥塞窗口cwnd = 16时(图中的点4),发送方一连收到3个对同一个报文段的重复确认。
在点4,发送方知道现在丢失了个别的报文段,于是不启动慢开始,而是执行快恢复算法,发送方调整门限值 s s t h r e s h = c w n d / 2 = 8 ssthresh = cwnd / 2 = 8 ssthresh=cwnd/2=8,并开始执行拥塞避免算法

TCP流量控制与拥塞控制

流量控制

流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。如果接收方来不及接收发送方发送的数据,那么就会有分组丢失。发送方的发送窗口不能超过接收方给出的接收窗口的数值。利用滑动窗口可以很方便地在TCP连接上实现对发送方的流量控制。

主要的方式是接收方返回的ACK中会包含自己的接收窗口大小,以控制发送方此次发送的数据量大小(发送窗口大小)

拥塞控制

在实际的网络通信系统中,除了发送方和接收方外,还有路由器,交换机等复杂的网络传输线路。若网络中有许多资源同时呈现供应不足,网络的性能就要明显变坏,整个网路的吞吐量将随输入符合的增大而下降。网络拥塞往往是由许多因素引起的。例如,某个结点缓存的容量太小时,到达该结点的分组因无存储空间暂存而不得不被丢弃。又如,处理机处理的速率太慢可能引起网络的拥塞。

此时就需要拥塞控制。拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况。常用的解决方法有:慢开始,拥塞避免,快重传,快恢复

拥塞控制和流量控制的区别

拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。但TCP连接的端点只要迟迟不能收到对方的确认信息,就猜想在当前网络中的某处很可能发生了拥塞,但这时却无法知道拥塞到底发生在网络的何处,也无法知道发生拥塞的具体原因。

流量控制往往是点对点通信量的控制,是个端到端的问题(接收端控制发送端)。流量控制所要作的就是抑制发送端发送数据的速率,以便使接收端来得及接收。

如果接收方滑动窗口满了,发送方会怎么做?

基于TCP流量控制中的滑动窗口协议,我们知道接收方返回给发送方的ACK包中会包含自己的接收窗口大小,若接收窗口已满,此时接收方返回给发送方的接收窗口大小为0,此时发送方会等待接收方发送的窗口大小直到变为非0为止。

然而,接收方发送的ACK包是有可能丢失的,此时,发送方一直等待接收方的ACK包,而接收方一直等待发送方发送的数据,如果没有其他措施,这种互相等待的死锁局面将一直延续下去。

为了解决这个问题,TCP为每一个连接设置有一个持续计时器。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。如果窗口值仍然为零,那么收到这个报文段的一方就重新设置持续计时器;如果窗口不为零,那么死锁的僵局既可以打破了。

停止等待协议

停止等待协议是为了实现TCP可靠传输而提出的一种相对简单的协议。该协议指的是发送方每发完一组数据后,直到收到接收方的确认信号才继续发送下一组数据。

1.无差错情况

停止等待协议可用图5-9来说明。图5-9(a)是最简单的无差错情况。A发送分组M1,发完就暂停发送,等待B的确认。B收到了M1就向A发送确认。A在收到了对M1的确认后,就再发送下一个分组M2。同样,在收到B对M2的确认后,再发送M3。

在这里插入图片描述

2.出现差错

图5-9(b)是分组在传输过程中出现差错的情况。

出现差错的情况主要分为两种

  • 发送方发送的M1在中途丢失了,接收方完全没收到数据。
  • 接收方收到M1时检测出了差错,就丢弃M1,其他什么也不做。

在这两种情况下,接收方都不会发送任何消息。可靠传输协议是这样设计的:发送方只要超过了一段时间仍然没有收到确认,就认为刚才发送的分组丢失了,因而重传前面发送过的分组。这就是超时重传。要实现超时重传,就要在每发送完一个分组时设置一个超时计时器。如果在超时计时器到期之前收到了对方的确认,就撤销已设置的超时计时器。

3.确认丢失和确认迟到

图5-10(a)说明的是另一种情况,B所发送的对M1的确认丢失了。A在设定的超时重传时间内没有收到确认,并无法知道是自己发送的分组出错,丢失,或者是B发送的确认丢失了。因此A在超时计时器到期后就要重传M1。现在应该注意B的动作。假定B又收到了重传的分组M1。这时应该采取两个动作。

第一,丢弃这个重复的分组M1,不向上层交付。

第二,向A发送确认。不能认为已经发送过确认就不再发送,因为A之所以重传M1就表示A没有收到对M1的确认。

在这里插入图片描述
图5-10(b)也是一种可能出现的情况。传输过程中没有出现差错。但B对分组M1的确认迟到了。A会收到重复的确认。对重复的确认的处理很简单:收下后就丢弃。B仍然会收到重复的M1,并且同样要丢弃重复的M1,并重传确认分组。

通常A最终总是可以收到对所有发出的分组的确认。如果A不断重传分组但总是收不到确认,就说明通信线路太差,不能进行通信。

TCP超时重传的原理

发送方在发送一次数据后就开启一个重传定时器,在一定时间内如果没有收到发送数据包的ACK报文,那么就重新发送数据,在达到一定次数还没有成功的话就放弃重传并发送一个复位信号

其中超时时间的计算是超时的核心,而定时时间的确定往往需要进行适当的权衡,因为当定时时间过长会造成网络利用率不高,定时时间太短会造成多次重传,使得网络阻塞。在TCP连接过程中,会参考当前的网络状况从而找到一个合适的超时时间。

TCP粘包问题

为什么会发生TCP粘包和拆包

  • 发送方写入的数据大于套接字缓冲区的大小,此时将发生拆包。
  • 发送方写入的数据小于套接字缓冲区大小,由于TCP默认使用Nagle算法,只有当收到一个确认后,才将分组发送给对端,当发送方收集了多个较小的分组,就会一起发送给对端,这将会发生粘包。
  • 发送方发送的数据太快,接收方处理数据的速度赶不上发送端的速度,将发生粘包。
  • 当TCP报文的数据部分大于MSS(最大报文长度)时将发生拆包。

常见解决方法

  • 在消息头部添加消息长度字段,服务端获取消息头的时候解析消息长度,然后向后读取相应长度的内容。
  • 固定消息数据的长度,服务端每次读取既定长度的内容作为一条完整消息,当消息长度不够时,空位补上固定字符,但是该方法会浪费网络资源。
  • 设置消息边界,也可以理解为分隔符,服务端从数据流中按消息边界分离出消息内容,一般使用换行符。

什么时候需要处理粘包问题

  • 当接收端同时收到多个分组,并且这些分组之间毫无关系时,需要处理粘包问题;而当多个分组属于同一数据的不同部分时,并不需要处理粘包问题

为什么TCP协议有粘包问题

TCP/IP协议族建立了互联网通信协议的概念模型,该协议族中的两个主要协议就是TCP和IP协议。TCP、IP协议族中的TCP协议能够保证数据段的可靠性和顺序,有了可靠的传输层协议之后,应用层协议就可以直接使用TCP协议传输数据,不需要关心数据段的丢失和重复问题。

IP协议解决了数据包的路由和传输,上层的TCP协议不再关注路由和寻址,那么TCP协议解决的是可靠性和顺序问题,上层不需要关心数据能够传送至目标进程,只要写入TCP协议的缓冲区的数据,协议栈几乎都能保证数据的送达。

当应用层协议使用TCP协议传输数据时,TCP协议可能会将应用层发送的数据分成多个包依次发送,而数据的接收方收到的数据段可能有多个应用层数据包,所以当应用层从TCP缓冲区中读取数据时发现粘连的数据包,需要对收到的数据进行拆分

粘包并不是TCP协议造成的,它的出现是因为应用层协议设计者对TCP协议的错误理解,忽略了TCP协议的定义并且缺乏设计应用层协议的经验。

  • TCP协议是面向字节流的协议,他可能会组合或者拆分应用层协议的数据;
  • 应用层协议没有定义消息的边界将导致数据的接收方无法拼接数据;

面向字节流

TCP协议是面向连接的,可靠的,基于字节流的传输层通信协议,应用层交给TCP协议的数据并不会以消息为单位向目的主机传输,这些数据在某些情况下会被组合成一个数据段发送给目标的主机。

Nagle算法是一种减少数据包的方式提高TCP传输性能的算法。因为网络带宽有限,它不会将小的数据块直接发送到目的主机,而是会在本地缓冲区中等待更多待发送的数据,这种批量发送数据的策略虽然会影响实时性和网络延迟,但是能够降低网络拥堵的可能性并减少额外开销。

当应用层协议通过TCP协议传输数据时,实际上待发送的数据先被写入了TCP协议的缓冲区,如果用户开启了Nagle算法,那么TCP协议可能不会立刻发送写入的数据,它会等待缓冲区中数据超过最大数据段(MSS)或者上一个数据段被ACK时才会发送缓冲区中的数据

TCP协议粘包问题是因为应用层协议开发者的错误设计导致的,他们忽略了TCP协议数据传输的核心机制——基于字节流,其本身不包含信息,数据包等概念,所有数据的传输都是流式的,需要应用层协议自己设计消息的边界,即消息帧。

粘包问题出现的核心原因:

  • TCP协议是基于字节流的传输层协议,其中不存在消息和数据包的概念
  • 应用层协议没有使用基于长度或者基于终结符的消息边界,导致多个消息的粘连

使用UDP和TCP协议的各种应用和应用层协议

在这里插入图片描述

常用的熟知端口号

21端口:FTP文件传输服务

22端口:SSH远程连接服务

23端口:TELNET终端仿真服务

25端口:SMTP简单邮件传输服务

80端口:HTTP超文本传输服务

443端口:HTTPS加密的超文本传输服务

3306端口:MYSQL数据库端口

8080端口:TCP服务端默认端口

8888端口:Nginx服务器端口

6379端口:Redis数据库端口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值