前言
本篇计划从什么是网络协议开篇,提出几个核心问题,聊一聊网络通讯的基础;例如,网络分层带来的好处?HTTP报文传输的原理?TCP/IP协议报文的格式?TCP/IP的通讯建立机制?常见关于TCP/IP的问题?
温馨提示:本文面向没有基础,或者基础分散零散的朋友,里面的很多协议格式、数据包转发过程,数据传输过程相对抽象;因本人时间有限,希望一篇讲清楚基本原理,常见问题;篇幅过长,请保持耐心;
总字数:14760,预计阅读时长:90~100分钟
1
什么是网络协议?
图1
在生活中,使用互联网目前已经司空见惯了,经常使用电脑、平板、手机去追剧、购物、聊天、写文章、办公等等,在使用过程中,会发生各式各样的网络通信;而网络设备要相互通信,双方就必须基于相同的方法,比如,如何在网络的海洋里探测到通讯目标,由那一边先发起通信、使用哪种语言进行通信、怎样结束通信等规则都需要事先约定好,而且不同的硬件、操作系统之间的通信,所有这一切都需要某个规则,这个规则被称之为:协议(protocol)
以最常见的家庭网络为例,办了宽带之后(不管是电信、移动、联通),接上无线路由器简单初始化之后,对于咱们普通用户而言就可以用了,不用关心是咋实现上网的,上网时候到底发生了啥。但是对于做开发的我们,这可是干活吃饭的“工具”,必须要了解。使用手机链接WIFI到无线路由器,用的是802.11(wlan协议),电脑网口插网线用的是以太网(Ether2协议),接入网络过程中,无线路由器会给电脑、手机下发网络配置信息用的是DHCP协议,然后可以接入局域网了。所有这些协议的目的都是:为了让通讯设备各方之间能够通过标准与规范来进行沟通交流,从而制定出来的一套标准。
在本文话题展开之前,先了解一下七层ISO模型。国际标准化组织ISO为使网络应用更为普及,推出OSI模型,即Open System Interconnect,开放式系统互联模型。
一般都叫作OSI参考模型。OSI参考模型于1985年发布,其目标就是为了让所有公司和个人使用统一标准规范来控制网络,实现互联互通。
图2
OSI参考模型把网络协议提供的服务分为了七层,并且定义每一层对应的服务内容(功能和协议),同时又约定与相邻层之间的接口通信。下表列举了OSI模型各层的通信协议概览:
应用层HTTP、SMTP、SNMP、FTP、Telnet、SIP、SSH、MQTT等等表示层NCP、XDR、SMB、AFP等等会话层ASAP、SSH、RPC、Sockets、Winsock、BSD等等传输层
TCP、UDP、TLS、RTP、ATP等等
网络层IP、ICMP、ARP、IGMP等等数据链路层以太网、令牌环、帧中继、HDLC、820.11等等物理层铜线、网线、光缆、无线电、光电等等
下面解释各层的作用和用途:
应用层
直接提供给用户用的服务,并又规定应用程序中通信相关的细节,譬如:HTTP、HTTPS、邮件、FTP、SMTP、远程登陆等
表示层
负责数据格式数据格式的互相组转换,转换成大家看得懂的内容,比如图片、文字、音频、视频这些内容,但计算机又不懂这些玩意,那么咋做呢。一般是转换成机器能看懂的“语言”,但用户也看不懂这些“语言”,所以这个表示层就有它存在的意义:数据格式转换
会话层
管理和协调不同主机上各个进程间通信,负责建立、维护、终止、销毁。在Windows上,CMD里敲下一行命令netstat-anob,立马就可以看到系统上各种进程的会话信息,有超时的,刚建立的等等
传输层
为上面几个层提供主机间端到端的可靠(TCP)或不可靠(UDP)访问,并通过端口号 点至来区分不同的应用进程;
网络层
数据链路层
在通信传输过程中,本质都是通过物理介质进行传输实现的,譬如光纤、WIFI、网线等,数据链路层的作用就是把物理层的比特流划分成数据帧的形式进行传输,并且在不同的链路层有不同的寻址方式(以太网、PPP)
物理层
最底层的东西,负责逻辑信号(比特流)与物理信号(电信号、光信号)之间的互相转换,并通过物理传输介质为数据链路层提供物理连接
TCP/IP协议族
上面提到的这几个玩意,并没有被在实际生产生活中被广泛使用;在OSI模型提出前,各个牛逼大厂(IBM、Apple等)本身在数据通信网络占据着主导地位,各自都有成熟的网络架构和标准,后来ISO为了解决彼此厂商间的兼容性问题才得以问世,但是在问世前,TCP/IP就已经在当时的大学学术圈搞出来了,后来经过不断测试和完善,各个厂商的协议特性也都能兼容,因而得到厂商的推广,后面由IETF组织管理并不断演进,得到广泛运行;这是网络协议的发展路线;回归模型本身,由于OSI模型太事无巨细了,徒增成本和维护周期,属于理论产物,而且也未经过实际验证和实验,存在诸多不稳定因素;在厂商间难以推广。而TCP/IP则不同,经过实践考验,又简单又容易理解,符合各个厂商的需求,后面才得以推广至今沿用。
TCP/IP协议是目前互联网最基本的协议,由于其简化了七层模型(合并会话层和表示层到应用层中,同时又将物理层合并到数据链路层中),最终是四层;TCP/IP协议与七层OSI模型的对应关系如图2中所表示的那样。
TCP/IP通信协议概览:
应用层HTTP、TeInet、FTP、SMTP等用于读取传输层的数据或将数据传输写入传输层传输层UDP、TCP实现端到端的数据传输网络层ICMP、IP、IGMP主要负责网络中数据包的传输等链路层(网络接口层)ARP、RARP操作系统中设备驱动程序,网络接口卡,处理与传输媒介的物理接口工作
TCP/IP协议族各层的作用
应用层
应用层包含与各式应用程序协同的工作,并利用基础网络交换应用程序的业务数据协议。某些系统支持的特定应用运行在此层,并且可直接支持用户的应用。其中包含的协议:HTTP(万维网服务),FTP(文件传输)、SMTP(电子邮件)、SSH(安全远程登陆)、DNS(域名解析)以及其他协议。
传输层
这一层解决譬如端到端可靠性问题,确保数据可靠抵达目标,甚至保证数据以特定顺序抵达。其功能大致如下:
提供端到端的传输服务
提供不同传输方式:可靠(TCP)与不可靠(UDP)
提供流量控制、差错控制、QoS服务质量等管理服务
这里简单解释下TCP和UDP的差别:
TCP:面向连接、可靠的传输协议,提供可靠的字节流,保证数据完整、无损并且按指定顺序抵达。TCP尽最大可能连续不断测试网络负载并且控制发送数据的速度以避免网络过载。另,TCP尝试以规定顺序发送
UDP:无连接的数据协议,以“尽力传输”和“不可靠”著称,不会对数据包是否抵达进行检查,也不管数据包的传输顺序
网络层
链路层
有时候也叫数据链路层或网络接口层,用于处理连接网络的硬件部分。包括操作系统的设备驱动,NIC(网卡)、光纤等物理可见部分。还包括其他连接器,例如光模块的其他传输介质。在这层,数据是以比特形式传递
2
网络分层有哪些好处?
在第二节,我们通过从0构建如上图的公司网络拓扑,以解释TCP/IP分层的产生和数据传输过程:
通信起始
假设你刚买了个电脑,还没联网,啥都还没配置,在互联网中处于“黑洞”状态
接入网络
你这会儿想用电脑给公司二狗发个消息,随便找了根网线连了光猫,接入了局域网网
发消息
连上后打开你写的内部沟通小工具,发了条消息给二狗那边的小工具
新人加入
现在有三德子也要了你的小工具并加进群聊,你三个就某四字姓明星的事儿讨论了起来;
多人加入
后面又有好几个公司里的吊毛们,想加入进来。这个时候这些“好奇”的同事就这个话题讨论了起来,由于加入的兄弟过多,网口不够用啊。
物理层(调整连接方式)
现在你发了个如下消息格式给你的二狗小宝贝儿
广播消息
据此,你解决了网络连接中的身份认证问题;你发了上面的消息格式准备给二狗,但是你的电脑是通过“集线器”广播给了所有人的,这样虽说能发给二狗,但是消息很容易给攻破,而且又浪费资源,给其他人也造成干扰了,所以你又在想能不能把“集线器”换成别的呢?
链路层(交换机)
新需求
10几号人,你想了想;算球,多加个交换机;搞一搞还能用;此时你老板从你们部门哪里走过,发现了你开发的小工具,寻思公司内部200多号人全用起来,并提了个要求:不准用交换机串联的形式;你在想,这咋整,交换机不给买,那不完犊子了嘛,你挠了挠头,突然灵光一闪,加个路由器也行啊。
新挑战
C:00-16-EA-AD-3C-9D
D:00-16-EA-OF-5C-8C
https://blog.csdn.net/qq_45947664/article/details/121111511
https://baike.baidu.com/item/MAC地址/1254181?fr=aladdin
网络层(路由器)
新消息格式
子网
解释子网对于一般由路由器和主机组成的互连系统,我们可以使用下列方法定义系统中的子网。
为了确定网络区域,分开主机和路由器的每个接口,从而产生了若干个分离的网络岛,接口端连接了这些独立网络的端点。这些独立的网络岛叫做子网(subnet)。
路由表
那么,你所在的子网1的数据转发问题解决了;但是,从路由器到C咋整?路由器又咋知道收到这个从A到C的数据包,从自己那个端口出去,才能直接或间接地到达C。关键的点就是通过一个叫“路由表”的东西
到此为止,我们已经描述了一个完整网络拓扑;并解释了发送过程中涉及的内容。我们从几个不同视角回顾一下,网络上两节点如何发送数据包的整个数据传输过程:
单个设备视角
依据子网掩码判断目标IP和源IP是否在同一子网下
交换机视角
通过MAC查询端口映射关系
查询的到,则通过指定端口发送
查不到,则所有端口都发送
路由器视角
依据路由表查询IP,子网,端口的映射关系
查询的到,则通过指定端口发送(独立于所有子网,通过子网的默认网关找到对应的路由器)
查不到,则返回路由不可达的数据包,给源IP
网络层(IP协议)本身是不具备传输包的功能,包的实际传输是委托给数据链路层(以太网中的交换机)来实现的
TCP/IP层次化的好处:
依据设计好的各层级间的接口,每个层次内设计可自由改动
部分层级被替换不影响整体功能
3
Http报文传输
原理
利用TCP/IP进行网络通信时,数据包会按照分层顺序与对方通信。发送端从应用层向下走,接收端从链路层往上走。从客户端到服务器的数据,每一帧数据的传输顺序如下图:
数据封装和分用
数据通过互联网传输的时候,会带有一定的特定标识;否则,在如海洋一般的互联网中,数据会杂乱无序。加特定标识符的过程称之为‘数据封装’;同样的,在数据使用过程中,拆除特定标识过程称之为分用。大致过程如下:
在数据封装时,经过每层都会打上对应层的特定标识,并添加头部。
在传输层时,添加报文首部要存入应用程序的标识符,,无论TCP还是UDP都会用一个16位端口号表示不同的应用程序,并将源端口和目标端口一并存在报文首部
在网络层封装时,IP首部会标识处理数据的协议类型,或者说标识出网络层数据帧所携带的上层数据,如TCP、UDP、ICMP、IP、IGMP等,会在IP首部存入协议域(一个8位数值来标识对应协议版本):
在链路层封装时,网络接口分别要发送和接收IP、ARP和RARP等多种不同协议的报文,因而也必须在以太网的帧首部加入某种形式标识,以明确所处理的协议类型;对此,以太网的报文帧的首部中有一个16位的类型域,标识以太网数据帧所携带的上层数据类型,如IPv4、ARP、IPv6、PPPoE等等
综上,数据封装和分用的大致过程为:发送端每层增加该层的首部,接收端每层删除该层的首部;这样做的好处在于第一节最后提到的三个点。
Http报文传输过程
数据包在不同物理网络之间传输过程中,网络层会利用路由器对不同网络间的数据包进行存储、分组转发处理。
构造互联网最简单的方法就是把两个或多个网络通过路由器进行连接,路由器可以理解为一个网络互链的“硬件黑盒”,其作用就是为不同类型物理网络提供链接,譬如以太网,令牌网、点对点连接、光纤分布式数据接口(FDDI)等等
对于不同类型的物理网络来讲,可能存在多个路由器,但是对于应用层来说,就这些是不需要知道的,TCP协议族就是屏蔽了物理层的复杂性和差异性,是的互联网TCP/IP传输变得简单且强大。
各版本间的问题及优化手段
网络协议有很多种,但是对互联网来讲,用的最多的就是HTTP协议。HTTP距今发展已有1.0、1.1、2,还有在路上的3.0版本;同时在Http之上还有Https;接下来我们梳理一下HTTP协议的发展脉络以及各个版本间的问题:
1996年,HTTP1.0协议规范RFC 1945发布;
1999年,HTTP2.0协议规范RFC 2616发布;
2015年,HTTP/2协议规范RFC 7540/7541发布;
HTTP1.0
HTTP协议的基本特点就是:“一来一回”;客户端发起TCP连接,在连接上面发送个HTTP Request到服务器,服务器再返回一个HTTP Response,然后连接关闭。每来一个请求,就要开个连接,请求完了,就关闭连接。据此,我们可以看到有两个毛病在这里:
性能问题
连接的建立,关闭都是耗时耗资源的操作。对于网页来说,除页面的HTML请求外,还有JS、CSS、图片等资源,这都是HTTP请求啊。一个大型网页,这种资源请求量挺可怕的,根据这些资源每个都搞一个TCP连接那是非常耗时的。虽说可以搞多个连接,并发发送请求,但连接数的资源也是有限的啊。
服务器推送问题
不支持“一来多回”的机制,服务器无法在客户端没有请求情况下主动向客户端推送消息。但是目前很多前置前端业务需要这样的机制
针对上面第一个问题的解决,HTTP1.0设计了两个属性字段来解决:
Keep-Alive
在客户端HTTP请求头添加字段:Connection:Keep-Alive;服务器收到后,在处理完请求会不会关闭连接;同时,在Response里加上该字段,然后后等待客户端该链接上的下一个请求;但引入这个之后又引发了另一问题,连接数有限,每个连接都不关的话,有些连接“占着茅坑不拉屎”,终会导致连接数耗光,因而又弄了个“Kepp-Alive timeout”,设置等待下次请求的超时时间,过段时间每请求就关闭。
Content-Length
由于上面Keep-Alive引入,提供了连接复用机制,但也有了新问题。虽说一次请求服务器,响应了。但是对于客户端来说,它又不知道服务器处理请求完成了没或者咋知道接收回来的数据包是完整的。因而在HTTP Response中设置一个标志位:Content-Length:xxx。这个字段表示返回的Body共有多少字节。
HTTP1.1
Chunk机制:上面Content-Length引入后,需要计算接收后数据包的大小;这对于服务器的资源和性能提出了要求;假设服务器返回的数据是动态语言生成的内容,则此时计算长度会很困难;即使能够计算,那也需要在服务器内存中渲染出整个页面后,再计算长度,这非常耗时且影响服务器整体性能。
为此,大佬们又在HTTP1.1中缝缝补补的添加了一个Chunk机制(Http Streaming),就是在响应头部加上“Transfer-Encoding:chunked”属性,其目的是告诉客户端,响应body是分块发送的,块与块之间有间隔符,所有块结尾有特殊标记。
Pipeline机制:有了“连接复用”之后,减少了建立和关闭连接的开销。但仍存在一个问题,请求是串行的,One By One(一来一回,再一来一回),如此并发程度不够。因而,为提高响应速度,HTTP1.1引入了Pipeline机制。
从图中明显看出,Pipeline提高了请求的处理效率。但有个致命问题,就是Head-of-Line-Blocking(队头阻塞);客户端发送的请求顺序为1,2,3,虽然服务器是并发处理的,但客户端接收响应的顺序必须是1,2,3,如此这样才能把响应和请求成功配对,跟队列一样,先进先出。一旦队列头部请求1发生延迟,客户端迟迟收不到请求1的响应,则请求2,3的响应也会被阻塞,也正是因为如此,为避免Pipeline机制副作用,很多浏览器默认把Pipeline关闭了。
HTTP/2出现前改善性能办法
由于Pipeline不能用,在同一TCP连接上,请求串行;对同一域名,浏览器限制只能开6~8个连接。但一个网页由上面我们知道,会因资源同时发起几十个HTTP请求,只有6~8连接可用。如何提高并发量,或者说提高网页渲染的性能?前人们尝试了很多方式,具体每一项就不具体展开讲了:
Spriting技术
CSS内联(Inling)
JS压缩合并(拼接)
请求分片(如,CDN)
到目前为止我们还没有对HTTP1.0中第二个问题做任何解释,我们现在来说说常见几种处理办法:
客户端定期轮询
譬如,每5s向服务器发起一个HTTP请求,如果有新消息,就返回;但效率低又增加服务器压力
FlashSocket/WebSocket/SignalR
不再基于HTTP,而是基于TCP其他协议
HTTP长轮询
客户端发送一个HTTP请求,如有新消息,立马返回;如没有则hold住,等个约定好的个把时间后,服务器如果没新消息则发送一个空消息给客户端,客户端收到空消息后直接断开连接;这是目前服务器常用的推送方法
HTTP Streaming
HTTP/2
对于HTTP1.1Pipeline所带来的副作用,很多优秀的开发人从应用层的角度提高HTTP1.1的效率,没办法进行普适性推广。Google期望从协议层实验性的去解决这个问题,于2009年年中公布于世。继各大浏览器厂商(Chrome、Firefox、Opera)嗅探到这个优秀的东西之后,各大大型网站(Google、Twitter、Facehook)也陆续在其服务器上部署了SPDY。后来HTTP-WG工作组于2012年也留意到这样的趋势,就尝试引进SPDY并规避了其自身的一些缺陷,发布HTTP/2官方标准。
SPDY引进一个新的二进制分帧数据层,实现了多向请求和响应、优先次序、最小化及消除不必要的网络延迟,目的是更有效利用底层TCP连接。在HTTP-WG引进SPDY初期,HTTP1.1已经成当时互联网的主流,必须考虑其新旧版本的兼容性:
不可更改URL范式
不可更改HTTP请求和响应的报文结构
知道SPDY大致作用后,我们可以清楚知道,HTTP-WG其实是在网络分层模型中,改进了HTTP1.1与TCP之间不合理的地方,做了一步兼容性尝试。所以HTTP/2不是对HTTP1.1的演进,而是其实如下图所处的位置:
二进制分帧
二进制分帧是HTTP/2为解决HTTP1.1中Head-of-Line-Blocking(队头阻塞)问题所设计的核心特性,在应用层和传输层之间增加此二进制分帧层,打破HTTP1.1的性能限制、改进传输性能。
HTTP1.1本身是明文字符串格式,所谓二进制分帧就是指把这个字符格式的报文给TCP之前转换成二进制,并且分成多个帧(数据块)来发送。
解释帧
帧是最小的通信单位,承载着特定类型的数据,例如HTTP标头、消息负载等,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装
HTTP/2把请求和响应通过分帧并且给每个帧打上流ID去避免依次响应的问题,对方接收到帧之后再根据ID拼接出流,这样就可以乱序响应从而避免请求时的队列阻塞问题。
有了这玩意,在TCP层面虽仍是串行的,但从HTTP来讲,请求就是并发收发的。咱列举一下这玩意的好处有啥:
并行交错地发送多个请求,请求之间互不影响
并行交错地发送多个响应,相应之间互不干扰
使用一个连接并行发送多个请求和响应
不在为绕过HTTP/1.x的诸多限制,而作其他尝试(参考HTTP/2出现前的几大尝试)
消除不必要的延迟和提高现有的网络容量利用率,从而减少页面的加载时间。
等等。。
虽说有这么多好处,但是,你看任何问题都有但是,哈哈哈;这儿有个关键问题:二进制分帧真的就解决了Pipeline的“队头阻塞”问题吗?
答案是并没有(你看是不是很难受,哈哈哈)二进制分帧只是将问题的力度细化到了“帧”的地步。由于最终还是使用的TCP,所以很现实。
只要你用了TCP协议,你就绕不开“队头阻塞”问题。因为TCP协议是具备天然“先进先出”特性的。如下图中帧F3(队头第一个帧)在网络上因为不知名原因被阻塞了或者丢了。服务器肯定要一直等着F3来,它不来后面的包服务器会自动丢弃的。
但是中的但是,虽说没有完全解决“队头阻塞”问题,它却降低这种事儿发生的概率。分两种情况,分析过程参照HTTP/2请求和响应的时序图:
情况一:服务器对请求1处理的很及时,但网络传输慢;这玩意无解,无论是二进制分帧还是其他手段,你都不能保证在如此庞大的互联网传输链路上能保证及时且可靠,故不作考虑。
情况二:服务器对请求1处理很慢,但服务器的响应2、3是先响应1发出去的,所以响应2、3并不会因为请求1处理慢而被hold住。
HTTP/2除了二进制分帧还有其他尝试,如指定每个流的优先级以及头部压缩。这个就不做详细展开了;但是好奇你的就问了,HTTP/2既然没解决“队头阻塞”问题,那咋样解决呢?那就不得不把Google这个公司再次拿出来说事儿了,人家又祭出一个大招“QUIC”协议,直接摒弃TCP协议。因为你TCP协议不是天然“先进先出”吗,那我不用你不得了。
HTTP/3(在路上)
QUIC(Quick UDP Internet Connection)是由Google公司基于UDP协议提出的多路并发传输协议;实现原理是UDP 迪菲赫尔曼算法(Diffie-Hellman)
如上图所示,QUIC协议在网络分层中的位置,它取代了TCP的部分功能(数据包不丢失);又实现了SSL/TLS的所有功能,还替代了HTTP/2中的部分功能(HTTP/2多路复用);QUIC协议又几个关键的特性:
不丢包(Raid5算法和Raid6算法)
针对UDP丢包问题,TCP是通过ACK 重传解决的,但大多数时候这样效率不高。Google他们借助磁盘存储领域经典的Raid5和Raid6算法来解决TCP中重传效率问题。例如现在又ABCDEFGHIJ依次顺序的数据包,首先对其划块,分为’ABCDE’ 【】 ‘FGHIJ’ 【】,如此这样的数据块,其中【】表示冗余数据包。当服务器收到ABCDE后,根据数据冗余包中对’ABCDE’完整性描述,譬如Hash(ABCDE)=123,假设C服务器没收到,根据这个完整性描述值反推,发现少了C,那就要求客户端再重发一次C,这就是Raid5.Raid6就是多了一个数据冗余块;具体算法描述就不展开了
更少的RTT
了解过HTTPS握手方式(TCP3次握手 SSL/TLS4次握手),三次RTT。常见网络延迟的原因要么是带宽要么就是RTT。RTT有个特点就是同步阻塞。发出去后必须等反馈才能发下一个。对于QUIC协议是直接把RTT降为0次或1次,具体实现原理可以自行了解
连接迁移
解释RTT和TCP元组
RTT:TCP单次握手的往返时间。连接的往返时间称为RTT(Round Trip Time)
4元组:源IP、目标IP、源端口、目标端口
5元组:源IP、目标IP、协议号、源端口、目标端口
7元组:源IP、目标IP、协议号、源端口、目标端口、服务类型、接口索引
解释了关键特性,咱了了QUIC到底有啥好的:
基于UDP协议,使用UDP端口号识别机器上的特定服务器
可靠,基于不丢包特性,提供了类似TCP的数据包重传、拥塞控制、调整传输节奏以及其他TCP的特性
无序并发字节流,QUIC单个数据流可保证有序交付,但对多个数据流会以乱序接收后重组
快速握手,提供0-RTT和1-RTT的不同层级的单次握手响应时间
基于TLS1.3传输安全协议,单次握手往返次数更低,降低了协议延迟
??
最后我们搞一下HTTP各个版本比对表(不含未上市的HTTP/3):
HTTP1.0HTTP1.1HTTP/2Host头
Range头
长连接
request methodGET/HEAD/POSTOPTIONS/PUT/DELETE/TRACE/CONNECT/GET/HEAD/POST全部cacheExpire/Last-Modefied PragmaExpire/Last-Modefied Pragma/ETag/Cache-Control全部header压缩
多路复用
服务器推送
关于HTTP1.1与HTTP2的直观响应速率提升,请看:https://http2.akamai.com/demo
对于QUIC协议如有兴趣可以自行进行深入了解,譬如它目前存在那些劣势。但仍旧是一个新趋势。目前仍是TCP的天下;仍有必要了解清楚TCP协议,下面我们进行展开。
4
TCP/IP协议
报文格式
TCP/IP协议栈中,IP协议只关心如何让数据跨本地网,不关心如何传输。整个TCP/IP协议栈,解决数据是如何通过许许多多的点对点通路,并保证其顺利抵达目的地。这个点对点通路称之为“跳(hop)”,通过许多“跳”的基础上建立相互的数据通路。
传输层的TCP协议提供面向连接、可靠字节流服务,数据帧格式如下图所示:
下面我们对每个字段进行逐一解释:
源端口
源端口表示报文的发送端口,占16位。
目的端口
目的端口表示报文的接收端口,占16位。
TCP协议基于IP协议基础传输,TCP报文源端口 源IP与目标IP 目标端口一起,这四元组,组合起来确定一条TCP连接
序号
TCP传输过程中,发出端的字节流中传输报文数据部分的每个字节都有其32位序号,发起方发送数据标记序号;通常SN与控制标志位中的SYN的值有关,二者组合表述不同含义:
SYN=1,建立连接初始阶段,SN表示位初始序号(ISN-Initial Sequence),通过算法来随机生成序号
SYN=0,数据传输阶段,第一个初始报文的序号位:ISN 1,之后的则为:前一个SN TCP报文净荷字节数(不含TCP头)。数据传输过程中,TCP通过序号对上层提供有序的数据流。发送端根据序号来跟踪发送数据量;而接收端根据序号判断识别并丢弃重复的包,对乱序的包依据序号再作重新排序。
确认序号
确认序号(AN)标识报文接收端期望接收字节序列。如设置ACK控制位,则AN标识一个准备接收包的序列码(就是在说下一个期望接收的包的序列码)
如上图所示,服务器每收到一个包后,都会发送ACK响应确认数据包给客户端,其数据包的值为每个客户端包的SN 包净荷;用于表示服务器已确认收到的字节数同时也表示期望下次收到客户端的数据包的SN序号;其中只有控制标志中的ACK表示为1时,数据帧中的确认序号AN才有效。TCP协议规定,连接建立后。所有发送报文的ACK必须为1,也就是说建立连接后,所有报文的确认序号有效。如果是SYN类型的报文,其ACK标志则为0,没有确认序号。
头部长度
用于表示TCP报文首部长度,4位。这个值表示的不是字节数,而是头部包含32位的数目或倍数或者4字节的倍数。所以TCP头部最多可有60字节(4*15)。没有任何选项字段的TCP头部长度会超过20字节,所以头部长度为5(20/4)
控制标志
控制标志(Control Bits)共6位。分别为URG、ACK、PSH、SYN、FIN,这些都各占用1位。下面用个表解释下每个标志位的作用:
标志位说明URG表示紧急指针字段有效,指示报文段内的上层实体(数据)标记为“紧急”数据。当URG=1,其后紧急指针表示紧急数据在当前数据段中所在为止(相当于序列号的字节偏移量),TCP接收方必须通知上层实体。ACKACK=1表示‘确认号字段-AN’有效;TCP协议规定,连接建立后所发送报文ACK必须为1;ACK=0时,则表示该数据段不包含确认信息。当ACK=1,表示该报文包括一个对已被成功接收报文段确认序号,该序号也是下一个预期序号PSH表示当前报文须请求推(push)操作,当PSH=1,接收方在接收到数据后立即将数据交给上层,而不是单纯等到缓冲区满了在推RSTRST=1表示复位TCP连接;用于重置一个已混乱的连接,也可用于拒绝无效数据段或者一个连接请求。如数据段被设置RST位,则表明发送方有问题发生SYN在建立连接时用于同步序号。SYN=1,ACK=0时,表明为请求连接报文。对方同意连接后,在响应报文中设置SYN=1,ACK=1。所以SYN=1表示一个连接请求或者连接接收报文FIN用于释放TCP连接,标识发送发比特流结束,用于释放一个连接。FIN=1,表明此报文发送发数据已发送完毕,并要求释放连接
窗口
用于流量控制,16位,2个字节;流量控制的单位为字节数,表明自身期望单次接收的字节数。
校验和
发送端对整个TCP报文段(头部和数据)进行校验和计算并设置此值,接收端用这个字段对收到的数据包进行验证
紧急指针
偏移量,长度16位,2个字节。与SN序号值相加表示紧急数据最后一个字节的序号
可选选项和填充
上面所有字段是TCP报文首部固有字段,总长20字节。可选项和填充部分长度位4n字节(n为整数),是根据需要自行添加。如果不满4n字节,则补上填充位(加0),使得选项总长度为32位的整数倍,最常见的就是MSS(Maximum Segment Size–最长报文大小),每个连接方通常都在通信的第一个报文段(SYN标志为1的那个段)中指明这个选项字段,表示当前连接方所能接受的最大报文段的长度。
既然了解了所有字段,那我们来聊聊TCP协议的可靠性是怎么保障的?
应用数据分割,切割成适合发送的数据块。这部分通过MSS选项位来控制,这种机制也被称为协商机制。MSS由上面我们知道了,其表示TCP传向另一端的数据块最大长度。MSS只能出现在SYN报文中,若一方不接收另一方的MSS值,则MSS就为536字节。一般来说,MSS越大越利于提高网络利用率。
重传机制。设置定时器,等待确认包,如果定时器超时没收到确认包,则重新再发一次
对首部和数据进行校验和计算
接收端对数据进行重排序,交给应用层
接收端丢弃重复包
通过滑动窗口实现流量控制
三次握手(网络二将军问题)图示
解释二将军问题
两支军队,分别由两个将军领导,正准备攻击一个坚固城市。两支军队都驻扎在城市两旁的不同山谷中。两军之间隔着第三个山谷,两将军通讯唯一方式就是穿过第三山谷传信件。问题是,第三山谷被敌人守军占据,并且经此山谷的信息极有可能被截获;如何保证信件安全传输?
四次挥手图示
常见关于3次握手和4次挥手的问题
1.为啥需要三次握手和四次挥手?
答:三次握手:为解决网络通信中的“二将军问题”,在建立连接场景中,服务端收到客户端的SYN连接请求报文后,其中ACK报文表示对请求报文的应答,SYN报文用于表示服务端连接已同步开启,而ACK报文和SYN报文之间,不会有其他报文需要发送,因而可合二为一,直接发送SYN ACK报文。所以建立连接时只需三次;
四次挥手:在关闭连接场景中,被动断开一方,在收到对方的FIN结束请求报文时,极有可能业务数据还没有发送完,并不能马上关闭连接,被动方只能先回发一个ACK响应报文告诉主动方:“我知道了,手头忙完,我就断,你先等一会儿,我先发你FIN ACK,你先拿着一会儿时间(2*MSL)”。所以,被动断开的确认报文,需拆成两步走,因而是四次挥手。
2.为啥是三次握手,看起来好像两次就够了?
答案:三次握手最关键的一点是解决了二将军问题,在开始数据传输前,确认双方都已完成对应的准备工作,而且彼此知道对方是已经准备好了;同时双方也同时完成初始SN序列号的协商,双方的SN号在握手过程中被确认和发送。
假设如上图,只需两次握手;客户端发送一个SYN请求帧,服务端收到后立马发了SYN ACK确认帧。按二次握手的隐含意义,表明服务端认为连接已经准备好了,立马就可以发送数据帧了。但是有没有想过万一SYN ACK的确认帧在传输过程中丢失了。客户端没有收到,此时客户端不知道服务端到底有没有准备好,客户端在没收到确认帧之前就认定服务端没准备好,此时也不知道服务端的SN号。此时服务端开始发送数据,客户端会拒收直接丢弃这些后续来的数据包,而服务端在发送完数据后又要等客户端的确认应答,如果没收到就会一直发,然后就搞得彼此不知道对方在干嘛,造成死锁。
3.为啥主动断开方要在TIME-WAIT状态时必须等够2MSL时间呢?1MSL不行吗?
答案:
1)为了确保两端都能最终正常关闭。假设网络不可靠(本身就不可靠),被断开方发送FIN ACK报文后,主动方的ACK响应有可能存在丢失风险,这个时候被动断开方处于LAST-ACK状态;收不到主动断开方的ACK是不会更改自己的状态到CLOSED的。在这种情形下,被动断开方的计时器任务会发现超时并重传FIN ACK断开响应报文,如果在2MSL内,则主动断开方收到重传的FIN ACK后,主动再发一次ACK报文,然后主动关闭方会再次重置计时器开启下一次的2MSL,来保证被动断开方是可正常关闭的。假设主动断开方不等或者只等1个MSL,那主动方不会收到重传的FIN ACK也不会再发一次确认ACK给被动方,那么被动方就一直处于LAST-ACK状态,无法关闭。
2)防止“旧连接已失效的数据报文”混入新的连接中。主动断开方在发送完最后一个ACK后,经过2MSL,才能最终关闭和释放端口。那就意味着,极有可能象用端口的新的TCP连接,再这个2MSL时间之后才能正常建立。在这个2MSL时间内,旧连接所产生的所有数据报文,肯定都已经完成老的数据发送了,所以就不会在新连接中存在了。
4.如果在TCP连接过程中,客户端突然抽风故障了?
答案:TCP有保活机制,有个保活计时器,如果客户端出现故障,服务端肯定不能无脑等下去,这样会浪费系统资源。每每收到来自客户端的消息时,服务端的保活计时器都会重置复位。计时器超时时间一般设置为两小时,嫌弃太长也可以自己更改。若2小时都没收到客户端的任何数据,服务端会发送探测报文,之后每隔75秒发送一次。若一连发了十次,都没响应,服务端直接认为客户端死了,紧接着就关闭连接。
注:关于网络编程的最佳实践,后续会在个人框架核心内容写完后;单独开篇文章聊一聊
END
●内存管理的噩梦:泄露与溢出
●Linux及Docker网络基础知识