TCP/IP的整理

传输控制协议(transmission control protocol,TCP) 是一种面向连接的、可靠的、基本字节流的传输层通信协议,由RFC 793定义。在计算机网络OSI模型中,它完成第四层传输层所指定的功能。用户数据报协议(UDP)是同一层另一个重要的传输协议。

在因特网协议中,TCP层位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的,像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。

应用层向TCP发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分割成适当长度的报文段。之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。TCP为了保证不发生丢包,就给每一个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已经成功收到的包发回一个相应的确认信息(ACK);如果发送端实体在合理的往返时延内未收到确认,那么对应的数据报就假设为丢失并进行重传。TCP用一个校验和函数来检验数据是是否有错误,在发送和接收时都要计算校验和。

简介

  1. 数据在TCP层成为流(stream),数据分组成为分段(segment)。作为比较,数据在IP层成为Datagram,数组分组称为分片(Fragment)。UDP中分组成为Message。

运作方式

TCP协议的运行可以划分为三个阶段:连接阶段(connection establishment)、数据传送(data transfer)和连接终止(connection termination)。操作系统将TCP连接抽象为套接字表示的本地端点(Local end-point),作为编程接口给程序使用。在TCP连接的生命期内,本地端点要经历一系列的状态改变。

创建通路

TCP用三次握手(three-way handshake)过程创建一个连接。在连接创建过程中,很多参数要被初始化,例如序号被初始化以保证按序传输和连接的强壮性。

图片来源-《TCP/IP 详解 卷1》

 

连接通常由一端打开一个套接字(socket)然后监听来自另一方的连接,这称为被动打开(passive open)。服务器端被被动打开以后,用户端就开始创建主动打开(active open)。

  1. 客户端发送一个SYN报文段(即一个在TCP头部的SYN位字段置位的TCP/IP数据包),并指明自己想要连接的端口号和它的客户端初始序列号,记为ISN(c)。
  2. 服务器也发送自己的SYN报文段作为响应,并包含了它的初始序列号,记做ISN(s)。此外,为了确认客户端的SYN,服务端将其包含的ISN(c)数值加1后作为返回的ACK数值。因此,每发送一个SYN,序列号就会自动加1.如果出现丢失的情况,该SYN段将会重传。
  3. 为了确认服务器的SYN,客户端将ISN(s)的数值加1作为返回的ACK数值。

 如果服务器端接到了客户端发送的SYN后回了SYN-ACK后客户端掉线了,服务器端没有收到客户端回来的ACK,那么,这个连接处于一个中间状态,即没成功也没失败。于是服务器如果在一定时间内没有收到的TCP会重发SYN-ACK。在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻倍,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s才知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP才会断开这个连接。使用三个TCP参数来调整行为:tcp_synack_retries减少重试次数;tcp_max_sync_backlog,增大SYN连接数;tcp_abort_on_overflow决定超出能力时的行为。

资源使用

主机收到一个TCP包时,用两端的IP地址和端口来标识这个TCP包属于哪个session。使用一张表来存储所有的session,表中的每条称作Transmission Control Block(TCB),tcb结构的定义包括连接使用的源端口、目的端口、目的ip、序号、应答序号、对方窗口大小、已方窗口大小、tcp状态、tcp输入/输出队列、应用层输出队列,tcp的重传有关变量等。

服务器端的连接数量是无限的,只受内存的限制。客户端的连接数量,过去由于在发送第一个SYN到服务器之前需要先分配一个随机空闲的端口,这限制了客户端IP地址的对外发出连接的数量上限。从Linux 4.2开始,有了socket选项IP_BIND_ADDRESS_NO_PORT,它通知Linux内核不保留usingbind使用端口号为0时内部使用的临时端口(ephemeral port),在connect时会自动选择端口以组成独一无二的四元组(同一个客户端端口可用于连接不同的服务器套接字;同一个服务器端口可用于接受不同客户端套接字的连接)。

对于不能确认的包,接收但是还没读取的数据,都会占用操作系统的资源。

数据传输

在TCP的数据传送状态,很多重要的机制保证了TCP的可靠性和强壮性。包括:使用序号,对收到的TCP报文段进行排序以及检测重复数据;使用校验和检测报文段的错误,即无错传输;使用确认和计时器来检测和纠正丢包和延时;流控制(Flow control);拥塞控制(congestion control);丢失包的重传。

可靠传输

通常每个TCP报文段中都有一对序号和确认号TCP报文发送者称自己的字节流的编号为序号,把接收到对方的字节流编号为确认号。TCP报文的接受者为了确保可靠性,在接收到一定数量的连接字节流后才发送确认。这是TCP的一种扩展,成为选择确认(selective Ackonwledgemet)。选择确认使得TCP接收者可以对乱序到达的数据块进行确认。每个字节传输过后,ISN号都会递增1。

通过使用序号和确认号,TCP层可以把收到的报文段中的字节按正确的顺序交付给应用层。序号是32位的无符号数,在它增大到232-1时,便会回绕到0。对于ISN的选择是TCP中关键的一个操作,它可以确保强壮性和安全性。

TCP协议使用序号(sequence number)标识每端发出的字节的顺序,从而另一端接收数据时可以重建顺序,无惧传输时的包的乱序交付或者丢包。在发送第一个包时(SYN包),选择一个随机数作为序号的初始值,以克制TCP序号预测攻击。

发送确认包(Acks),携带了接收到的对方发来的字节流的编号,称为确认号,以告诉对方已经成功接收的数据流的字节位置。Ack并不意味着数据已经交付了上层应用程序。

可靠性通过发送方检测到丢失的传输数据并重传这些数据。包括超时重传(Retransmission timeout,RTO)与重复累计确认(duplicate cumulative acknowledgements,DupAcks)。

基于重复累计确认的重传

如果一个包(假设它的序号为100,即该报始于第100字节)丢失,接收方就不能确认这个包以及其以后的包,因为采用了累计ack。接收方在收到100以后的包时,发出对包含第99字节的包的确认。这种重复确认是包丢失的信号。发送方如果收到3次对同一个包的确认,就重传最后一个未被确认的包。阈值设置为3被证实可以减少乱序包导致的无作用的重传。选择性确认(SACK)的使用明确反馈哪个包收到了,极大改善了TCP重传必要的包的能力。

超时重传

发送方使用一个保守估计时间作为收到数据包的确认的超时上限。如果超过这个上限仍未收到确认包,发送方就重传这个数据包。每当发送方收到确认包后,会重置这个重传定时器。如果重传定时器被触发,仍然没有收到确认包,定时器的值将被设置为前次值的二倍(直到特定阈值)。这可对抗中间人攻击方式的决绝服务攻击,这种攻击愚弄发送者重传很多次导致接收者被压垮。

数据传输举例

  1. 发送方首先发送第一个包含序列号为1(可变化)和1460字节数据的TCP报文段给接收方。接收方以一个没有数据的TCP报文段来回复(只含报头),用确认号1461来表示已完全收到并请求下一个报文段。
  2. 发送方然后发送第二个包含序列号为1461,长度为1460字节的数据的TCP报文段给接收方。正常情况下,接收方以一个没有数据的TCP报文段来回复,用确认号2921(1461+1460)来表示已完全收到并请求下一个报文段。发送接收这样继续下去。
  3. 然而当这些数据包都是相连的情况下,接收方没有必要每一次都回应。比如,他收到第1到5条TCP报文段,只需回应第五条就行了。在例子中第3条TCP报文段被丢失了,所以尽管他收到了第4和5条,然而他只能回应第2条。
  4. 发送方在发送了第三条以后,没能收到回应,因此当时钟(timer)过时(expire)时,他重发第三条。(每次发送者发送一条TCP报文段后,都会再次启动一次时钟:RTT)。
  5. 这次第三条被成功接收,接收方可以直接确认第5条,因为4,5两条已收到。

校验和

TCP的16位的校验和(checksum)的计算和校验过程如下:发送者将TCP报文段的头部和数据部分的和计算出来,再对其求反码,就得到了校验和,然后将结果装入报文中传输。接收者在收到报文后再按相同的算法计算一次校验和。这里使用的反码使得接收者不用再将校验和和字段保存起来清零,而可以直接将报文段连同校验加总。如果计算结果似乎权全部为一,那么就表示了报文的完成性和正确性。

按照现在的标准,TCP校验和是一个比较脆弱的校验。

流控制

流量控制用来避免主机分组发送得过快而使接收方来不及完全收下,一般由接收方通告发送方进行调控。

TCP使用滑动窗口协议实现流量控制。接受方在“接收窗口”域指出还可接收的字节数量。发送方在没有新的确认包的情况下至多发送“接收窗口”允许的字节数量。接收方可修改“接收窗口”的值。

当接收方宣布接收窗口的值为0发送方停止进一步发送数据,开始了“保持定时器”(persist timer),以避免因随后的修改接收窗口的数据包丢失使连接的双侧进入死锁,发送方无法发出数据直至收到接收方修改窗口的指示。当“保持定时器”到期时,TCP发送方尝试恢复发送一个小的ZWP包(Zero Window Probe),期待接收方回复一个带着新的接收窗口大小的确认包。一般ZWP包会设置成3次,如果3次过后还是0的话,有的TCP实现就会发RST把链接断了。

如果接收方以很小的增量来处理到来的数据,它会发布一系列小的接收窗口。这被称作愚蠢窗口综合征,因为它在TCP的数据包中发送很少的一些字节,相对于TCP包头是很大的开销。解决这个问题,就要避免对小的window size做出响应,直到有足够大的window size再响应:

  • 接收端使用David D Clark算法:如果收到的数据导致window size小于某个值,可以直接ack把window给关闭了,阻止了发送端再发数据。等到接收端处理了一些数据后windows size大于等于了MSS,或者接收端buffer有一半为空,就可以把window打开让发送端再发数据过来。
  • 发送端使用Nagle算法来延时处理,条件一:Window Size>=MSS 或是 Data Size >=MSS;条件二:等待时间或是超时200ms,这两个条件有一个满足,才会发数据,否则就是在积累数据。Nagle算法默认是打开的,所以对于一些需要小包场景的程序——比如像telnet或ssh这样的交互性程序,需要关闭这个算法。可以在Socket设置TCP_NODELAY选项来关闭这个算法。

拥塞控制

拥塞控制是发送方根据网络的承载情况控制分组的发送量,以获取高性能又能避免拥塞崩溃(congestion collapse网络性能下降几个数量级)。在网络流之间产生近似最大最小公平分配。

发送方与接收方根据确认包或者包丢失的情况,以及定时器,估计网络拥塞情况,从而修改数据流的行为,这称为拥塞控制或网络拥塞避免。

TCP的现代实现包含四种相互影响的拥塞控制算法:慢开始、拥塞避免、快速重传、快速恢复

此外,发送方采取“超时冲重传”(retransmission timeout,RTO),这是估计出来回通信延迟(RTT)以及RTT的方差。

最大分段大小

最大分段大小(MSS)是在单个分段中TCP愿意接受的数据的字节数最大值。MSS应当足够小以避免IP分分片,它会导致丢包或过多的重传。在TCP连接创建时,双端在SYN报文中用MSS宣布各自的MSS,这是从双端各自直接相连的数据链路层的做大传输单元(MTU)的尺寸减去固定的IP首部和TCP首部长度。以太网MTU为1500字节,MSS值可达1460字节。如果目的IP地址为“非本地的”,MSS通常的默认值为536(这个值允许20字节的IP首部和20字节的TCp首部以适合576字节的IP数据包)。此外,发送方可用传输路径MTU发现(RFC1191)推导出发送方到接收方的网络路径上的最小MTU,以此动态调整MSS以避免网络IP分片。

MSS发布也被称作“MSS协商”(MSS negotiation)。严格讲,这并非是协商出来的一个统一的MSS值,TCP允许连接两端使用各自不同的MSS值。例如,这会发生在参与TCP连接的一台设备使用非常少的内存处理到来的TCP分组。

选择确认

最初采取累计确认的TCP协议在丢包时效率很低。例如,假设通过10个分组发出了1万个字节的数据。如果第一个分组丢失,在纯粹的累计确认协议下,接收方不能说它成功收到了1,000到9,999字节,但未收到包含0到999字节的第一个分组。因而,发送方可能必须重传所有1万个字节。

为此,TCP采取了“选择确认”(selective acknowledgment,SACK)选项。RFC 2018对此定义为允许接收方确认它成功收到的分组的不连续的块,以及基础TCP确认的成功收到最后连续字节序号。这种确认可以指出SACK block,包含了已经成功收到的连续范围的开始与结束字节序号。在上述例子中,接收方可以发出SACK指出序号1000到9999,发送方因此知道只需重发第一个分组(字节 0 到 999)。

TCP发送方会把乱序收包当作丢包,因此会重传乱序收到的包,导致连接的性能下降。重复SACK选项(duplicate-SACK option)是定义在RFC 2883中的SACK的一项扩展,可解决这一问题接收方发出D-ACK指出没有丢包,接收方恢复到高传输率。D-SACK使用了SACK的第一个段来做标志,

  • 如果SACK的第一个段的范围被ACK所覆盖,那么就是D-SACK;
  • 如果SACK的第一个段的范围被SACK的第二个段覆盖,那么就是D-SACK

D-SACK旨在告诉发送端:收到了重复的数据,数据包没有丢,丢的是ACK包;或者“Fast Retransmit算法”触发的重传不是因为发出去的包丢了,也不是因为回应的ACK包丢了,而是因为网络延时导致的reordering。

SACK选项并不是强制的。仅当双端都支持时才会被使用。TCP连接创建时会在TCP头中协商SACK细节。在 Linux下,可以通过tcp_sack参数打开SACK功能(Linux 2.4后默认打开)。Linux下的tcp_dsack参数用于开启D-SACK功能(Linux 2.4后默认打开)。选择确认也用于流控制传输协议 (SCTP).

TCP窗口缩放选项

TCP窗口尺寸域控制数据包在2到65535字节。RFC 1323定义的TCP窗口缩放选项用于把最大窗口尺寸从65535字节夸大到1G字节。扩大窗口尺寸时TCP优化的需要。

窗口缩放选项尽在TCP三次握手时双端在SYN包中独立指出这个方向的缩放指数。该数值是16比特窗口尺寸的向左位移数,从0到14。

某些路由器或分组防火墙会重写窗口缩放选项,这可能导致不稳定的网络传输。

TCP时间戳

RFC 1323定义了TCP时间戳,并不对应于系统时钟,使用随机值初始化。许多操作系统每毫秒增加一次时间戳;但RFC只规定tick应当成比例。

有两个时间戳域:

4字节的发送时间戳值
4字节的响应回复时间戳值(最近收到数据的时间戳)

TCP时间戳用于“防止序列号回绕算法”(Protection Against Wrapped Sequence numbers,PAWS),细节见RFC 1323。PAWS用于接收窗口跨序号回绕边界。这种情形下一个包可能会重传以回答问题:“是否是第一个还是第二个4 GB的序号?”时间戳可以打破这一问题。

另外,Eifel检测算法(RFC 3522)使用TCP时间戳确定如果重传发生是因为丢包还是简单乱序。

 

终结通路

连接终止使用四次握手(Four-way handshake),在这个过程中连接的每一侧都独立地被终止。当一个端点要停止它一侧的连接,就向对侧发送FIN,对侧回复ACK表示确认。因此,拆掉一侧的连接过程需要一对FIN和ACK,分别由两侧端点发出。

首先发出FIN的一侧,如果给对侧的FIN响应了ACK,那么就会超时等待2*MSL时间,然后关闭连接。在这段超时等待时间内,本地的端口不能被新连接使用;避免延时的包的到达与随后的新连接相混淆。RFC793定义了MSL为2分钟,Linux设置成了30s。参数tcp_max_tw_buckets控制并发的TIME_WAIT的数量,默认值是180000,如果超限,那么,系统会把多的TIME_WAIT状态的连接给destory掉,然后在日志里打一个警告(如:time wait bucket table overflow)

连接可以工作在TCP半开状态。即一侧关闭了连接,不再发送数据;但另一侧没有关闭连接,仍可以发送数据。已关闭的一侧仍然应接收数据,直至对侧也关闭了连接。

也可以通过三次握手关闭连接。主机A发出FIN,主机B回复FIN & ACK,然后主机A回复ACK.

状态编码

下表为TCP状态码列表,以S指代服务器,C指代客户端,S&C表示两者,S/C表示两者之一:

LISTEN S
服务器等待从任意远程TCP端口的连接请求。侦听状态。
SYN-SENT C
客户在发送连接请求后等待匹配的连接请求。通过connect()函数向服务器发出一个同步(SYNC)信号后进入此状态。
SYN-RECEIVED S
服务器已经收到并发送同步(SYNC)信号之后等待确认(ACK)请求。
ESTABLISHED S&C
服务器与客户的连接已经打开,收到的数据可以发送给用户。数据传输步骤的正常情况。此时连接两端是平等的。这称作全连接。
FIN-WAIT-1 S&C
(服务器或客户)主动关闭端调用close()函数发出FIN请求包,表示本方的数据发送全部结束,等待TCP连接另一端的ACK确认包或FIN&ACK请求包。
FIN-WAIT-2 S&C
主动关闭端在FIN-WAIT-1状态下收到ACK确认包,进入等待远程TCP的连接终止请求的半关闭状态。这时可以接收数据,但不再发送数据。
CLOSE-WAIT S&C
被动关闭端接到FIN后,就发出ACK以回应FIN请求,并进入等待本地用户的连接终止请求的半关闭状态。这时可以发送数据,但不再接收数据。
CLOSING S&C
在发出FIN后,又收到对方发来的FIN后,进入等待对方对己方的连接终止(FIN)的确认(ACK)的状态。少见。
LAST-ACK S&C
被动关闭端全部数据发送完成之后,向主动关闭端发送FIN,进入等待确认包的状态。
TIME-WAIT S/C
主动关闭端接收到FIN后,就发送ACK包,等待足够时间以确保被动关闭端收到了终止请求的确认包。【按照RFC 793,一个连接可以在TIME-WAIT保证最大四分钟,即最大分段寿命(maximum segment lifetime)的2倍】
CLOSED S&C
完全没有连接。

数据包结构

  • 来源连接端口(16位长)-识别发送连接端口
  • 目的连接端口(16位长)-识别接收连接端口
  • 序列号(seq,32位长)
    • 如果含有同步化旗标(SYN),则此为最初的序列号;第一个数据比特的序列码为本序列号加一。
    • 如果没有同步化旗标(SYN),则此为第一个数据比特的序列码。
  • 确认号(ack,32位长)—期望收到的数据的开始序列号。也即已经收到的数据的字节长度加1。
  • 数据偏移(4位长)—以4字节为单位计算出的数据段开始地址的偏移值。
  • 保留(3比特长)—须置0
  • 标志符(9比特长)
    • NS—ECN-nonce。
    • CWR—Congestion Window Reduced。
    • ECE—ECN-Echo有两种意思,取决于SYN标志的值。
    • URG—为1表示高优先级数据包,紧急指针字段有效。
    • ACK—为1表示确认号字段有效
    • PSH—为1表示是带有PUSH标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满。
    • RST—为1表示出现严重差错。可能需要重现创建TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求。
    • SYN—为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步
    • FIN—为1表示发送方没有数据要传输了,要求释放连接。
  • 窗口(WIN,16位长)—表示从确认号开始,本报文的接受方可以接收的字节数,即接收窗口大小。用于流量控制。
  • 校验和(Checksum,16位长)—对整个的TCP报文段,包括TCP头部和TCP数据,以16位字进行计算所得。这是一个强制性的字段。
  • 紧急指针(16位长)—本报文段中的紧急数据的最后一个字节的序号。

 

参考

  1. 《TCP/IP详解卷1》
  2. TCP的那些事儿
  3. TCP的那些事儿-下

 

转载于:https://www.cnblogs.com/Jamie1032797633/p/10844790.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值