目录
一、传输层
二、UDP
1、UDP的数据格式
2、UDP头部
三、TCP
1、TCP的数据格式
2、TCP头部
3、TCP是可靠传输(详述)
4、TCP是面向连接的(详述)
网络(一):基础知识
网络(二):传输层TCP、UDP
网络(三):应用层HTTP
网络(四):应用层HTTPS
一、传输层
传输层有2个协议:
- TCP(Transmission Control Protocol),传输控制协议。
- UDP(User Datagram Protocol),用户数据报协议。
TCP和UDP的区别:
- 连接性:TCP是面向连接的,UDP是无连接的。这句话什么意思?这里先通俗地说一下,后面会用专业知识解释:比如说客户端向服务器发送一个请求,如果走的是TCP,就必须先经过三次握手建立连接,连接成功后才发送真正的数据,数据发送完还必须经过四次挥手断开连接;如果走的是UDP,则客户端不会跟服务器建立连接,而是直接朝着服务器扔数据。
- 可靠性:TCP是可靠传输,UDP是不可靠传输。这句话什么意思?这里先通俗地说一下,后面会用专业知识解释:比如说客户端向服务器发送100K的数据,如果走的是TCP,则传输过程中丢了某些字节,TCP会重传,确保这100K的数据完整;如果走的是UDP,则传输过程中丢了某些字节,UDP就不管了,这100K的数据就不完整了。
- 传输速率和延迟:TCP慢、延迟大,UDP快、延迟小。因为TCP是面向连接的,建立连接、保持连接和断开连接都要开销,而且是可靠传输,重传、流量控制、拥塞控制也都要开销,所以相对来说,TCP慢、延迟大,UDP快、延迟小。
- 应用场景:如果我们对数据的完整性要求较高,丢一个包都不行,则使用TCP;如果我们对数据的传输速率和实时性要求较高,数据丢那么几个包也没关系,则使用UDP。比如说我们通过浏览器访问一个网页(应用层对应协议为HTTP、HTTPS),肯定是希望一个完整的网页展示在用户面前,不可能说网页里丢了一段文字还展示给用户,文件传输(应用层对应协议为FTP)、邮件发送(应用层对应协议为SMTP)也是同样道理,它们用的都是TCP;而音视频通话、直播(应用层对应协议RTP等,当然也有RTMP使用的是TCP),我们肯定是希望快速地、实时地传输当前时间节点的数据给对方,有些画面丢了也没什么大不了,这种用的就是UDP了。
- 应用层对应协议:TCP——HTTP、HTTPS、FTP、SMTP、RTMP等,UDP—— RTP等。
二、UDP
1、UDP的数据格式
UDP的数据格式 = UDP头部 + UDP数据部分(即应用层传下来的真正数据)。
2、UDP头部
UDP是无连接的,而且是不可靠传输,所以它的头部就不需要维护很多复杂的参数来做这两件事,固定占8个字节。
- 源端口号:这2个字节存储的是源端口号。假设我们是客户端访问服务器,客户端的端口都是临时开启的随机端口,这次请求结束后,这个随机端口就不占用了,因为占2个字节,所以随机端口共2^16=65536个,随机端口号取值范围为0~65535。(那多个客户端随机到同一个端口号会不会有问题?不会,因为数据传输过程中还会携带客户端和服务器的唯一标识——IP地址,所以不会出现错乱)
- 目标端口号:这2个字节存储的是目标端口号。假设我们是客户端访问服务器,一台服务器上会开启很多个端口用来监听来自客户端的请求,每一个端口里都启动着一个服务器软件———Tomcat,每一个Tomcat里又部署着若干个Java项目,可见端口和Tomcat是一对一的,Tomcat和项目是一对多的,所以如果想访问某个项目,就必须得确定好是哪个Tomcat,也即必须确定好是哪个端口,目标端口号指的就是这个端口。(服务器的网卡收到数据后,就会根据目标端口号将数据投递到相应的端口,也即投递给相应的Tomcat去处理)
- UDP长度:这2个字节存储的是UDP整个数据段的长度,即UDP头部的长度 + UDP数据部分的长度。
- UDP检验和:这2个字节存储的是UDP检验和。UDP检验和是UDP伪头部 + UDP头部 + UDP数据部分算出来的这么一个数值(UDP伪头部仅在计算UDP检验和时才生成、才有作用,它并不会被传递给网络层)。UDP检验和提供了差错检验的功能,用来验证UDP整个数据段是否有误(有的数据可能在传输过程中发生0-1翻转,即数据出错)。假设我们是客户端访问服务器,那么当服务器收到UDP整个数据段后,也会按照同样的规则算出来一个数值,如果两个值一样,就ok;如果两个值不一样,就会认为传输过程中发生了错误,于是丢掉该数据段(但是因为UDP是不可靠传输,丢掉就丢掉了,不会重传)。
三、TCP
1、TCP的数据格式
TCP的数据格式 = TCP头部 + TCP数据部分(即应用层传下来的真正数据)。
2、TCP头部
TCP是面向连接的,而且是可靠传输,所以它的头部就需要维护很多复杂的参数来做这两件事,至少占20个字节,最多占60个字节,也就是说有40个字节是可选的。
- 源端口号:这2个字节存储的是源端口号。
- 目标端口号:这2个字节存储的是目标端口号。
- 序号(Sequence Number):这4个字节存储的是一个数值,这个数值代表的是当前TCP数据段的偏移量(具体地说,是当前TCP数据段的数据部分的首字节相对于应用层数据首字节的偏移量 + 1)。通常出现在发送方的TCP头部里,代表当前我给你发送的是哪个字节开始的数据,接收方收到数据段后就可以根据这个数值做排序。比如应用层数据有1200个字节,这1200个字节的数据到了传输层后会被切成十二段M1 ~ M12,每段100个字节,那么这十二段数据分别加上TCP头部后就是H1M1 ~ H12M12,则H1里存储的Seq = 1、H2里存储的Seq = 101、H11里存储的Seq = 1001、H12里存储的Seq = 1101。(但实际上为了防止攻击,序号不是直接存储1、101、1001、1101这么简单明确的数据,它会有一个随机初始序号j,然后再加上1、101、1001、1101。因为占4个字节,所以随机初始序号j的取值范围差不多就是0 ~ 2^32=4294967296)
- 确认号(Acknowledgment Number):这4个字节存储的是一个数值,这个数值代表的是希望对方下一次传过来的TCP数据段的偏移量(具体地说,是希望对方下一次传过来的TCP数据段的数据部分的首字节相对于应用层数据首字节的偏移量 + 1),注意只有当ACK = 1时,确认号才有意义。通常出现在接收方的TCP头部里,代表我希望你下次给我发送哪个字节开始的数据,发送方收到数据段后就可以根据这个数值发送相应的数据段。比如我已经收到对方1 ~ 100字节的数据了,希望对方下一次给我传101 ~ 200字节的数据,那我就会给对方回复一个“ACK = 1,Ack = 101”的确认数据。(但实际上为了防止攻击,确认号也不是直接存储101这么简单明确的数据,它也会有一个随机初始确认号k,然后再加上101。因为占4个字节,所以随机初始确认号k的取值范围差不多就是0 ~ 2^32=4294967296)
- 数据偏移:这4位(半个字节)存储的是TCP头部的长度信息。它里面存储的值乘以4就是TCP头部的长度,上面我们知道TCP头部的长度至少占20个字节,所以这个值最小是5(十六进制0x0101),反过来这4位能存储的最大值十六进制0x1111(十进制15),所以我们就知道了TCP头部的长度最多占60个字节,有40个字节是可选的。(上面我们知道UDP头部里有2个字节记录了整个UDP数据段的长度,那这里为什么TCP头部没有记录整个TCP数据段的长度呢?其实UDP头部里记录整个UDP数据段的长度是可有可无的,那2个字节纯粹是为了保证UDP头部的32位对齐,UDP和TCP数据部分长度其实都可以靠网络层计算得来。我们知道网络层一个IP数据包头部里会存储IP头部的长度和整个IP数据包的长度,所以可以计算得到IP数据部分长度 = 整个IP数据包的长度 - IP头部的长度,而IP数据部分长度 = UDP头部长度 + UDP数据部分长度 || IP数据部分长度 = TCP头部长度 + TCP数据部分长度,所以UDP数据部分长度 = IP数据部分长度 - UDP头部长度固定的8个字节 || TCP数据部分长度 = IP数据部分长度 - TCP头部长度)
- 保留:这6位(一个半字节)是保留空间,暂时没什么用,将来可能会有用,存的全是0。
- URG标志位(Urgent,紧急):紧急标志位。当URG = 1时,紧急指针那2个字节才有意义,代表当前TCP数据段内有紧急重要的数据,应该优先尽快传递。
- ACK标志位(Acknowledgment,回复、确认):回复标志位、确认标志位,通常出现在接收方的TCP头部里,当ACK = 1时,代表我收到了你的消息、给你个回复。
- PSH标志位(Push):一般用不上。
- RST标志位(Reset,重置):重置标志位。当RST = 1时,代表当前TCP连接出现了严重问题,必须断开连接,如有需要可重新建立连接。
- SYN标志位(Sync,建立连接、同步):建立连接标志位、同步标志位,既有可能出现在发送方的TCP头部里,也有可能出现在接收方的TCP头部里。当SYN = 1时,代表一个希望跟你建立连接的请求。
- FIN标志位(Finish,断开连接、结束):断开连接标志位、结束标志位,既有可能出现在发送方的TCP头部里,也有可能出现在接收方的TCP头部里。当FIN = 1时,代表一个希望跟你断开连接的请求。
- 窗口(Window):这2个字节存储的是一个数值——被称作窗口大小,用来告诉对方下次最多能给我传输多少个字节的数据。通常出现在接收方的TCP头部里,发送方收到数据段后就可以根据这个数值控制发送的数据量了,一般用来做流量控制。
- 检验和:这2个字节存储的是TCP检验和。跟UDP一样,TCP检验和是TCP伪头部 + TCP头部 + TCP数据部分算出来的这么一个数值,TCP检验和提供了差错检验的功能。假设我们是客户端访问服务器,那么当服务器收到TCP整个数据段后,也会按照同样的规则算出来一个数值,如果两个值一样,就ok;如果两个值不一样,就会认为传输过程中发生了错误,于是丢掉该数据段(但是TCP是可靠传输,会重传)。
- 紧急指针:这2个字节存储的是当前TCP数据段内紧急重要数据的信息。这2个字节要和URG标志位联合使用,当URG = 0时,这2个字节没有意义;当URG = 1时,这2个字节才有意义,里面存储的是一个长度——比如8个字节,代表当前TCP数据段内前8个字节是紧急重要的数据,应该优先尽快传递。
- 选项:TCP头部的可选空间。如SACK(Selective Acknowledgment)选择性确认——SACK信息会放在TCP头部的选项里,里面有一个字段叫Kind,如果Kind设置值为5则代表使用Sack,此时Kind字段后面会有若干个字节携带哪些数据段丢失掉了的信息,以便发送方据此做选择性重传;如果Kind设置值不为5则代表不使用Sack,发送方就不做选择性重传,而是根据窗口做全部重传。又如MSS(Maximum Segment Size)每个数据段最大大小,对方切TCP数据段的时候要依据这个数值的,不能超过这个数值。
- 填充:当TCP头部长度基本确定下来后,如果不是4的倍数,这个空间负责填充成4的倍数,因为数据偏移那里要求了TCP头部长度必须是4的倍数。
总结:TCP头部有那么多的数据,其中有几个是需要着重关心的。
- 通常出现在发送方TCP头部里的(当然它肯定也会出现在接收方TCP头部里,只不过我们不需要太关心它的意义而已):
(1)序号Seq,代表当前我给你发送的是哪个字节开始的数据,接收方收到数据段后就可以根据这个数值做排序。
- 通常出现在接收方TCP头部里的(当然它肯定也会出现在发送方TCP头部里,只不过我们不需要太关心它的意义而已):
(1)确认号Ack,必须与确认标志位ACK = 1连用,代表我希望你下次给我发送哪个字节开始的数据,发送方收到数据段后就可以根据这个数值发送相应的数据段。
(2)窗口Win,用来告诉对方下次最多能给我传输多少个字节的数据,发送方收到数据段后就可以根据这个数值控制发送的数据量了。
- 同时出现在发送方、接收方TCP头部里的:
(1)建立连接标志位SYN,当SYN = 1时,代表一个希望跟你建立连接的请求。
(2)回复标志位ACK,当ACK = 1时,代表我收到了你的消息、给你个回复。
(3)断开连接标志位FIN,当FIN = 1时,代表一个希望跟你断开连接的请求。
3、TCP是可靠传输
可靠传输是什么?可靠传输是指传输过程中不丢包、数据有序。
为什么要做可靠传输?就是为了保证传输过程中不丢包、数据有序。
怎么做到可靠传输的?
3.1 最初的做法:一段一段传 + 一段一段回复 + 超时重传
假设A要给B发送300个字节的数据,应用层这300个字节的数据到了传输层后会被切成三段M1、M2、M3。(数据分段和数据重组是在OSI参考模型的传输层中完成的)
- 情况一:正常传输
A会把“M1”先发送给B,B收到“M1”后会给A发送一个“确认收到M1”的回复,A收到“确认收到M1”后就给B发送“M2”......如此循环,直到“M3”发送完毕。
- 情况二:发送数据丢失或者发送数据有误
A会把“M1”先发送给B,但是在传输过程中“M1”不小心丢了、根本就没传输到B,或者“M1”没丢、传输到B了、但是B经过差错检验发现这段数据有误、丢掉这段数据,那B根本就不会给A发送“确认收到M1”的回复,那A就迟迟无法收到B的回复、没法发下一段数据了......其实A这边会有一个定时器,过了超时时间如果还没有收到B的回复,A就会认为“M1”传输出错了、进而执行超时重传“M1”......如此循环,直到“M3”发送完毕。
- 情况三:确认数据丢失或者确认数据有误
A会把“M1”先发送给B,B收到“M1”后会给A发送一个“确认收到M1”的回复,但是在传输过程中“确认收到M1”不小心丢了、根本就没传输到A,或者“确认收到M1”没丢、传输到A了、但是A经过差错检验发现这段数据有误、丢掉这段数据,那A都会认为“M1”传输出错了、进而重传“M1”,B再次收到“M1”后会丢掉重复的“M1”并再次给A发送一个“确认收到M1”的回复.....如此循环,直到“M3”发送完毕。
- 情况四:确认数据迟到或者发送数据迟到
A会把“M1”先发送给B,B收到“M1”后会给A发送一个“确认收到M1(第一次)”的回复,但是在传输过程中这个回复可能选了个较远的路径或者网不好,那A就会迟迟无法收到B的回复了,过了超时时间如果还没有收到,A就会认为“M1”传输出错了、进而超时重传“M1”,B再次收到“M1”后会丢掉重复的“M1”并再次给A发送一个“确认收到M1(第二次)”的回复,A收到“确认收到M1(第二次)”后就给B发送“M2”,此时“M2”发出去了,A才收到“确认收到M1(第一次)”,那A就什么都不做、不会再发“M2”了......如此循环,直到“M3”发送完毕。
同理,A会把“M1”先发送给B,如果“M1”迟到了、过了超时时间才到达B,也会导致一连串反应,但是这一套处理可以避免这种情况出错。
- 疑问:若有一段数据重传了N次还是没有成功,会一直重传直至成功吗?
不会,重传了N次还是没有成功,会被判定为当前连接出了严重问题,一般都是断开连接、重新建立连接,比如有些操作系统重传了5次还是没有成功,就会发送reset数据段(RST)了。
综上:
一段一段传 + 一段一段回复 + 超时重传可以保证TCP传输的可靠性。我给你传一段你给我个回复,我再传下一段,如果你不给我回复我就给你超时重传、直到你给我回复,这肯定能保证不丢包、数据有序。
但是:
这种做法的传输效率很低,是个串行操作,下一段数据必须得等上一段数据完完全全发送结束后才能发送。
3.2 优化后的做法:多段一起传 + 多段一起回复 + 被动重传(是否使用SACK选择性确认)
假设A要给B发送1200个字节的数据,应用层这1200个字节的数据到了传输层后会被切成十二段M1 ~ M12,每段100个字节。
因为是多段一起传,如果你一下子传太多段,超过了接收方的缓存区大小,就会导致丢包,所以A与B三次握手建立连接时,B会通过窗口Win告诉A下次最多能给它传输多少个字节的数据,比如是400个字节。
那A会把“M1”(序号Seq = 1,代表我给你发送的是第1个字节开始开始的数据)、“M2”(序号Seq = 101,我给你发送的是第101个字节开始开始的数据)、“M3”(序号Seq = 201,我给你发送的是第201个字节开始开始的数据)、“M4”(序号Seq = 301,我给你发送的是第301个字节开始开始的数据)四个数据段一起发送给B(注意:一起发送不是指“M1”、“M2”、“M3”、“M4”合并成一个大段通过一次传输一起发送过去,它们还是四个段,还是四次传输,一起发送是指一次性发起、并行传输,哪一个数据段会先到达也不一定),B收到“M4”后会给A发送一个“确认收到M4”(回复标志位ACK = 1,代表给你个回复,同时也使确认号Ack有意义;确认号Ack = 401,代表B希望A下次给它发送第401个字节开始的数据。注意:B只要回复“确认收到M4”,就意味着它全部收到了“M1”、“M2 ”、“M3 ”、“M4 ”,如果丢失了其中的某一个,它就不会回复“确认收到M4”,可能是回复别的,下面会说到)。
A收到“确认收到M4”后就开始给B发送“M5”、“M6”、“M7”(序号Seq = 601,代表我给你发送的是第601个字节开始开始的数据)、“M8”,但是在传输过程中“M7”不小心丢了,那B收到“M5”、“M6”、“M8”后过了超时时间如果发现“ M7”还没到,它就不会给A发送“确认收到M8”,而是给A发送“确认收到M6”(回复标志位ACK = 1,代表给你个回复,同时也使确认号Ack有意义;确认号Ack = 601,代表B希望A下次给它发送第601个字节开始的数据,因为丢掉的M7刚好就是第601个字节开始的数据)。
A收到“确认收到M6”后就开始给B发送“M7”、“M8”、“M9”、“M10”,但是A发现B发给它的“确认收到M6”是选择性确认(也是三次握手是B告诉A的),于是A又会拿着“确认收到M6”的TCP头部的选项去做判断,发现之前已经成功发过“M8”了,于是就不传“M8”了,只给B发送“M7”、“M9”、“M10”(当然如果三次握手时B告诉A不使用SACK选择性确认,A就会重复发“M8”),那B收到“M7”、“M9”、“M10”就会按照序号Seq把“M7”放在它该在的位置上,保证有序,然后给A发送一个“确认收到M10”......如此循环,直到“M12”发送完毕。
综上:
多段一起传 + 多段一起回复 + 被动重传(是否使用SACK选择性确认)可以保证TCP传输的可靠性。它就是通过TCP头部的序号Seq来保证数据有序,通过被动重传来保证不丢包(当然超时重传是保留下来的)。
同时:
通过多段一起传(是个并行操作) + 多段一起回复 + SACK选择性确认来优化提高了传输效率。
3.3 流量控制
流量控制是什么?接收方动态地告诉发送方发送的数据量不要太大,以便接收方能来得及处理。
为什么要做流量控制?如果接收方的缓存区满了,发送方还在疯狂地发送数据,接收方就只能把放不下的数据段丢掉,这样大量的丢包会极大的浪费网络资源,所以要做流量控制。据此我们看出流量控制也是保证TCP不丢包的一个手段。
怎么做到流量控制的?接收方的传输层有个缓存区,它接收到的数据都会先放到缓存区,然后再传递给应用层。接收方会根据缓存区的状态,动态地调整回复TCP数据段里的Win窗口大小,来告诉发送方下次最多能给它传输多少个字节的数据,以此来控制发送方发送的数据量———发送方发送的数据大小不能超过接收方给出的窗口大小,当接收方给出的窗口大小为0时、说明接收方缓存区已经满了、发送方会停止发送数据、如果缓存有了空间、打算让发送发继续发送数据、则发送确认数据窗口大小大于0即可。
3.4 拥塞控制
我们看上图网络中的链路A、链路B、链路C,链路A的带宽为1000M,所以理论上来讲链路B和链路C每秒传输的数据量之和只要小于等于1000M,那链路A就可以不丢包地把数据从路由器3传输到路由器4,但是如果链路B和链路C每秒传输的数据量之和大于1000M,那链路A就会出现过载、进而丢包了,因为它传不了这么多的数据。
拥塞控制是什么?防止过多的数据注入到网络中。(拥塞控制是一个全局的概念,它针对的是整个网络,涉及到了所有计算机、路由器、交换机等,就像全局统筹整个城市的交通那样;而流量控制是一个点对点的概念,它针对的是两台计算机之间,就像国庆期间从长安街东驶入的车辆到长安街西的限流一样。一言以蔽之,拥塞控制控制的是整个网络,流量控制控制的是对方计算机发出的数据量)
为什么要做拥塞控制?避免链路过载、大量丢包。据此我们看出拥塞控制也是保证TCP不丢包的一个手段。
怎么做到拥塞控制的?慢开始 + 拥塞避免 + 快恢复。
-
慢开始:A与B三次握手建立连接时,B会通过窗口Win告诉A下次最多能给它传输多少个字节的数据,比如是3000个字节;B还会通过选项区每个数据段最大大小MSS告诉A每个数据段最大是多大,比如是100个字节;这样A就会下次把要发送的3000个字节的数据切成30个数据段,每段100个字节。接下来实际上A并不是把这30个数据段通过“下次”这一次全都传给B,而是第一次传2 ^ 0 = 1个数据段100个字节过去,如果收到了回复、则代表网络状况良好、那我就试试传更多的数据段过去,于是第二次传2 ^ 1 = 2个数据段200个字节过去,如果收到了回复、则代表网络状况良好、那我就试试传更多的数据段过去,于是第三次传2 ^ 2 = 4个数据段400个字节过去......如此循环,一次一次试探着传输、避免一次性往网络中投放大量的数据段、指数级增长,这就是慢开始。
-
拥塞避免:但是因为慢开始是指数级增长的,如果一直任由它指数级增长下去,很快就会出现某一次传输的数据量变得超级大,从而导致网络过早出现拥塞。所以慢开始是有一个阈值的,到了阈值后,就不会在指数级增长了,而是改作线性增长,每次多加一点、每次多加一点,防止网络过早出现拥塞(如果一直是慢开始的话,可能传六次就会导致网络拥塞了,但其实第五次和第六次传输之间可能还有很大的空间可以利用,而拥塞避免就充分利用了第五次和第六次传输之间的空间,可能传十三次才会导致网络拥塞,大大地将网络拥塞的可能性延后),这就是拥塞避免。
-
重新慢开始 + 拥塞避免:拥塞避免加到一定程度,一旦真得发生了网络拥塞——一个很直观的体现就是开始丢包,发送方就能通过“我都好几个回复没收到了”感知到可能是发生了网络拥塞,于是重新慢开始 + 拥塞避免,并且将慢开始的阈值重新设定为发生网络拥塞时传输数据量的一半。
- 快恢复:但是“重新慢开始 + 拥塞避免”的慢开始无疑会使数据的传输效率大大降低,所以新版本的TCP协议里引入了快恢复。拥塞避免加到一定程度,一旦真得发生了网络拥塞,会直接开始下一次拥塞避免,而下一次拥塞避免的起点就是发生网络拥塞时传输数据量的一半,这就是快恢复。
4、TCP是面向连接的
连接是什么?这里的连接肯定不是个物理连接(如网线、光纤等),甚至连个虚拟的连接都算不上(虚拟的连接至少让我们感觉到它是个类似于通道一样的东西),这里的连接其实就是指通信双方的一些初始化数据,如通信双方的Win窗口大小、通信双方的MSS每个数据段最大大小、通信双方是否支持SACK选择性确认、通信双方的随机初始序号、通信双方的随机初始确认号等,我们之所以把这些数据称之为“连接”,就是因为通信双方彼此知道对方的这些数据,这种“彼此知道对方数据”的状态——我知道你、你知道我,就好像是有某种“连接”把通信双方关联起来了一样,所以我们才把这些数据称之为“连接”了。(注意:源端口号、目标端口号、源IP、目标IP这四个数据不属于连接的范畴。我们之所以说UDP是无连接的,就是因为它只有这四个数据,而没有通信双方的一些初始化数据,无脑地给对方扔东西;TCP是面向连接的,就是因为它除了这四个数据,还有通信双方的一些初始化数据,有规定地给对方扔东西)所以建立连接就是指通信双方交换彼此的初始化数据,断开连接就是指通信双方清空彼此初始化数据。
为什么要建立连接?为什么要释放连接?因为保证可靠传输那三套机制必须得有一堆初始化数据才能搞,所以我们必须在真正传输数据前先建立连接交换彼此的数据,否则没办法做可靠传输。反过来,当真正的数据传输完了,就清空一下彼此的数据,一方面可以节省内存空间,二方面客户端的端口号是随机的,下次万一这个客户端又随机到这个的端口号(这就意味着源端口号、目标端口号、源IP、目标IP这四个数据可能跟上次全部一样),那大家要是基于上次的数据做数据传输就会出错了。
怎么建立连接的?三次握手建立连接。
假设客户端要向服务器发一个HTTP请求。
一开始客户端处于CLOSED关闭状态,服务器处于LISTEN监听状态。
当客户端要给服务器发送数据的时候,客户端就会发起第一次握手——即发一个“SYN = 1 + 客户端的初始化数据”的TCP数据段给服务器(SYN = 1,代表一个希望跟你建立连接的请求),发出后客户端立即进入SYN-SENT连接请求已发送状态,此时服务器处于LISTEN监听状态。
当服务器收到第一次握手后,就会发起第二次握手——即回复一个“ACK = 1、SYN = 1 + 服务器的初始化数据”的TCP数据段给客户端(ACK = 1,代表我收到了你的消息、给你个回复;SYN = 1,代表一个希望跟你建立连接的请求),发出后服务器立即进入SYN-RCVD连接请求已接收状态,此时客户端处于SYN-SENT同步已发送状态。
当客户端收到第二次握手后,就会发起第三次握手——即回复一个“ACK = 1”的TCP数据段给服务器(ACK = 1,代表我收到了你的消息、给你个回复,发出后客户端立即进入ESTABLISHED连接已建立状态,此时服务器处于SYN-RCVD连接请求已接收状态。
当服务器收到第三次握手后,立即进入ESTABLISHED连接已建立状态,此时客户端处于ESTABLISHED连接已建立状态,连接建立成功。
客户端就可以给服务端发送数据了。
疑问:为什么非要三次握手?既然建立连接就是指通信双方交换彼此的初始化数据,那两次握手就够了呀,客户端通过第一次握手把自己的初始化数据传递给服务器,服务器通过第二次握手把自己的初始化数据传递给客户端,通信双方就都有了彼此的初始化数据,就能够保证可靠传输了呀,那为什么还要第三次握手呢?通俗地回答就是“让服务器知道客户端已经收到了它的初始化数据”,那我们就来分析一下这个通俗的回答到底站不站得住脚,假设根本不存在第三次握手——也就是说服务器只是把初始化数据发出去了、但是根本不知道客户端到底收没收到它的初始化数据,正常情况下是客户端收到了服务器的初始化数据,那双方就是正常地传输数据;异常情况下是客户端没收到服务器的初始化数据——这不就等价于是客户端没收到服务器针对第一次握手的回复嘛,那过了超时时间客户端肯定会发起超时重传,直到收到服务器的初始化数据,如果重传N次还不行,那就断开连接,伺机再连接,所以说两次握手肯定就能保证客户端和服务器都收到对方的初始化数据,因为有超时重传机制存在啊。所以“服务器知不知道客户端收到了它的初始化数据”根本无所谓,它知道了也没有任何意义,它不知道也不会造成任何影响,所以这个通俗的回答有待商榷。那从专业知识的角度回答一下:假设没有第三次握手,客户端发起了第一次握手(第一次),但是在传输过程中这个数据段可能选了个较远的路径或者网不好,迟迟没到达服务器,服务器就没法给客户端回复了,过了超时时间还是没收到,那客户端就会认为第一次握手(第一次)出问题了,于是重新发起第一次握手(第二次),第二次握手顺利到达服务器,服务器也顺利发起了第二握手,接着双方传输数据完毕,四次挥手断开了连接,可就在连接都断开了,很可能客户端发起的第一次握手(第一次)才到达服务器,那此时服务器会认为是个有效的连接请求,于是给客户端发了一个第二次握手的回复,客户端收到第二次握手的回复后,发现我早就跟你传输完数据了,我不想建立连接了啊,于是就不搭理服务器,这样服务器就会白白分配资源监听客户端了,造成服务器资源的浪费。但是如果加上第三次握手——即服务器会在收到第三次握手后才会分配资源监听客户端,那当饶了很大一圈的第一次握手(第一次)到达服务器,服务器虽然依旧会认为是个有效的连接请求,于是给客户端发了一个第二次握手的回复,客户端收到第二次握手的回复后,发现我早就跟你传输完数据了,我不想建立连接了啊,于是就不搭理服务器,于是服务器就不会收到第三次握手,于是服务器就不会分配资源监听客户端了,从而避免了资源浪费。
怎么断开连接的?四次挥手断开连接。
TCP是全双工通信。
假设客户端发现没什么数据要发给服务器了,想要主动断开连接,那客户端就会发起第一次挥手——即给服务器发送一个“FIN = 1”的数据段(FIN = 1,代表一个希望跟你断开连接的请求),代表客户端告诉服务器我已经没有什么东西要发给你了,服务器收到第一次挥手后就会发起第二次挥手——即给客户端发送一个“ACK = 1”的数据段(ACK = 1,代表我收到了你的消息、给你个回复),代表服务器已经知道客户端没有什么东西要发给我了,但是此时还不能断开连接,因为这一个回合下来只代表客户端给服务器发数据这个方向的通道可以关闭了,服务器可还有可能想给客户端传数据呢,所以我们没看到第二次挥手的数据段里有“FIN = 1”。
等到了某个时机,服务器也发现没什么数据要发给客户端了,于是发起第三次挥手——即给客户端发送一个“FIN = 1”的数据段,代表服务器告诉客户端我已经没有什么东西要发给你了,客户端收到第三次挥手后就会发起第四次挥手——即给服务器发送一个“ACK = 1”的数据段,代表客户端已经知道服务器没有什么东西要发给我了,此时双向通道就可以都关闭掉了,于是断开连接。
网友评论