TCP,UDP和ICMP
TCP:TCP将应用的数据流包装为TCP Segment,然后TCP Segment又在网络层被封装为IP Datagram。TCP创建的是双向通信通道。
三次握手协议:首先A向B发送一个Syn信号(发起一个新的连接请求),同时发送一个基数字,表明数据从哪个数字开始。然后B发送一个Syn和Ack信号(确认已成功收到了对方的数据),然后建立B到A的连接。然后A向B发送一个Ack信号,建立A到B的连接。
关闭TCP连接:A向B发送一个Fin(finish)信号,表明A不再会向B发送信息,B会向A发送一个Ack信号并关闭A到B的连接。自此,A和B之间成为单向连通,B可以向A发送信号。同理,B之后又向A发送Fin信号,然后A向B发送Ack信号。
TCP向两个应用程序之间提供了可靠的字节流。TCP使用4种机制来确保“可靠性”:
1.当TCP层接受到数据时,它会向发送方发送一个确认包来告知数据已经到达。
2.使用校验和来保证数据没有损坏。
3.序列号用来检验数据是否丢失,每个TCP Segment头部都携带该段第一个字节在字节流中的序列号,如果一个段的序列号为500,并且携带500字节的数据,那么下一段将携带序列号1000。(同时TCP也使用序列号来将TCP Segment重新排好序,确保接受到的数据始终和源数据相同)
4.Flow-control,避免一方发送数据过快,而另一方来不及接受。此时接受方会不断告诉发送方是否可以继续发送。
Congestion control(拥塞控制)。
TCP Header:
destination port,目标端口(端口用于标识主机中的应用程序)。
source port,来源端口,例如当接受方回复时需要使用。
sequence number,序列号,表示TCP数据字段中第一个字节在字节流中的位置。
acknowledgement number,告诉另一端我们期望接受的下一个字节,也表示我们已经成功接受到这个字节之前的所有字节。
header length filed,告诉我们TCP头部有多长。
标志:包括ACK,SYN,FIN,PSH等。
其他:下文会讲
在任何时刻,source port,destination port和IPV4 Hdr中的IP DA,IP SA(source address),Protocol ID(=“TCP”)这五者标识了整个网络中的唯一的TCP连接。
为了确保这点,Host A为每个新的连接都提供一个递增的端口,但是仍然有可能会回绕。通过使用随机的初始序列号可以减少这种混淆的发生。
小结:TCP在应用进程之间提供有序,可靠的字节流传输。
UDP:和TCP一样,UDP datagram也被封装在IP datagram中。UDP只提供不可靠的字节流,因而UDP Header比TCP Header简单的多,它只有source port,destination port,length(指明UDP datagram的长度),checksum(可选,它包含IP Header的一部分)。
UDP协议的三个特性:
1.无需连接。谈到这里,也让我们对TCP的所谓连接可以有更好的认识,TCP协议的所谓连接其实并不是和TCP没有关系的一个类似于实体电线的关系,它是对TCP协议的两端之间【不断互相发送数据并且不断发送“我已经确认接受了你的xx数据”】这样的关系的一个抽象概括,实际上网络层仍然是尽力而为的,只是TCP通过TCP Header中的数据确保了这样的”连接“而已。这也是为什么老师会在课程中说,UDP表现得就像是IP层提供的服务一样。
2.self contained datagrams,即自包含,UDP datagrams包含他所有需要的信息,甚至IP的部分。
3.unreliable delivery:没有acknowledgements来确认数据已经被接受,不能处理丢失和顺序错误的datagrams,也没有flow control。
总结:UDP在应用进程之间提供更简单的数据段的传输服务。。
字节流和数据段:区别在于字节流有序(接收方应用程序读取字节的顺序与发送方应用程序写入字节的顺序完全一致),并且无边界性(协议不保留发送方写入操作的边界,例如发送方write("ad"),write("dd"),可能接收方一次就接受"addd",而UDP绝不可能一次接受一个数据段的一部分或两个数据段的混合)
ICMP协议(Internet Control Message Protocol):
有三种机制来确保互联网中的网络层正确工作:
1. IP
2. 路由器内部的转发表
3. ICMP,ICMP不是用来传输用户数据的(像TCP或UDP那样),而是专门用于在IP网络设备(如路由器、主机)之间传递控制、诊断和错误信息的。
ICMP信息类型:包括ICMP Type和ICMP Code。
ICMP的特征:
1.报告信息,一条自包含的报错的信息。
2.不可靠,只是提供简单的数据段。
ICMP是一个传输层的机制,但是它是为网络层而服务的。
ICMP和UDP的区别:UDP服务的对象是上层应用,而ICMP的服务对象是IP层,它不包含端口等信息,它也只能停留在接受方的网络层。虽然ICMP产生的原因可以来自网络层自身或者传输层,但它几乎总是在网络层产生的。
端到端原则:端到端原则主张保持网络核心(IP层)的简单和“无知”,只负责尽力而为地转发数据包。而将所有复杂的、需要状态和智能的功能(如可靠性、会话管理、数据格式化)都推到网络的边缘(终端主机上的TCP和应用程序)去实现。
事实上,端到端原则也是确保正确性的一个必要的原则。中间过程中在进行检查时,因为缺少终点端的信息,所以它不可能进行完备的错误检查,它最多只能提供一个不完全的特性版本来进行优化。这样的优化一方面可能确实有一定好处,但是另一方面,这样的优化都建立在对相应层级的一定假设之上。随着这样的优化越来越多,在重新设计新的协议的时候也必须建立在这些前提的基础之上,使之受到这些前提的限制也会越来越多。
三种主要的错误检测算法:
校验和:将数据包中的所有数据相加,优点在于快,缺点在于检测错误的能力弱。将数据包中的每个16位字相加,每当求和结果大于2的16次方,对2的16次方取模。最后,翻转求和结果的比特,将其作为数据包的校验和,将校验和以及整个数据包相加,应该得到全1。校验和只能保证检测出单比特位的错误。
循环冗余校验(CRC):更加复杂,但是检测错误能力保证得到提高。链路层一般使用CRC,由于链路层容易遭受突发错误,使得CRC的突发错误检测能力发挥了价值。CRC的核心功能是数据摘要。它能把一大段数据(n位)通过一个确定性的算法,映射到一个非常短的校验码。具体实现是通过多项式长除法实现,但是我没听懂。CRC可以100%检测出所有奇数个比特的错误。可以100%检测出所有长度小于等于生成多项式位数的突发错误(Burst Errors,即连续的一段比特出错)。在很大概率上(接近100%)可以检测出更长的突发错误。
消息认证码(MAC):对恶意修改的检测能力强,但相比CRC,对错误的检测能力稍弱。MAC不能保证可以检测出任何类型的错误。
在实际的网络中,链路层会用CRC检测,网络层会用校验和检测,TCP会用校验层检测,应用程序自己也有自己的错误检测,所以,几乎很难有错误作为漏网之鱼。但是,必须指出的是链路层和网络层的检验属于中间优化,TCP和应用程序的检验才属于端到端思想的体现。链路层通过检测并丢弃损坏的数据帧避免垃圾数据包毫无意义的路由。网络层通过IP的头部校验和确认IP头没有损坏,毕竟一旦目标IP损坏,这个数据包也显得毫无意义。TCP的校验和由源主机计算,目标主机验证,它保证了数据在从一个操作系统内核到另一个操作系统内核的整个旅程中是完整的,中间的路由器不关心也不修改它。应用层(进程到进程)是最彻底的端到端的保障。
Socket:Socket是操作系统内核为用户空间(应用程序)提供的一个接口,它连接了应用层和传输层。应用程序通过调用Socket API(如connect, send),向操作系统内核下达指令。操作系统内核的TCP/IP协议栈负责执行这些指令,进行真正的数据打包、发送、接收、重组等复杂的网络通信工作。Socket完美地封装了底层的复杂性,让应用程序开发者可以像读写文件一样进行网络通信。Socket分为流式套接字和数据报套接字,两者分别基于TCP和UDP。它是一个强大的抽象,将底层复杂的TCP/IP网络协议,简化成了应用程序可以轻松使用的、类似于文件读写的接口。
有限状态机(finite state machine,FSM):FSM并不是一个物理意义上的机器,它是一种逻辑概念上的模型。它描述了一个系统在任何给定时间,都必然处于有限个(Finite)预定义状态(State)中的一个。该系统可以根据接收到的输入(Input),从一个状态转换(Transition)到另一个状态,并在转换时产生输出(Output)。大概就是一张表,表存储了多个状态和一个状态对所有事件的处理方式。也许这样的模型听起来平平无奇,但他相比之前的通过一堆标志位和一大堆if-else语句的方式相比是巨大的进步。我们只需要专注于将这个数学模型理清楚,就没必要去关注乱七八糟的if-else语句,我们需要做的只是将模型中对应的状态转换和输入输出一一实现即可。TCP的状态机就是一个很好的例子。
流量控制(Flow Control):介绍一个最简单的实现方式,Stop and wait。流量控制解决的基本问题是发送方发送数据的速度超过接受方处理数据的速度。就是这样:
检测重复:当发生ACK Delay,即sender发送数据包1之后,由于超时又发送了数据包2,数据包2出去之后,收到了ACK信息。此时,sender会发送数据包3。之后,我们又一次收到了ACK信息,此时,我们怎么知道收到的是数据包2的ACK信息还是数据包3的ACK信息呢?解决这个问题,需要借助TCP的累计确认机制,具体如下:
发送方发送了序列号为100-199的数据段(包1),但因超时又重传了一次(包2)。
当接收方确认收到包1时,它会回复一个ACK=200,表示“199之前的数据都已收到,请从200开始发”。
发送方收到ACK=200后,便知道数据已达,于是接着发送了新的数据段(包3,序列号200-299),与此同时,它会将超时计时器计时的对象设为包3而非包2。
随后,当迟到的重传数据(包2,序列号仍是100-199)也到达接收方时,接收方会发现这是重复数据并丢弃它。然而,它当前的期望仍然是从序列号200开始,所以它会再次回复一个**ACK=200**。
发送方收到这第二个ACK=200时,会发现这个确认号并没有推进确认的进度(因为它期望的是ACK=300来确认包3),因此,它能毫不含糊地将其识别为一个迟到的、对旧数据的重复确认,而不是对新数据(包3)的确认。
总之,发送方收到一个确认号如果小于或等于其当前记录的“最早未确认序列号”,就不会将其视为对新数据的有效确认,也不会因此发送新的数据包。
接收方如果收到的数据段的序列号 (Sequence Number)大于其当前期望的序列号,则会将这份乱序的数据先缓存起来,并立即回送一个确认号等于当前期望序列号的ACK;如果发现收到的数据段的序列号小于期望的序列号,这说明是重复数据,接收方会直接将其丢弃,无需再发送ACK。发送方和接收方可能会收到很多数据段和ACK,但凭借序列号和确认号这一机制,它们始终能明确地知道通信的进度,并据此做出正确的行为。
注:接收方发送的ACK信号表明该ACK中确认号之前的信息都已经接受完毕,发送的是第一个缺失的字节a而不是最后一个得到的字节。
流量控制:滑动窗口
在stop and wait机制中,大部分时间发送方都在无意义地等待ACK信号,导致对链路的利用率极低。滑动窗口就是在它的基础上进行改进。滑动窗口会根据“网络管道”在某一时刻最多能容纳多少比特的数据以及一个包的大小,来计算出理想的窗口大小(即链路中包的数量),并始终确保链路中包的数量小于等于这个理想值。滑动窗口将无效的等待时间转换为了有效的数据传输时间,带来了巨大的效率提升。
具体实现:发送方需要维护三个核心状态:发送窗口大小,最早未确认的字节的序列号 (窗口左边界),以及下一个将要发送的新字节的序列号 (它将窗口分为“已发送但未确认”和“可发送”两个区域)。
如果发送方从接收方那里得到的确认号 (Acknowledgment Number) ackno,小于或等于其窗口左边界(代表ackno之前的数据早已被确认,对推进窗口没有任何帮助),那么这个ACK通常被忽略(仅用于快速重传计数)。如果ackno大于窗口左边界,那么这是一个有效的新确认,发送方会将窗口左边界滑动到ackno的位置。
对接收方而言,需要维护两个核心状态:接收窗口大小,以及下一个期望收到的字节的序列号 (这也是接收窗口的左边界)。二者圈定了可接收数据的序列号范围。
对于收到的、序列号为seqno的数据段:
如果seqno落在窗口左侧(即seqno小于期望序列号),说明是重复数据,直接丢弃。
如果seqno落在窗口内,但不等于期望序列号,说明是乱序数据,缓存该数据段,并立即回送一个确认号等于当前期望序列号的ACK。
如果seqno落在窗口内,且正好等于期望序列号,则处理这个数据段(以及所有能从缓存中拼接上的连续数据),更新期望序列号,并回送一个确认号等于新的期望序列号的ACK。
如果seqno落在窗口之外(即过于超前),则丢弃该数据段,并
回送一个确认号等于当前期望序列号的ACK。
接受窗口一般小于等于发送窗口。如果大于的话,那么接受方的窗口永远不会被装满,这只是浪费空间。(发送方发送乱序数据把接受方除了窗口最左侧的位置全部填满就已经是设想中接收方窗口所能被占用的最大空间了)
序列号空间之和需要大于等于接受窗口和发送窗口,否则,一旦序列号到底,窗口需要回绕到序列号的开端,这时如果不满足要求的话可能会出现一定问题。
重传策略(Retransmisson Strategies):
回退重传协议:这是一种悲观的策略,当超时的时候,发送方会假设窗口中的所有数据都已经丢失,并将整个窗口中的数据包都重新发送。
选择性重传协议:这是一种更为乐观的策略,当超时的时候,发送方只会重新发送窗口最左端的包。
选择性重传会给所有发送窗口内的所有包都设置计时器,当出现大量包丢失的时候,它的效率会低于回退重传协议。如何选择根据具体情况来权衡。比如当发送窗口大小为N而接受窗口大小为1时(即为Go-Back-N),由于接收窗口不会提前缓存任何包,如果一个包丢失了,接受方得不到它想要的包的话会将剩余的包全部丢弃。此时应该使用选择性重传协议。也可以看出,Go-Back-N的方式在面临乱序数据包时效率非常低下。而当发送窗口大小为N而接受窗口大小为N时,由于接收窗口会提前缓存乱序数据包,所以我们没必要把所有都重传,此时选择性重传是更好的选择。
TCP头详细介绍:
源端口和目标端口:都是16位,用于标识发送和接收的应用程序进程。
序列号 (Sequence Number):32位,标识当前数据段中第一个数据字节在整个字节流中的位置。
确认号 (Acknowledgment Number):32位,当ACK标志位为1时有效,其值代表期望从对方收到的下一个字节的序列号。
数据偏移 (Data Offset):4位,表示TCP头部的总长度(以4字节为单位),用于定位数据载荷的起始位置。
控制位 (Flags):6个1位的标志:
URG: 紧急指针有效。
ACK: 确认号有效。
PSH: 请求接收方立即将数据推送给应用层。
RST: 异常重置连接。
SYN: 发起一个新连接。
FIN: 正常关闭一个方向的数据流。
窗口大小 (Window):16位,由接收方设置,用于流量控制。它告知发送方,自己当前还有多少可用的接收缓冲区空间。
校验和 (Checksum):16位,用于检测TCP头部和数据的完整性。其计算范围包括TCP伪头部(源/目的IP等)、整个TCP头部和整个TCP数据。
紧急指针 (Urgent Pointer):16位,当URG标志位为1时有效,指出紧急数据在字节流中的结束位置。
深入了解TCP建立和拆除:
建立过程会使用TCP头中的四个部分:序列号,确认号,ACK位,SYN位。
三次握手:
第一步:一开始接收方会处于监听状态。然后发送方发送一个设置了SYN位,包含一个起始序列号(表明发送数据的第一个序列号)的数据段。
注:起始序列号不会从0开始,而是出于安全的考虑从一个随机的值开始。源端口分配算法和起始序列号随机大大保证了TCP的安全性。
第二步:接收方接受数据段后,发送一个设置了ACK位和SYN位,包含一个起始序列号和一个确认号(值等于发送方的起始序列号+1)的数据段。
第三步:发送方接受之后发送一个设置了ACK位,包含一个序列号值为起始序列号+1,确认号值为接收方起始序列号+1的数据段。
同时打开:如果双方几乎同时向对方发送了SYN。
第一步:A发送一个设置了SYN位,包含起始序列号SA的数据段。B发送一个设置了SYN位,包含起始序列号SB的数据段。
第二步:A接收到B的数据段之后发送一个设置了SYN和ACK位,包含序列号SA+1,确认号SB+1的数据段。B接收到A的数据段之后发送一个设置了SYN和ACK位,包含序列号SB+1,确认号SA+1的数据段。此时双方都已经同步,知道起始序列号并已经确认,连接就已经建立完成。但是,这种连接建立需要四条消息。
连接关闭:使用ACK位和FIN位。
第一步:A发送一个带有FIN位,序列号为SA,确认号为SB的数据段。
第二步:接受之后,B发送一个带有ACK位,确认号为SA+1的数据段。
注:此时A不能再发送新的应用层数据,但它仍然必须能够、也必须会发送ACK包。
第三步:一段时间后,B发送一个带有FIN位,序列号为SB,确认号为SA+1的数据段。
第四步:接受之后,A发送一个带有ACK位,确认号为SB+1的数据段。
让我们把目光放回三次握手协议:我们会发现,在建立TCP连接后,为了确保一个数据段被发送到接收方,我们的逻辑如下:A向B发送了一个数据段,B接受了A的数据段,然后向A发送了ACK包。如果A接受到了ACK包,那么A就知道这个数据段的发送工作正常,对A而言工作完成。但是,与此同时,B还不知道A收没收到他的确认,它会思考“A到底收没收到我的确认呢,我要不要再发送一个确认消息呢?他怎么不告诉我他已经确认我的确认了呢”。他会吗,他不会。他只会想,“A都没有重发这个数据包,那他肯定收到了我的ACK包了,如果他没收到,应该他自己再重发这个数据包,反正和我没有关系”。而在三次握手协议中,如果我们也采取这样的逻辑呢,“哦,A都没有重发SYN,那他肯定收到了我的SYN+ACK,如果他没收到我的SYN+ACK,应该由他自己重发SYN,我不需要再确认他的确认。如果他没有重发,那么我就确认他的确认了。不要再发送一个ACK来烦我”。这样的话,看起来三次握手只需要两次就能完成连接了。
但问题在于,在建立TCP连接后的发送数据段中,我们可以很轻松的处理重复的数据段。而在建立连接过程中,如果我们在发送SYN1后,由于SYN1严重滞留,应用进程放弃了这个连接。一段时间后,这个端口又被其他连接复用,向相同的目标端口,目标IP发送了SYN2。而这时,对于接收方而言,他根本不能知道收到的是姗姗来迟的SYN1还是新的SYN2。根据“ 在任何时刻,source port,destination port和IPV4 Hdr中的IP DA,IP SA(source address),Protocol ID(=“TCP”)这五者标识了整个网络中的唯一的TCP连接。”,我们无法区分这二者。如果只是发送SYN+ACK之后就认为,连接已经建立,那么SYN1所建立的连接将永远占用资源并无法被释放。所以,我们需要第三次握手来确保安全。
仍然是上面的情况,假设服务器收到了严重滞留的SYN1并发送了对应SYN1的SYN+ACK。然后发送方收到1这个SYN+ACK。
如果SYN1之前的端口处于空闲状态,内核得到结论:“这是一个不请自来的、指向一个不存在的连接的数据包。这要么是网络错误,要么是某种攻击。我必须立即终止这个无效的通信。然后内核会立即构建并回送一个RST(Reset)包给服务器。服务器(正处于SYN_RECEIVED状态)收到了这个RST包。它会立即放弃这个半开连接,释放所有为它分配的少量资源,并回到LISTEN状态。问题被完美解决。
注:RST包是一个强硬的信号,意思是“立即、无条件地终止这个连接”。
如果SYN1之前的端口现在被新的应用进程所占用,那么收到SYN+ACK之后,会通过核对期望的确认号来保证正确,如果得到的并非期望确认号。为了安全起见,也会发送一个RST。