TCP/IP数据传输
协议栈可以简单理解成是一种网络控制软件,它是要配合一系列Socket组件才能完成网络的连接以及收发操作。
“套接字”是协议栈负责收发数据的关键角色,往抽象点的方面来解释的话就是连接管道的出入口,如果要赋予实体的话那它就是一个存放“控制信息”的临时空间,例如:通信对象的“ip地址”,“端口号”,“通信操作状态”等等,协议栈是根据套接字信息来进行工作的。
通过以下命令可以查看当前系统连接的管道(也就是套接字的情况)
那么套接字又是怎么生成的呢?
-
套接字的一生都是调用Socket库一系列的的组件来完成的,如图
创建套接字阶段:
协议栈先分配一个套接字所需的内存空间,然后向其中写入初始状态。
将表示这个套接字的描述符告知应用程序。描述符相当于用来区分协议栈中的多个套接字的号码牌。为什么需要区分?因为一个应用程序可以连接多个web服务器,所以需要知道具体是哪个套接字进行数据收发操作的。
连接阶段(调用Connect组件):
连接实际上是通信双方交换控制信息的过程,具体交换什么信息是由通信规则来确定的。
控制信息分为两类:
- 客户端和服务器相互联络时交换的控制信息(ip地址,端口号,等等)这些规格在tcp协议的控制信息进行了定义,每次客户端和服务器之间进行通信时,都需要提供这些控制信息
在连接阶段,由于数据收发还没有开始,网络包没有实际的数据,只有控制信息,这些信息位于开头,因此也称为头部。此外,以太网和IP协议也有自己的控制信息,这些信息也叫头部,为了避免各种不同的头部发生混淆,我们一般会记作TCP头部、以太网头部 、IP头部。
- 另外一类,那就是保存在套接字中,用来控制协议栈操作的信息,应用程序传递来的信息以及从通信对象接收到的信息都会保存在这里,还有收发数据操作的执行状态等信息也会保存在这里,协议栈会根据这些信息来执行每一步的操作。协议栈中的控制信息通信对方是看不见的,只要在通信时按照规则将必要的信息写入头部,客户端和服务器之间的通信就能够得以成立。例如,Windows和Linux操作系统的内部结构不同,协议栈的实现方式不同,必要的控制信息也就不同。但即便如此,两种系统之间依然能够互相通信,同样地,计算机和手机之间也能够互相通信。
通过TCP头部中的发送方和接收方端口号可以找到要连接的套接字(当TCP头部创建好之后,接下来TCP模块会将信息传递给IP模块并委托它进行发送),从而建立连接。然后,我们将头部中的控制位的SYN比特设置为1(如果由于某些原因不接受连接,那么将不设置SYN,而是将RST比特设置为1。),可以认为它表示连接 。此外还需要设置适当的序号和窗口大小。服务器需要知道“谁连接了我”,客户端也要知道“我连接了谁”也就是说这个过程双方都要同步的。、
收发数据
当套接字之间完成连接形成“管道”时那么就可以自由的收发数据了,数据的收发是调用write组件将要发送的数据交给协议栈开始的。
- 协议栈不关心这个数据的是什么样的,在它眼里就是一个有一定长度的二进制字节序列。
- 协议栈并不是一收到数据就马上发送出去的,而是会将数据存放在内部的发送缓冲区中
- 为什么需要缓冲区?应用程序交给协议栈发送的数据长度是由应用程序本身来决定的,有些应用程序是分段发送的(分段发送是为了减少重发网络包的成本),这种情况下如果如果一收到数据就马上发送出去,就可能会发送大量的小包,导致网络效率下降,因此需要在数据积累到一定量时再发送出去。至于要积累多少数据才能发送?由两个要素决定
- 由网络包的长度来限制的
- 如果每次都要等到mss要满了才发送,这样的网络效率太低了,协议栈内部有个计时器,在经过一段时间也会自动发送,两者之间其实是相互矛盾的,具体怎么设置是由协议栈开发者自己决定。
- 由网络包的长度来限制的
- 为什么需要缓冲区?应用程序交给协议栈发送的数据长度是由应用程序本身来决定的,有些应用程序是分段发送的(分段发送是为了减少重发网络包的成本),这种情况下如果如果一收到数据就马上发送出去,就可能会发送大量的小包,导致网络效率下降,因此需要在数据积累到一定量时再发送出去。至于要积累多少数据才能发送?由两个要素决定
如果长度超过一个网络包所能容纳的数据量,比如在博客或者论坛上发表一篇长文这种情况。发送缓冲区中的数据就会超过MSS的长度,不会继续等待之后的数据,而是将数据以MSS的长度进行分割成各个单独的网络包上,当判断需要发送这些数据时,就在每一块数据前面加上TCP头部,并根据套接字中记录的控制信息标记发送方和接收方的端口号,然后交给IP模块来执行发送数据的操作
怎么确定服务器收到数据呢?
首先tcp模块在拆分数据时,会先算好整个网络包在整体数据的字节序列,接下来在发送的时候将算好的字节数写在TCP头部中,对方收到以后会告知发送方收到了多少字节的数据(ACK号)。通过ACK号也可以判断数据是否完整,比如第一个包ack号是1460,第二个包的ack号是1470,那么中间就少了10个字节的数据了。
整体流程
在得到对方确认之前,发送过的包都会保存在发送缓冲区中。如果对方没有返回某些包对应的ACK号,那么就重新发送这些包。
那么每次如果都等待ack号的返回才确认是否收到数据的话,等待时间是不是浪费了?
- tcp并没有浪费等待的时间,它是滑动窗口的方式来管理数据发送和ack号的操作。所谓滑动窗口,就是在发送一个包之后,不等待ACK号返回,而是直接发送后续的一系列包。这样一来,等待ACK号的这段时间就被有效利用起来了。
- 这种不等待ack号的确认就连续发送包会不会出现处理不过来的问题呢?
- 如果不做任何限制,会有处理不过来的情况。接收方需要通过TCP头部中的窗口字段将自己能接收的数据量告知发送方才行
ACK与窗口的基本是合并发送出去的,不会立马发送而是会等待一段时间跟其它通知一起打包发送,在这包中可能有连续ack号和连续的窗口,只会发送最终的结果,中间全部省略。这样做的目的是为了减少网络包的数量。
应用程序如果读取缓冲区的为空的话会先将当前应用程序挂起。
从服务器断开并删除套接字
http1.0中服务器一方会先调用Socket的close程序,然后,服务器的协议栈会生成断开信息的TCP头部,具体来说就是将控制位中FIN比特设为1,接下来会委托ip模块进行发送,同时服务器的套接字会记录断开操作的信息。客户端同理也是会做这些操作。
http1.1由客户端务器返回响应消息之后,客户端还可以继续发起下一个请求消息,如果接下来没有请求要发送了,客户端一方会发起断开过程。这是为了保持持久连接。
删除套接字并不是立马删除,而是会等待一段时间,因为考虑到可能会出现的误操作,比如客户端发送的ACK号丢失了,服务端没有收到会重新发送FIN,但是此时客户端已经删除了套接字。如果别的应用程序创建新套接字刚好是同一个端口,那么新建的连接就关闭了!一般来说都是等待几分钟才删除的。
TCP/IP协议栈的整体流程以及总结
总结:服务端应用启动时会自动创建套接字等待连接,客户端应用一般是用户触发特定操作,需要访问服务器的时候创建套接字。创建套接字后,客户端会向服务端发起连接,客户端会生成一个SYN为1的TCP包并发送给服务器,这个TCP包还包含了初始序列号,到达服务端后,服务端也会返回一个SYN为1的TCP包并且包含了窗口的大小以及ACK号,返回后客户端会发送一个包含表示确认的ACK号的TCP包,其实也就是三次握手来互相确认双方的能力并且设置一些为收发数据做准备的操作。收发数据会根据应用程序不同而有一些差异,web程序TCP会将请求消息切分成一定大小的块,并在每一块前面加上TCP头部,然后发送给服务器,这样做的目的是防止数据太大导致网络效率的下降,TCP头部中包含序号,它表示当前发送的是第几个字节的数据,当服务器收到数据时,会向客户端返回ACK号作为确认。随着数据收发的进行,数据不断传递给应用程序,接收缓冲区就会被逐步释放。这时,服务器会将新的窗口大小告知客户端进行确认收发双方的最新的能力,最后数据不再发送就会进行断开并且删除连接用的套接字。
IP与以太网的包收发操作
TCP模块在执行连接、收发、断开等各阶段操作时,都需要委托IP模块将数据封装成网络包发送给通信对象
那么网络包是什么?
- 由头部和数据两部分构成的,头部包含目的地址等控制信息,可以理解为快递包裹的面单,头部后面就是委托方要发送给对方的数据,也就相当于快递包裹里的货物。
发送方到达接收方的网络传输过程中并不是一下子就到的,而是要经过各个路由器节点(可以看做路由器),最后才抵达目的地。在这之中有几个设备在网络传输担任重要的角色,分别是:
- 路由器:根据目标地址(是按照IP规则)判断下一个路由器的位置(指路)
- 集线器:在子网中将网络包(按照以太网规则)传输到下一个路由(运输)
为了判断包接下来应该向什么地方传输,集线器里有一张表(用于以太网协议的表),可根据以太网头部中记录的目的地信息查出相应的传输方向。以太网的部分也可以替换成其他的东西,例如无线局域网、ADSL、FTTH等,它们都可以替代以太网的角色帮助IP协议来传输网络包!!!
从上面图可以看到真正做到网络收发操作的其实是由集线器、路由器等网络设备来完成的,那么协议栈的IP模块只是个简单的入口吗?
- 其实并不是的。收到TCP委托后,IP模块会将包的内容当作一整块数据,会添加IP头部和MAC头部这两种头部,也就是在原本的基础上进行加工。加工后的网络包会交给网卡,网卡会将这些数字信息转换成电信号或者光信号,通过网线(光纤)发送出去,然后这些信号就会到达集线器、路由器等转发设备,再由转发设备一步一步地送达接收方。包送达对方之后,对方会作出响应。返回的包也会通过转发设备发送回来,然后我们需要接收这个包。接收的过程和发送的过程是相反的。
IP头部包含的内容:
IP地址是由TCP模块告知的,而TCP又是执行连接操作时从应用程序那里获取的,IP模块是不会自行判断包的目的地,即使是错误的,IP模块也只能照做。
IP地址实际上并不是分配给计算机的,而是分配给网卡的,所以获取发送方的IP地址需要判断发送所使用的网卡。
如何判断呢?查看路由表,比如你的目标地址是192.168.1.21那么就对应图的第6行,因为它和192.168.1的部分相匹配,这样就可以找到对应的网卡了
IP模块根据路由表Gateway栏的内容判断应该把包发送给谁
生成了IP头部之后,接下来就MAC头部,在以太网的世界中,以太网在判断网络包目的地时和TCP/IP的方式不同,因此必须采用相匹配的方式才能在以太网中将包发往目的地,而MAC头部就是干这个用的。
发送方的MAC地址还好说,通过路由表确定哪个网卡后就可以获取到了,那么接收方要怎么获取呢?
使用ARP一种叫作广播的方法,可以把包发给连接在同一以太网中的所有设备,“×× 这个IP地址是谁的?请把你的MAC地址告诉我。”
通过arp -a 查看ip地址对应的mac地址,arp会进行缓存,一般过几分钟就删除了。
IP地址和MAC地址的区别
- IP地址是软件层面的由网络号和主机号构成,它可以随时修改。可以理解成不同子网下两个设备连接的桥梁
- MAC是硬件层面的,在世界是唯一的(00-16-EA-AE-3C-40) 前 24 位(00-16-EA)代表网络硬件制造商的编号,后 24 位(AE-3C-40)是该厂家自己分配的,一般表示系列号。
有了IP地址为什么还要MAC地址?
- 局域网是为任意网络层协议设计的,而不仅仅服务IP协议!
- 如果网卡存储网络层的地址,那么网络层地址就必须存储在网卡的RAM中,每次主机所在网络发生变化就要发生一次更改的操作,在通信上是得不偿失的。
- 如果只有MAC地址,那么路由器在寻址的时候就需要记下每个48位MAC地址所在的子网,这会占用极大的内存,显然也是不现实的。
UDP协议
TCP做了很多复杂的工作来确保数据能高效且可靠地发送给对方,UDP协议不像TCP那样复杂,它只要接收到对方的回复就算确认了,这种设计有个缺点如果包相对比较大的情况下,在中途传送失败的话那么重发的成本会比TCP高很多。UDP比较适合数据短并且要求响应快的数据传输,比如视频传输,音频(音频和视频数据中缺少了某些包并不会产生严重的问题,只是会产生一些失真或者卡顿而已,一般都是可以接受的)等等....如果像TCP一样通过接收确认响应来检查错误,需要一定时间的,很容易错过最佳播放时间。
网卡的工作
网卡会给网络包加上报头、起始帧分界符和FCS来测量信号,之后网卡的MAC模块生成通用信号,然后由PHY(MAU)模块转换成可在网线中传输的格式,并通过网线发送出去。
发送信号的操作分为两种,一种是使用集线器的半双工模式,另一种是使用交换机的全双工模式(发送和接收同时并行的方式叫作“全双工”,相对地,某一时刻只能进行发送或接收其中一种操作的叫作“半双工”),半双工可能会产生信号碰撞(区分不出两个应用同时发送的信号),这种情况下,发送操作会被终止。每个应用的信号会进行错开时间的阻塞发送。网卡接收包会根据报头、起始帧分界符和FCS来测量信号,没问题之后匹配MAC地址,如果是自己的就接收,不是就丢弃。接收之后放入缓冲区,并且向扩展总线中的中断信号线发送信号,叫CPU过来中断网卡的进程。
总结
涉及到的三张表分别是
-
交换机中有 MAC 地址表用于映射 MAC 地址和它的端口
-
路由器中有路由表用于映射 IP 地址(段)和它的端口
-
电脑和路由器中都有 arp 缓存表用于缓存 IP 和 MAC 地址的映射关系
这里简述下自己理解的TCP/IP传输流程吧,假设主机A要跟主机B通信,分为两种情况:
- 如果在同一子网上,只要通过ARP表获取对方的MAC地址即可通信,这里是不用到路由转发的,只需要用到“交换机/集线器”。
- A通过子网掩码计算出自己与B并不在同一个子网上,那么就会获取主机设置的默认网关的MAC地址,这时网络包有发送方的MAC地址,网关的MAC地址,以及发送双方的IP地址(注意:ip地址是不会变化的,MAC地址一直在变化)交换机通过MAC地址匹配到最近的路由器,路由器根据网络包的B的IP地址匹配其路由表,如果需要转发就会前往下个路由器,以此类推,直到匹配到有B的IP地址的路由器为止。到达对方路由之后会根据其arp 缓存表查询交换机的MAC地址,最后根据交换机缓存的MAC表把包发送到主机B上。主机B会检查网络包所携带的MAC地址,如果是自己的就接收,不是就丢弃。
IP(路由器)负责将包送达通信对象这一整体过程,而其中将包传输到下一个路由器的过程则是由以太网(交换机)来负责的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报