计算机网络-传输层的工作原理以及TCP协议:三次握手

本文深入解析了TCP/IP协议中的传输层,特别是TCP协议的工作机制。介绍了TCP的三次握手和四次挥手过程,以及套接字在连接、数据传输和断开过程中的作用。传输层主要负责端到端的数据传输,而套接字作为应用程序与协议栈交互的接口,包含了连接状态、IP和端口等信息。文章还讨论了传输效率与延迟之间的平衡策略,并强调了最后的确认步骤对于防止误操作的重要性。
摘要由CSDN通过智能技术生成

在这里插入图片描述

传输层的定义

TCP协议是工作在传输层的,应用层的应用程序准备好数据后委托给传输层处理

在这里插入图片描述
为什么我这里网络传输只有四层,OSI模型不是有7层吗,我是想简单点讲讲,实际上每一层都有一定的耦合,不一定要拆分的那么细,TCP/IP协议就是四层,这样对于初入门可以更好理解,七层只是在这四层划分的更细。

那传输层具体是什么样子的,传输层做了些什么工作呢?

传输层的功能是由操作系统提供负责实现的,传输层位于操作系统的协议栈。操作系统通过socket(即套接字)向应用层提供接口,应用层的程序仅需使用socket套接字的接口即可完成网络通讯(应用层只管调用socket的接口,就可以委托给传操作系统的协议栈处理了)。即应用程序调用socket(即套接字)库向把数据包发给操作系统的协议栈。

如下图,协议栈有两部分组成,即传输层和应用层两部分都在这里,协议栈的上半部分是传输层,这里还有两块,分别是负责用 TCP 协议收发数据的部分和负责用 UDP 协议收发数据的部分,它们会接受应用程序的委托执行收发数据的操作。下半部分是用 IP 协议控制网络包收发操作的网络层部分。

在这里插入图片描述
如图,上面的部分会向下面的部门委派工作,下面的部分接收委派工作并执行完后再委托给自己的下一层

上面我们已经了解了协议栈的内部结构了,应用层的程序通过Socket接口委派工作到协议栈的传输层。

协议栈里面还有一个重要概念: Socket,中文解释叫套接字

我下面统一叫套接字,但是socket也是指它。那么套接字到底是什么来的呢?套接字看起来挺抽象的,因为在操作系统控制的,我们看不到。套接字的概念表示的是包含了通讯对象的客户端IP、客户端端口,服务器IP、服务器端口、连接状态等通讯需要的控制信息的一个称呼。一个套接字实体,表示的就是一块保存有这些控制信息的内存空间。

感觉还描述的不是很明白,我再用编程的角度打个比喻:
套接字就像一个类(这个类包含字段信息有,客户端IP、客户端端口,服务器IP、服务器端口、连接状态等字段),套接字实例就像这个类的实例,new出来的实例是在内存里面的,所以套接字理解也是这样的。一个套接字实体就好比一个类的实例对象。

套接字的具体样子

我们在windows的cmd命令输入netstat -ano就可以看到下面的表,这个表的每一行就可以理解为套接字的一个实体。
在这里插入图片描述

其中本地 IP 地址和远程 IP 地址都是 0.0.0.0,这表示通信还没开始,IP 地址不确定,这个时候状态是LISTENING

传输层的工作原理

上面知道了套接字的定义了,那么看看怎么使用套接字在传输层进行连接和收发数据
客户端的套接字和服务器的套接字创建连接流程是不一样的,区分如下:

首先,客户端的数据收发需要经过下面 4 个阶段:
(1)创建套接字(创建套接字阶段)
(2)用管道连接服务器端的套接字(连接阶段)
(3)收发数据(收发阶段)
(4)断开管道并删除套接字(断开阶段)

相对地,服务器是将阶段(2)改成了等待连接,具体如下:
(1)创建套接字(创建套接字阶段)
(2-1)将套接字设置为等待连接状态(等待连接阶段)
(2-2)接受连接(接受连接阶段)
(3)收发数据(收发阶段)
(4)断开管道并删除套接字(断开阶段)

客户端的套接字具体工作流程

在这里插入图片描述

1. 创建套接字的阶段

      首先是创建套接字的阶段 。应用程序调用 socket 申请创建套接字,协议栈根据应用程序的申请执行创建套接字的操作。在这个过程中,协议栈首先会分配用于存放一个套接字所需的内存空间。用于记录套接字控制信息的内存空间并不是一开始就存在的,因此我们先要开辟出这样一块空间来 ,这相当于为控制信息准备一个容器。但光一个容器并没有什么用,还需要往里面存入控制信息。套接字刚刚创建时,数据收发操作还没有开始,因此需要在套接字的内存空间中写入表示这一初始状态的控制信息。到这里,创建套接字的操作就完成了。
      套接字创建完成后会返回一个描述符给应用程序,这个描述符可以理解为用来识别不同套接字的,套接字的唯一标识,应用程序可以根据这个描述符找到对应的套接字。

套接字描述符如果用java编程的角度理解,就是对象的引用,c语言的指针,用来指向某一块内存的

在这里插入图片描述

2. 连接阶段

      这个阶段就是要把客户端的套接字和服务器的套接字连接起来

在这里插入图片描述

套接字连接成功后,就好像有一条管道,将数据从一端送入管道,数据就会到达管道的另一端然后被取出。数据可以从任何一端被送入管道,数据的流动是双向的。不过,看上去有一条管道,实际上这条管道并不存在,只是表示逻辑上他们网络互通,可以相互访问。

具体的连接过程是从应用程序调用套接字的connect开始的,程序调用connect会提供服务器的IP地址和端口号,TCP将这些控制信息(客户端IP和端口、服务器IP和端口)作为TCP头部加在数据包上。
在这里插入图片描述
      现在处在连接阶段的数据块是还没有数据的,TCP的控制信息还会有一个SYN=1的信息,SYN=1表示连接。
      当TCP头部创建好后,接下来TCP模块会将信息传递给IP模块并委托它进行发送,即传输层处理完后委托到下一层网络层去处理。

其实这里还有一个问题,connect的时候只有服务器IP、服务器端口,以及我们通过本地路由表可以知道自己的客户端IP,但是客户端的端口还不知道的。这个时候TCP头部的客户端端口是什么呢?

  1. 客户端调用connect请求连接服务器时如果不指定端口的话主机会随机分配一个
  2. 主动连接的一方一般去用那些操作系统随机分配的端口号。bind也可以指定端口号为0,这种情况下就是随机绑定一个没有使用过的端口号,可以用来在建立连接之前就确定客户端的端口号。

接下在网络层的IP模块,这里会在原来加上了TCP头部的数据包,再加上IP头部,以及根据本地路由表找到找到转发的下一个路由IP,然后再根据ARP协议找到这个IP所在的设备的MAC地址,把MAC地址也加在IP头部外面。再下一个路由再不断重复这个过程,直到把数据包传输到服务器上去为止。具体的过程看这篇有详细描述《计算机网络-学习路由器》
在这里插入图片描述
      网络包就会通过网络到达服务器,然后服务器上的 IP 模块会将接收到的数据传递给 TCP 模块,服务器的 TCP 模块根据 TCP 头部中的信息找到端口号对应的套接字,也就是说,从处于等待连接状态的套接字中找到与 TCP 头部中记录的端口号相同的套接字就可以了。当找到对应的套接字之后,套接字中会写入相应的信息,并将状态改为正在连接 。
      上述操作完成后,服务器的 TCP 模块返回响应,这个过程和客户端一样,需要在 TCP 头部中设置发送方和接收方端口号以及 SYN 比特 =1。此外,在返回响应时还需要将 ACK 控制位设为1,这表示已经接收到相应的网络包。网络中经常会发生错误,网络包也会发生丢失,因此双方在通信时必须相互确认网络包是否已经送达 ,而设置ACK 比特就是用来进行这一确认的。接下来,服务器 TCP 模块会将 TCP头部传递给 IP 模块,并委托 IP 模块向客户端返回响应。

服务器接收到客户端的请求连接的数据包后,也会返回一个确认连接的数据包给客户端,这个时候TCP头部除了客户端IP和端口,服务器IP和端口,还多了ACK=1的控制信息。ACK=1表示服务器已经确认接收到客户端发出的数据包,还有这里还有一个SYN=1,这个表示连接成功,将这些信息加在TCP头部,最后也一样委托给网络层,最后返回这个数据包到客户端。

      然后,网络包就会返回到客户端,通过 IP 模块到达 TCP 模块,并通过 TCP 头部的信息确认连接服务器的操作是否成功。如果 SYN 为 1 则表示连接成功,这时会向套接字中写入服务器的 IP 地址、端口号等信息,同时还会将状态改为连接完毕。到这里,客户端的操作就已经完成,但其实还剩下最后一个步骤。刚才服务器返回响应时将 ACK 比特设置为 1,相应地,客户端也需要将 ACK 比特设置为 1 并发回服务器,告诉服务器刚才的响应包已经收到。当这个服务器收到这个返回包之后,连接操作才算全部完成。

上面的三步,客户端发送连接包,服务器回复ack确认包,以及客户端再回复ack确认包,这三步就是TCP三次握手的基本流程。

为什么还需要最后一步呢,看起来不需要最后一步客户端也能发送数据包给服务器,服务器也能返回数据包给客户端,已经可以通讯了。但是最后一步是客户端发送ack=1告诉服务器,服务器发送的数据包可以到达客户端。如果没有第三步,会有什么问题呢?问题就是服务器并不知道自己能连通客户端的网络,前两步可以确定客户端可以确认连通服务器网络(客户端发了一个连接包,服务器回了一个ack确认包,)以及客户端知道服务器可以连通客户端,但是服务器不知道自己能连通客户端,所以需要客户端再发一个ack包给服务器确认。如果第二步就算连接完成的话,那么客户端和服务器马上就可以相互发数据了,例如服务器有某些出口IP黑名单限制或者端口出去限制,发往某些客户端IP的包或端口会被拦截过滤掉,这个时候就会发送失败了,因为客户端可以连通服务器,但是服务器还不能连通客户端。所以,最后一步不可或缺。

3.接入第三阶段收发数据

      连接成功之后客户端和服务器就可以相互发数据了协议栈并不关心应用程序传来的数据是什么内容。应用程序在调用 write 时会指定发送数据的长度,在协议栈看来,要发送的数据就是一定长度的二进制字节序列而已。其次,协议栈并不是一收到数据就马上发送出去,而是会将数据存放在内部的发送缓冲区中,并等待应用程序的下一段数据。

      应用程序交给协议栈发送的数据长度是由应用程序本身来决定的,不同的应用程序在实现上有所不同,有些程序会一次性传递所有的数据,有些程序则会逐字节或者逐行传递数据。总之,一次将多少数据交给协议栈是由应用程序自行决定的,协议栈并不能控制这一行为。在这样的情况下,如果一收到数据就马上发送出去,就可能会发送大量的小包,导致网络效率下降,因此需要在数据积累到一定量时再发送出去。至于要积累多少数据才能发送,不同种类和版本的操作系统会有所不同,不能一概而论,但都是根据下面几个要素来判断的。

      第一个判断要素是每个网络包能容纳的数据长度,协议栈会根据一个叫作 MTUA 的参数来进行判断。MTU 表示一个网络包的最大长度,在以太网中一般是 1500 字节(图 2.5)B。MTU 是包含头部的总长度,因此需要从MTU 减去头部的长度,然后得到的长度就是一个网络包中所能容纳的最大数据长度,这一长度叫作 MSSC。当从应用程序收到的数据长度超过或者接近 MSS 时再发送出去,就可以避免发送大量小包的问题了

MTU:一个网络包的最大长度,以太网中一般为 1500 字节。
MSS:除去头部之后,一个网络包能容纳的数据的最大长度。一般为1460字节, TCP 和 IP 的头部加起来一般是 40 字节,因此 MTU 减去这个长度就是 MSS。例如,在以太网中,MTU 为 1500,因此 MSS 就 是 1460。

      另一个判断要素是时间。当应用程序发送数据的频率不高的时候,如果每次都等到长度接近 MSS 时再发送,可能会因为等待时间太长而造成发送延迟,这种情况下,即便缓冲区中的数据长度没有达到 MSS,也应该果断发送出去。为此,协议栈的内部有一个计时器,当经过一定时间之后,就会把网络包发送出去。
      判断要素就是这两个,但它们其实是互相矛盾的。如果长度优先,那么网络的效率会提高,但可能会因为等待填满缓冲区而产生延迟;相反地,如果时间优先,那么延迟时间会变少,但又会降低网络的效率。因此,在进行发送操作时需要综合考虑这两个要素以达到平衡。不过,TCP 协议规格中并没有告诉我们怎样才能平衡,因此实际如何判断是由协议栈的开发者来决定的,也正是由于这个原因,不同种类和版本的操作系统在相关操作上也就存在差异。

在这里插入图片描述

这个阶段还有一个重要问题。TCP是如何确认对方收到网络包的,以及数据拆分成多个包发送到达另一边后,另一边的计算机是如何还原这些数据的,因为数据包到达的顺序不一定一样,可能后面的比前面的到达更快。(这一部分还有挺多逻辑,后面再写一篇补充,暂时不描述了)

4.断开管道并删除套接字

协议栈允许任何一方先发起断开过程。

即无论客户端还是服务器都可以主动断开

断开的操作顺序如下:
(1)客户端发送 FIN
(2)服务器返回 ACK 号
(3)服务器发送 FIN
(4)客户端返回 ACK 号

具体流程:
      在 TCP 协议的规则中,断开操作可以由客户端或服务器任何一方发起,具体的顺序是由应用层协议决定的。Web 中,这一顺序随 HTTP 协议版本不同而不同,在 HTTP1.0 中,是服务器先发起断开操作。
      这时,服务器程序会调用 Socket 库的 close,TCP 模块会生成一个控制位 FIN 为 1 的 TCP 头部,并委托 IP 模块发送给客户端。当客户端收到这个包之后,会返回一个 ACK 号。接下来客户端调用 close,生成一个FIN 为 1 的 TCP 头部发给服务器,服务器再返回 ACK 号,这时断开操作就完成了。HTTP1.1 中,是客户端先发起断开操作,这种情况下只要将客户端和服务器的操作颠倒一下就可以了。

这四步就是四次挥手的基本过程

在这里插入图片描述
      无论哪种情况,当断开操作完成后,套接字会在经过一段时间后被删除。

套接字并不会立即被删除,而是会等待一段时间之后再被删除?等待这段时间是为了防止误操作,引发误操作的原因有很多。例如最后一步(4)客户端返回 ACK 号这一步丢失了。,结果会如何呢?这时,服务器没有接收到 ACK 号,可能会重发一次 FIN。如果这时客户端的套接字已经删除了,会发生什么事呢?套接字被删除,那么套接字中保存的控制信息也就跟着消失了,套接字对应的端口号就会被释放出来。这时,如果别的应用程序要创建套接字,新套接字碰巧又被分配了同一个端口号 B,而服务器重发的 FIN 正好到达,会怎么样呢?本来这个 FIN 是要发给刚刚删除的那个套接字的,但新套接字具有相同的端口号,于是这个 FIN 就会错误地跑到新套接字里面,新套接字就开始执行断开操作了。之所以不马上删除套接字,就是为了防止这样的误操作。

TCP的三次握手

在这里插入图片描述

为了进行可靠的传输,无非是要保证客户端与服务器之间的数据发送和接收的正常进行。

  • 第一次握手:Client 什么都不能确认;Server 确认了Client发送正常。
  • 第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,Client发送正常。
  • 第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送接收正常。

为什么需要进行第三次握手了?一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。

通过上面的三个步骤,Client和Server能够进行可靠的传输,缺一不可

感觉很多地方我没描述清楚,这篇大多参考《网络是怎么连接的》,感兴趣的同学建议直接读这本书,我这里做一些自己的笔记。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值