Open System Interconnection Model,开放式系统互联模型,是一种概念模型,试图使计算机在世界范围内互联的标准框架,由国际标准化组织(International Organization for Standardization,简称IOS)提出。
OSI分七层,从低到高分别为:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
名称 | 作用 |
---|---|
物理层 | 在互联网上发送数据帧,负责管理电脑通信设备和网络媒体之间的互通,包括:集线器,中继器,网卡,主机接口卡等 |
数据链路层 | 负责网络寻址,错误侦测和改错。分为逻辑链路控制子层和介质访问控制子层。包括以太网,无线局域网和通用分组无线服务等 |
网络层 | 决定数据路径选择和转寄。包括互联网协议IP等 |
传输层 | 将网络层的数据报包装成传输层协议的分组,包括传输控制协议TCP,用户数据报协议UDP等 |
会话层 | 负责在数据传输中设置和维护计算机之间的通信连接 |
表示层 | 把数据转换为能与接受者兼容的传输格式 |
应用层 | 提供为软件设计的接口,用于软件之间的通信。包括HTTP、HTTPS等 |
网络层的IP数据报不提供TCP这样的可靠数据流机制,而是提供不可靠的包交换机制。
流程大致是这样的:应用层向传输层发送用8位字节表示的数据流,TCP把数据流分割成适当长度的报文段,再把结果包传给IP层,IP层透过网络将包传送给接收端实体的TCP层。
Transmission Control Protocl,传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。
数据包结构
来源连接端口(16位长)-识别发送连接端口
目的连接端口(16位长)-识别接收连接端口
序列号(seq,32位长)
确认号(ack,32位长)—期望收到的数据的开始序列号。也即已经收到的数据的字节长度加1。
资料偏移(4位长)—以4字节为单位计算出的数据段开始地址的偏移值。
保留(3比特长)—须置0
标志符(9比特长)
窗口(WIN,16位长)—表示从确认号开始,本报文的发送方可以接收的字节数,即接收窗口大小。用于流量控制。
校验和(Checksum,16位长)—对整个的TCP报文段,包括TCP头部和TCP数据,以16位字进行计算所得。这是一个强制性的字段。
紧急指针(16位长)—本报文段中的紧急数据的最后一个字节的序号。
选项字段—最多40字节。每个选项的开始是1字节的kind字段,说明选项的类型。
0:选项表结束(1字节)
1:无操作(1字节)用于选项字段之间的字边界对齐。
2:最大报文段长度(4字节,Maximum Segment Size,MSS)通常在创建连接而设置SYN标志的数据包中指明这个选项,指明本端所能接收的最大长度的报文段。通常将MSS设置为(MTU-40)字节,携带TCP报文段的IP数据报的长度就不会超过MTU(MTU最大长度为1518字节,最短为64字节),从而避免本机发生IP分片。只能出现在同步报文段中,否则将被忽略。
3:窗口扩大因子(3字节,wscale),取值0-14。用来把TCP的窗口的值左移的位数,使窗口值乘倍。只能出现在同步报文段中,否则将被忽略。这是因为现在的TCP接收数据缓冲区(接收窗口)的长度通常大于65535字节。
4:sackOK—发送端支持并同意使用SACK选项。
5:SACK实际工作的选项。
8:时间戳(10字节,TCP Timestamps Option,TSopt)
19:MD5摘要,将TCP伪首部、校验和为0的TCP首部、TCP数据段、通信双方约定的密钥(可选)计算出MD5摘要值并附加到该选项中,作为类似对TCP报文的签名。通过 RFC 2385 引入,主要用于增强BGP通信的安全性。
29:安全摘要,通过 RFC 5925 引入,将“MD5摘要”的散列方法更换为SHA散列算法。
大致分为三个阶段:创建连接(三次握手),传输数据 和 终止连接(四次挥手)。
三次握手过程中,很多参数要被初始化:
两个终端之间相互通信,通常是由一端(服务器端,passive open)打开一个套接字然后监听来自另一方(客户端,active open)的连接。
服务器端执行了listen函数后,就在服务器上创建两个队列:
1、客户端执行connect() 向服务器端发送一个SYN包,请求一个连接,该SYN包携带客户端为这个连接请求而设定的随机数A作为信息序列号。
2、服务器端收到一个合法的SYN包后,把该包放入SYN队列中;送回一个SYN/ACK包。ACK的确认码为A+1,SYN/ACK包本身携带一个随机产生的序号B。
3、客户端收到SYN/ACK包后,发送一个ACK包,该包的序号被设定为A+1,而ACK的确认码为B+1。然后客户端的connect()成功返回。当服务器端收到这个ACK包的时候,把请求帧从SYN队列中移出,放到ACCEPT队列中,此时的accept()如果处于阻塞状态,可以被唤醒。从ACCEPT队列中取出ACK包,重新创建一个新的用户双向通信的sockfd,并返回。
如果服务器端接收到客户端发来的SYN包后回了SYN/ACK包,客户端掉线了,服务器端没有收到客户端响应的ACK,那么 这个连接处于一个中间状态,既没有成功,也没有失败。于是,服务器端如果在一定时间内没有收到客户端的确认包,会重发SYN/ACK包。
linux下,重试的次数n默认是5次,重试的时间间隔是2^(n-1)秒,即1s、2s、4s、8s、16s,总共31s,第五次发出后还要等32s才知道第五次也超时了,所以总共需要63s,TCP才会断开连接。
使用三个TCP参数来调整行为:tcp_synack_retries减少重试次数、tcp_max_syn_backlog增大SYN连接数、tcp_abort_on_overflow决定超出能力时的行为。
三次握手的目的是:为了防止已失效连接的请求报文传送到服务端,而产生错误。 也解决了网络中的延迟导致重复分组的问题(或者为什么不是两次握手)。
例如:客户端发出的第一个连接请求报文并没有丢失,而是在某个节点滞留了很长时间,在连接已经释放后传送到服务端,这本来是一个失效的报文,但服务端收到报文段后,误认为客户端发出的新请求,于是向客户端发起第二次握手。假设不采用三次握手,而是两次握手,那么现在这种情况已经建立连接完成了,服务器端收发报文的能力都正常,但客户端却因并没有发出请求而不理会服务端,服务端会进入重试阶段,这样浪费了服务端的资源。
关于网络中面试时,总被问到的四次握手更扯淡了。首先我们需要明确一点是,TCP创建连接和终止连接的前提,是保证客户端和服务端收发报文的能力都正常。三次握手已经确认了这一点,那第四次握手的目的是什么?
主机收到一个TCP包时,用两端的IP地址与端口号来标识这个TCP包属于哪一个session。
用一张表存储所有的session,表中每一条数据称作Transmission Control Block(TCB)。
TCP可靠性的保障:
通常在每个TCP报文段中都有一对序号和确认号。TCP报文发送者称自己的字节流的编号为序号(sequence number),称接收到对方的字节流编号为确认号。TCP报文的接收者为了确保可靠性,在接收到一定数量的连续字节流后才发送确认。这是对TCP的一种扩展,称为选择确认(Selective Acknowledgement)。选择确认使得TCP接收者可以对乱序到达的数据块进行确认。每一个字节传输过后,SN号都会递增1。
通过使用序号和确认号,TCP层可以把收到的报文段中的字节按正确的顺序交付给应用层。序号是32位的无符号数,在它增大到232-1时,便会回绕到0。对于初始化序列号(ISN)的选择是TCP中关键的一个操作,它可以确保强壮性和安全性。
TCP协议使用序号标识每端发出的字节的顺序,从而另一端接收数据时可以重建顺序,无惧传输时的包的乱序交付或丢包。在发送第一个包时(SYN包),选择一个随机数作为序号的初值,以克制TCP序号预测攻击.
发送确认包(Acks),携带了接收到的对方发来的字节流的编号,称为确认号,以告诉对方已经成功接收的数据流的字节位置。Ack并不意味着数据已经交付了上层应用程序。
可靠性通过发送方检测到丢失的传输数据并重传这些数据。包括超时重传(Retransmission timeout,RTO)与重复累计确认(duplicate cumulative acknowledgements,DupAcks)。
如果一个包(不妨设它的序号是100,即该包始于第100字节)丢失,接收方就不能确认这个包及其以后的包,因为采用了累计ack。接收方在收到100以后的包时,发出对包含第99字节的包的确认。这种重复确认是包丢失的信号。发送方如果收到3次对同一个包的确认,就重传最后一个未被确认的包。阈值设为3被证实可以减少乱序包导致的无作用的重传(spurious retransmission)现象。选择性确认(SACK)的使用能明确反馈哪个包收到了,极大改善了TCP重传必要的包的能力。
发送方使用一个保守估计的时间作为收到数据包的确认的超时上限。如果超过这个上限仍未收到确认包,发送方将重传这个数据包。每当发送方收到确认包后,会重置这个重传定时器。
如果重传定时器被触发,仍然没有收到确认包,定时器的值将被设为前次值的二倍(直到特定阈值)。
这是由于存在一类通过欺骗发送者使其重传多次,进而压垮接收者的攻击,而使用上述的定时器策略可以避免此类中间人攻击方式的拒绝服务攻击。
TCP的16位的校验和(checksum)的计算和检验过程如下:
发送者将TCP报文段的头部和数据部分的和计算出来,再对其求反码(一的补码),就得到了校验和,然后将结果装入报文中传输。(这里用反码和的原因是这种方法的循环进位使校验和可以在16位、32位、64位等情况下的计算结果再叠加后相同)接收者在收到报文后再按相同的算法计算一次校验和。这里使用的反码使得接收者不用再将校验和字段保存起来后清零,而可以直接将报文段连同校验加总。如果计算结果是全部为一,那么就表示了报文的完整性和正确性。
注意:TCP校验和也包括了96位的伪头部,其中有源地址、目的地址、协议以及TCP的长度。这可以避免报文被错误地路由。
按现在的标准,TCP的校验和是一个比较脆弱的校验。出错概率高的数据链路层需要更高的能力来探测和纠正连接错误。TCP如果是在今天设计的,它很可能有一个32位的CRC校验来纠错,而不是使用校验和。但是通过在第二层使用通常的CRC校验或更完全一点的校验可以部分地弥补这种脆弱的校验。第二层是在TCP层和IP层之下的,比如PPP或以太网,它们使用了这些校验。但是这也并不意味着TCP的16位校验和是冗余的,对于因特网传输的观察,表明在受CRC校验保护的各层之间,软件和硬件的错误通常也会在报文中引入错误,而端到端的TCP校验能够捕捉到大部分简单的错误。这就是应用中的端到端原则。
流量控制用来避免主机分组发送得过快而使接收方来不及完全收下,一般由接收方通告给发送方进行调控。TCP使用滑动窗口协议实现流量控制。接收方在“接收窗口”域指出还可接收的字节数量。发送方在没有新的确认包的情况下至多发送“接收窗口”允许的字节数量。接收方可修改“接收窗口”的值。
当接收方宣布接收窗口的值为0,发送方停止进一步发送数据,开始了“保持定时器”(persist timer),以避免因随后的修改接收窗口的数据包丢失使连接的双侧进入死锁,发送方无法发出数据直至收到接收方修改窗口的指示。当“保持定时器”到期时,TCP发送方尝试恢复发送一个小的ZWP包(Zero Window Probe),期待接收方回复一个带着新的接收窗口大小的确认包。一般ZWP包会设置成3次,如果3次过后还是0的话,有的TCP实现就会发RST把链接断了。
如果接收方以很小的增量来处理到来的数据,它会发布一系列小的接收窗口,因为它在TCP的数据包中发送很少的一些字节,相对于TCP包头是很大的开销。解决这个问题,就要避免对小的window size做出响应,直到有足够大的window size再响应:
拥塞控制是发送方根据网络的承载情况控制分组的发送量,以获取高性能又能避免拥塞崩溃(congestion collapse,网络性能下降几个数量级)。这在网络流之间产生近似最大最小公平分配。
发送方与接收方根据确认包或者包丢失的情况,以及定时器,估计网络拥塞情况,从而修改数据流的行为,这称为拥塞控制或网络拥塞避免。
TCP的现代实现包含四种相互影响的拥塞控制算法:慢开始、拥塞避免、快速重传、快速恢复。
此外,发送方采取“超时重传”(retransmission timeout,RTO),这是估计出来回通信延迟 (RTT) 以及RTT的方差。
RFC793中定义的计算SRTT的经典算法:指数加权移动平均(Exponential weighted moving average)。
目前有很多TCP拥塞控制算法在研究中。
最大分段大小 (MSS)是在单个分段中TCP愿意接受的数据的字节数最大值。MSS应当足够小以避免IP分片,它会导致丢包或过多的重传。在TCP连接创建时,双端在SYN报文中用MSS选项宣布各自的MSS,这是从双端各自直接相连的数据链路层的最大传输单元(MTU)的尺寸减去固定的IP首部和TCP首部长度。以太网MTU为1500字节, MSS值可达1460字节。使用IEEE 802.3的MTU为1492字节,MSS可达1452字节。如果目的IP地址为“非本地的”,MSS通常的默认值为536(这个默认值允许20字节的IP首部和20字节的TCP首部以适合576字节IP数据报)。此外,发送方可用传输路径MTU发现(RFC 1191)推导出从发送方到接收方的网络路径上的最小MTU,以此动态调整MSS以避免网络IP分片。
MSS发布也被称作“MSS协商”(MSS negotiation)。严格讲,这并非是协商出来一个统一的MSS值,TCP允许连接两端使用各自不同的MSS值。[7] 例如,这会发生在参与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窗口尺寸域控制数据包在2至65,535字节。RFC 1323 定义的TCP窗口缩放选项用于把最大窗口尺寸从65,535字节扩大至1G字节。扩大窗口尺寸是TCP优化的需要。
窗口缩放选项尽在TCP三次握手时双端在SYN包中独立指出这个方向的缩放系数。该值是16比特窗口尺寸的向左位移数,从0 (表示不位移)至14。
某些路由器或分组防火墙会重写窗口缩放选项,这可能导致不稳定的网络传输。
RFC 1323 定义了TCP时间戳,并不对应于系统时钟,使用随机值初始化。许多操作系统每毫秒增加一次时间戳;但RFC只规定tick应当成比例。
有两个时间戳域:
4字节的发送时间戳值
4字节的响应回复时间戳值(最近收到数据的时间戳)
TCP时间戳用于“防止序列号回绕算法”(Protection Against Wrapped Sequence numbers,PAWS),细节见RFC 1323。PAWS用于接收窗口跨序号回绕边界。这种情形下一个包可能会重传以回答问题:“是否是第一个还是第二个4 GB的序号?”时间戳可以打破这一问题。
另外,Eifel检测算法( RFC 3522 )使用TCP时间戳确定如果重传发生是因为丢包还是简单乱序。
最近统计表明时间戳的采用率停滞在~40%,这归因于Windows服务器从Windows Server 2008起降低了支持
即四次挥手。这个过程中连接的每一侧都独立地被终止。
当一端要停止它这一侧的连接时,会发出FIN包,对侧回应ACK表示确认。即终止一侧的连接需要一对FIN和ACK,分别由两端发出。连接可以工作在TCP半开状态。即一侧关闭了连接,不再发送数据;但另一侧没有关闭连接,仍可以发送数据。已关闭的一侧仍然应接收数据,直至对侧也关闭了连接。
首先发出FIN的一端,如果给对侧的FIN响应了ACK,name会超时等待2*MSL时间,然后关闭连接,在超时等待这段时间内,本地端口不能被新连接使用,避免延时的包到达后发生混淆。
RFC793定义了MSL为两分钟,Linux设置为30s。参数tcp_max_tw_buckets控制并发的TIME_WAIT的数量,默认值是180000,如果超限,系统会把多雨的TIME_WAIT状态的连接给destory掉,然后再日志中进行警告。
下表为TCP状态码列表,以S指代服务器,C指代客户端,S&C表示两者,S/C表示两者之一:
传输控制协议