赞
踩
RESTful是一种架构的规范与约束、原则,符合这种规范的架构就是RESTful架构
RESTful 架构的核心规范与约束:统一接口
分为四个子约束:
目的:实现客户端无需借助任何文档即能调用到所有的服务器资源
REST中信息的关键抽象是一种资源。可以命名的任何信息可以是资源:文档或图像,临时服务,其他资源的集合,非虚拟对象(例如,人)等。REST使用资源标识符来标识组件之间交互中涉及的特定资源。
任何特定时间戳的资源状态称为资源表示。表示由数据,描述数据的元数据和超媒体链接组成,这些链接可以帮助客户转换到下一个期望的状态。
表示的数据格式称为**媒体类型**。媒体类型标识定义如何处理表示的规范。真正的RESTful API看起来像*超文本*。每个可寻址信息单元明确地(例如,链接和id属性)或隐式地(例如,从媒体类型定义和表示结构导出)携带地址。
根据罗伊菲尔丁的说法:
超文本(或超媒体)意味着信息和控制的同时呈现,使得信息成为用户(或自动机)通过其获得选择和选择动作的可供性。请记住,超文本不需要是浏览器上的HTML(或XML或JSON)。机器在理解数据格式和关系类型时可以跟踪链接。
此外,资源表示应该是自描述的:客户端不需要知道资源是员工还是设备。它应该基于与资源相关的媒体类型。因此在实践中,您最终将创建大量自定义媒体类型 - 通常是与一种资源相关联的一种媒体类型。
每种媒体类型都定义了默认处理模型。例如,HTML定义了超文本的呈现过程以及每个元素周围的浏览器行为。它与资源方法GET / PUT / POST / DELETE / …没有任何关系,除了一些媒体类型元素将定义一个过程模型,其类似于“具有href属性的锚元素创建一个超文本链接,当被选中时,在与CDATA编码的href属性对应的URI上调用检索请求(GET)。“
与REST相关的其他重要事项是用于执行所需转换的资源方法。许多人错误地将资源方法与HTTP GET / PUT / POST / DELETE方法联系起来。
Roy Fielding从未提及任何关于在哪种情况下使用哪种方法的建议。他所强调的是它应该是统一的接口。如果你决定HTTP POST将用于更新资源 - 而不是大多数人推荐HTTP PUT - 它没关系,应用程序接口将是RESTful。
理想情况下,更改资源状态所需的所有内容都应该是该资源的API响应的一部分 - 包括方法以及它们将保留表示的状态。
应输入REST API,除了初始URI(书签)和适用于目标受众的标准化媒体类型集之外没有任何先验知识(即,任何可能使用API的客户都应该理解)。从那时起,所有应用程序状态转换必须由客户端选择服务器提供的选择来驱动,这些选择存在于接收的表示中或者由用户对这些表示的操纵所暗示。转换可以由客户端对媒体类型和资源通信机制的知识来确定(或限制),这两者都可以在运行中(例如,按需代码)进行改进。
[失败在这里意味着带外信息驱动交互而不是超文本。]
在构建RESTful API时,另一件可以帮助您的是基于查询的API结果应该由带有摘要信息的链接列表表示,而不是由原始资源表示的数组表示,因为查询不能代替资源标识。
很多人更喜欢将HTTP与REST进行比较。REST和HTTP不一样。
REST!= HTTP
但是,由于REST还打算使web(互联网)更加简化和标准化,他主张更严格地使用REST原则。这就是人们试图开始将REST与网络(HTTP)进行比较的地方。Roy fielding在他的论文中没有提到任何实现指令 - 包括任何协议首选项和HTTP。到时候,您正在遵循REST的6个指导原则,您可以将您的接口称为RESTful。
简而言之,在REST架构风格中,数据和功能被视为资源,并使用统一资源标识符(URI)进行访问。通过使用一组简单,定义明确的操作来执行资源。客户端和服务器通过使用标准化接口和协议(通常是HTTP)来交换资源的表示。
资源与其表示分离,以便可以以各种格式访问其内容,例如HTML,XML,纯文本,PDF,JPEG,JSON等。例如,可以使用和使用关于资源的元数据来控制高速缓存,检测传输错误,协商适当的表示格式,以及执行认证或访问控制。最重要的是,与资源的每次交互都是无状态的。
所有这些原则都有助于RESTful应用程序简单,轻量和快速。
IP地址:电脑IP,可以说是网络上的电脑识别号,有IP的电脑才能上网,方便大家的交流和寻找作用
子网掩码:互联网是由许多小型网络构成的,每个网络上都有许多主机,这样便构成了一个有层次的结构。IP地址在设计时就考虑到地址分配的层次特点,将每个IP地址都分割成网络号和主机号两部分,以便于IP地址的寻址操作
子网掩码不能单独存在,它必须结合IP地址一起使用。子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分。
默认网关:一个网络就必须有网关,也就是internet网进入一个集体、或个体网络的最先接连者,有它我们的信息将能更好的得到规划。
网关:只有设置好网关的IP地址,TCP/IP协议才能实现不同网络之间的相互通信
网络号:用ip地址和子网掩码的二进制数进行“与”运算即可得出网络号。
广播地址(Broadcast Address)是专门用于同时向网络中所有工作站进行发送的一个地址。
在使用TCP/IP 协议的网络中,主机标识段host ID 为全1 的IP 地址为广播地址,广播的分组传送给host ID段所涉及的所有计算机。例如,对于10.1.1.0 (255.255.255.0 )网段,其广播地址为10.1.1.255 (255 即为2 进制的11111111 ),当发出一个目的地址为10.1.1.255 的分组(封包)时,它将被分发给该网段上的所有计算机。
文件句柄限制
在linux下编写网络服务器程序的朋友肯定都知道每一个tcp连接都要占一个文件描述符,一旦这个文件描述符使用完了,新的连接到来返回给我们的错误是“Socket/File:Can’t open so many files”。
这时你需要明白操作系统对可以打开的最大文件数的限制。
进程限制
执行 ulimit -n 输出 1024,说明对于一个进程而言最多只能打开1024个文件,所以你要采用此默认配置最多也就可以并发上千个TCP连接。临时修改:ulimit -n 1000000,但是这种临时修改只对当前登录用户目前的使用环境有效,系统重启或用户退出后就会失效。
重启后失效的修改(某些系统可能重启后并不会失效),编辑 /etc/security/limits.conf 文件, 修改后内容为:
soft nofile 1000000
hard nofile 1000000
永久修改:编辑/etc/rc.local,在其后添加如下内容:
ulimit -SHn 1000000
全局限制
执行cat /proc/sys/fs/file-nr 输出 9344 0 592026,分别为:
\1. 已经分配的文件句柄数,
\2. 已经分配但没有使用的文件句柄数,
\3. 最大文件句柄数。
但在kernel 2.6版本中第二项的值总为0,这并不是一个错误,它实际上意味着已经分配的文件描述符无一浪费的都已经被使用了 。
我们可以把这个数值改大些,用 root 权限修改 /etc/sysctl.conf 文件:
fs.file-max = 1000000
net.ipv4.ip_conntrack_max = 1000000
net.ipv4.netfilter.ip_conntrack_max = 1000000
端口号范围限制
操作系统上端口号1024以下的端口是系统保留的,1024-65535是用户可用的,我们知道每个TCP连接都会用掉一个端口,所以网上铺天盖地的文章都在说单机服务器能支持最多的TCP连接是60000+,你是不是也这样认为呢?
TCP如何标识一个连接
这个问题,可能你会说,TCP是用四元组(也有人说是五元组,五元组相比于四元组多了协议,下面都说四元组)来标识一个连接,这四元组是【local ip, local port,remote ip,remote port】,我们单机作为服务器的时候,bind(port)的时候绑定了一个端口,然后就在等待着accept,没有再消耗其他端口资源,由此发现,65535并不是单机服务器了连接数的限制因素。
我们服务器代码中总是先listen然后再accept,你有没有注意到这两个方法都会返回一个文件句柄socketfd,你想过这两个socketfd的区别吗?
listen的socketfd是调用socket生成的,该socketfd是告诉TCP/IP协议栈这个socketfd指向绑定的[IP:PORT],以后有请求[IP:PORT]的数据都发给这个socketfd,所以listen的socketfd就是表示服务器的绑定信息。
accept返回的socketfd是标识当前连接四元组的【local ip, local port,remote ip,remote port】,作用是表示谁跟我正连接着呢,我发送接收数据的来源和目的地是什么。其中与服务器绑定的local ip, local port是不变的,变化的是不同的客户端的remote ip,remote port。
所以我们得出结论:影响单机服务器最大并发连接数的因素是客户端的ip和port。
服务器最大连接数到底是多少
server通常固定在某个本地端口上监听,等待client的连接请求。不考虑地址重用(unix的SO_REUSEADDR选项)的情况下,即使server端有多个ip,本地监听端口也是独占的,因此server端tcp连接4元组中只有remote ip(也就是client ip)和remote port(客户端port)是可变的,因此最大tcp连接为客户端ip数×客户端port数,对IPV4,不考虑ip地址分类等因素,最大tcp连接数约为2的32次方(ip数)×2的16次方(port数),也就是server端单机最大tcp连接数约为2^48。
传输层协议使用带外数据(out-of-band,OOB)来发送一些重要的数据,如果通信一方有重要的数据需要通知对方时,协议能够将这些数据快速地发送到对方.为了发送这些数据,协议一般不使用与普通数据相同的通道,而是使用另外的通道.linux系统的套接字机制支持低层协议发送和接受带外数据.但是TCP协议没有真正意义上的带外数据.为了发送重要协议,TCP提供了一种称为紧急模式(urgentmode)的机制.TCP协议在数据段中设置URG位,表示进入紧急模式.接收方可以对紧急模式采取特殊的处理.很容易看出来,这种方式数据不容易被阻塞,可以通过在我们的服务器端程序里面捕捉SIGURG信号来及时接受数据或者使用带OOB标志的recv函数来接受.
Linux服务器之间同步文件使用:*rsync* 命令
rsync和scp区别:用rsync做文件的复制要比scp的速度快,rsync只对差异文件做更新。scp是把所有文件都复制过去。
答:token不同,cookie不同,本机对url缓存解析不同。这之中又问了问cookie和session,DNS解析的过程等
为了为了简化网络设计的复杂性,通信协议采用分层的结构,各层协议之间既相互独立又相互高效的协调工作。对于复杂的通信协议,其结构应该是采用层次的。分层的协议可以带来很多便利:
分层的好处有:
一灵活性好:当任何一层发生变化时,只要层间接口关系保持不变,则在这层以上或以下各层均不受影响。此外,对某一层提供的服务还可进行修改。当某层提供的服务不再需要时,甚至可以将这层取消,更容易管理。
二各层之间是独立的:在各层间标准化接口,允许不同的产品只提供各层功能的一部分,某一层不需要知道它的下一层是如何实现的,而仅仅需要知道该层通过层间的接口所提供的服务。由于每一层只实现一种相对独立的功能,所以比较容易实现!
数据传输速率(每秒传输二进制信息的位数),单位是位/秒 bps或b/s,
计算公式: R=1/T×log2N(bps)
T为一个数字脉冲信号的宽度(全宽码)或重复周期(归零码)单位为秒;
N为一个码元(一个数字脉冲称为码元)所取的离散值个数。
端口号:53
DNS 是一个分布式数据库,提供了主机名和 IP 地址之间相互转换的服务。这里的分布式数据库是指,每个站点只保留它自己的那部分数据。
DNS解析有两种方式:递归查询和迭代查询
DNS域名解析的过程:
打开浏览器,输入一个域名。比如输入www.163.com,这时,主机会发出一个DNS请求到本地DNS服务器。地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果。如果没有,本地DNS服务器还要向DNS根服务器进行查询。
根DNS服务器没有记录具体的域名和IP地址的对应关系,而是告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址。
本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。
最后,本地DNS服务器向域名的解析服务器发出请求,收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。
DNS采用UDP传输
一次UDP名字服务器交换可以短到两个包:一个查询包、一个响应包。一次TCP交换则至少包含9个包:三次握手初始化TCP会话、一个查询包、一个响应包以及四次分手的包交换。
考虑到效率原因,TCP连接的开销大得,故采用UDP作为DNS的运输层协议
在两种情况下会使用 TCP 进行传输:
65536.因为TCP的报文头部中源端口号和目的端口号的长度是16位,也就是可以表示2^16=65536个不同端口号,因此TCP可供识别的端口号最多只有65536个。但是由于0到1023是知名服务端口,所以实际上还要少1024个端口号。
而对于服务器来说,可以开的端口号与65536无关,其实是受限于Linux可以打开的文件数量,并且可以通过MaxUserPort来进行配置。
服务器端:
数据库端:
因为UDP是无连接的协议,所以在传输层上无法保证可靠传输,要想实现可靠传输,只能从应用层实现。需要实现seq/ack机制,重传机制和窗口确认机制。
就要接收方收到UDP之后回复个确认包,发送方有个机制,收不到确认包就要重新发送,每个包有递增的序号,接收方发现中间丢了包就要发重传请求,当网络太差时候频繁丢包,防止越丢包越重传的恶性循环,要有个发送窗口的限制,发送窗口的大小根据网络传输情况调整,调整算法要有一定自适应性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fGZzB0c6-1649154398316)(J:\面试\2021.2.16\pic\watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjkwNTE0MQ==,size_16,color_FFFFFF,t_70#pic_center)]
TCP的头部大致包括:源端口,目的端口,序号,确认号,偏移位,标志位,校验和等等
UDP的头部则包括:源端口,目的端口,长度,校验和。
IP数据包的头部包括:源IP地址,目的IP地址,协议,校验和,总长度等等
每个udp包的最大大小是多少?
65507 约等于 64K
为什么最大是65507?
因为udp包头有2个byte用于记录包体长度. 2个byte可表示最大值为: 2^16-1=64K-1=65535
udp包头占8字节, ip包头占20字节, 65535-28 = 65507
如果要发送的udp报文大于65507怎么办?
需要在应用层由开发者自己分片发送. 分片的粒度最大65507字节. 系统的sendto函数是不支持大于65507字节的单包发送的.
,路由器* 工作在第三层(网络层),而我们常说的交换机* 工作在第二层(链路层)
路由器:寻址,转发(依靠 IP 地址)
交换机:过滤,转发(依靠 MAC 地址)
路由器内有一份路由表,里面有它的寻址信息(就像是一张地图),它收到网络层的数据报后,会根据路由表和选路算法将数据报转发到下一站(可能是路由器、交换机、目的主机)
交换机内有一张MAC表,里面存放着和它相连的所有设备的MAC地址,它会根据收到的数据帧的首部信息内的目的MAC地址在自己的表中查找,如果有就转发,如果没有就放弃
每一个路由器与其之下连接的设备,其实构成一个局域网
交换机工作在路由器之下,就是也就是交换机工作在局域网内
它们两个可不可以少一个?
交换机在局域网内工作,它根据 MAC 地址转发数据,如果没有了路由器在网络层寻址,那么我们的数据就不能发送到其他网络终端上去了
路由器内集成了交换机的功能,主机与路由器相连也可以实现数据转发,但是不足之处是:
可扩展的接口不如交换机多
交换机通常由硬件加速转发,路由器主要靠软件寻址,速度慢
七层协议:物、数、网、传、会、表、应
物理层 RJ45
数据链路层 PPP,IEEE 802.3/802.2
网络层 IP,ARP:
主要任务:把分组从源端传到目的端,为分组交换网上的不同主机提供通信服务。网络传输单元:数据报。
传输层 TCP,UDP
会话层
表示层 TIFF,GIF,JPEG,
应用层 DNS,HTTP,FTP
应用层提供各种各样的应用层协议,这些协议嵌入在各种我们使用的应用程序中,为用户与网络之间提供一个打交道的接口。
四层协议:数据链路层(物理层,数据链路层),网络层(网络层),传输层(传输层),应用层(会话层,表示层,应用层)
RIP“路由信息协议(Route Information Protocol)”的简写,主要传递路由信息,通过每隔30秒广播一次路由表,维护相邻路由器的位置关系,同时根据收到的路由表信息使用动态规划的方式计算自己的路由表信息。RIP是一个距离矢量路由协议,最大跳数为16跳,16跳以及超过16跳的网络则认为目标网络不可达。
OSPF:详见:https://zhuanlan.zhihu.com/p/41341540
网络层使用的是IP地址,数据链路层使用的是硬件地址。
ARP协议的用途是为了从网络层使用的IP地址,解析出数据链路层使用的硬件地址。
数据链路层:
把网络层交下来的Ip数据包添加首部和尾部封装成帧
https://blog.csdn.net/gettogetto/article/details/72851734
因为在链路层中帧的大小通常都有限制,比如在以太网中帧的最大大小(MTU)就是1500字节。如果IP数据包加上头部后大小超过1500字节,就需要分片。
IP分片和完整IP报文差不多拥有相同的IP头,16位ID域对于每个分片都是一致的,这样才能在重新组装的时候识别出来自同一个IP报文的分片。在IP头里面,16位识别号唯一记录了一个IP包的ID,具有同一个ID的IP分片将会重新组装;而13位片偏移则记录了某IP片相对整个包的位置;而这两个表中间的3位标志则标志着该分片后面是否还有新的分片。这三个标志就组成了IP分片的所有信息(将在后面介绍),接受方就可以利用这些信息对IP数据进行重新组织。
IPv4:
16位标识:唯一标识主机发送的每一个数据报,其值由系统随机生成,每发送一个数据报,其值加1.该值在数据包分片时被分片到每一个分片中。一个数据报的所有分片具有相同的标识位
13位分片偏移:是分片相对于原始Ip数据报开始处的偏移。实际的偏移值是该值左移3位(乘以8)得到的
除了最后一个分片外,每个分片的数据部分的长度必须是8的整数倍(这样才能保证最后一个分片拥有一个合适的偏移值)
8位生存时间TTL:数据报到达目的地之前允许经过的路由器的跳数。
常见设置64,每经过一个路由器减1.当TTL值为0,路由器丢弃报文,并向源端发送一个ICMP差错报文。TTL值可以防止路由器陷入路由循环。
16位头部检验和:由头部端填充,接收端使用CRC算法检验IP数据报头部在传输过程是否损坏
ping主要是为了测试两台主机之间的连通性,通过应用层直接使用网络层ICMP,没有通过运输层TCP和UDP,是通过发送ICMP报文回显请求实现。
ICMP协议不包含端口信息的,端口信息在数据传输层定义。
A主机构建一个ICMP格式的数据包,通过ICMP协议把该数据包和B主机的IP地址一起交给IP协议;
然后IP协议就会构建一个IP数据报,并且在映射表中查找目的IP对应的mac地址,将其交给数据链路层,构建一个数据帧,附上源mac地址和目的mac地址发送出去。
数据包(A主机的IP地址+控制信息+B主机的IP地址)
IP协议会根据B主机的IP地址和自己的子网掩码判断是不是属于同一层网络,如果是属于同一层网络的话,就会获得目的主机的MAC地址,如果以前两机有过通信,在A机的ARP缓存表应该有构建一个数据帧主机IP与其MAC的映射关系,如果没有,就发一个ARP请求广播,得到目的主机的MAC
目的主机接收到数据帧后,就会检查包上的mac地址与本机mac是否相符,如果相符,就接收并把其中的信息提取出来交给IP协议,IP协议就会将其中的信息提取出来交给ICMP协议。然后构建一个ICMP应答包,用相同的过程发送回去。
traceroute用来跟踪一个分组从源点到终点的路径,及到达其中每一个路由器的往返时间
连接
服务对象
可靠性
拥塞控制,流量控制
报文长度
首部开销
第一次的序号是随机序号,但也不是完全随机,它是使用一个ISN算法得到的。
seq = C + H (源IP地址,目的IP地址,源端口,目的端口)。其中,C是一个计时器,每隔一段时间值就会变大,H是消息摘要算法,输入是一个四元组(源IP地址,目的IP地址,源端口,目的端口)
ACK SYN FIN解释及是否消耗序列号
TCP数据报首部的关键字段:
确认号 4byte;即期望收到对方下一个报文段的第一个数据字节的序号,若确认号为N,则表明:到序号N-1为止的所有数据都已正确收到。
同步SYN(SYNchronization)===> 1bit;在连接建立时用来同步序号
终止FIN(FINis)===> 用来释放一个连接
复位RST(ReSeT)===> 1bit;当RST=1时,表明TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。RST置1还用来拒绝一个非法的报文段或拒绝打开一个连接。RST也可称为重建位或重置位。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gRyqL3j5-1649154398319)(./pic/三次握手.png)]
SYN-SENT(同步已发送)
SYN-RCVD(同步已发送)
假设 A 为客户端,B 为服务器端。
服务器端给客户端发送同步及确认报文时可以合并,四次会浪费时间
三次握手原因:
第三次握手是为了防止失效的连接请求到达服务器,让服务器建立错误连接。
客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打建立连接。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BJI4QPje-1649154398321)(./pic/四次挥手.png)]
ACK 在连接建立之后都为 1。
A 发送连接释放报文,FIN=1 ,seq=u。
B 收到之后发出确认,ACK=1,seq=v,ack=u+1,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据。
若B已经没有向A发送的数据,B发出连接释放报文段,主动关闭TCP连接。FIN=1,seq=w,重复上次已发送确认号ack=u+1。
A 收到 B的连接释放,发送以确认报文段,再时间等待计时器设置的2MSL(最长报文段寿命)后,连接彻底关闭。ACK=1,seq=u+1,ack=w+1。
四次挥手的原因
客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。
MSL:最长报文段寿命,一般为2分钟
MTU:最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸
MSS (网络传输数据最大值)的时候,用 MTU 减去网络层报头长度以及传输层报头长度即可。
TIME_WAIT
客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL(最长保报文寿命)。这么做有两个理由:
为什么TIME_WAIT的时间是2MSL?
TIME_WAIT至少需要持续2MSL时长,这2个MSL中的第一个MSL是为了等自己发出去的最后一个ACK从网络中消失,而第二MSL是为了等在对端收到ACK之前的一刹那可能重传的FIN报文从网络中消失。
为了可靠地实现全双工连接的终止,假设图2-5中客户端发送的最后一个ACK丢失,服务端将重传FIN,为了能够收到这个超时重传的FIN,客户端需要TIME_WAIT状态;那TIME_WAIT状态就必须是2MSL了吗?其实这个要看服务端FIN的超时重传时间RTO,如果RTO小于MSL,那TIME_WAIT状态MSL就够了,如果RTO大于2MSL那么TIME_WAIT状态2MSL也是不够的,所以只有RTO在MSL和2MSL之间的时候,TIME_WAIT状态存在的理由1才是TIME_WAIT的时间是2MSL的原因。其实一般情况下,RTO都是远远小于MSL的,但考虑到最糟糕的情况,RTO是为2MSL,所以TIME_WAIT状态为2MSL可以保证最糟糕情况也可以收到超时重传的FIN
为了保证本连接持续的时间所产生的所有分组都从网络中消失,也就是保证新建立一个TCP连接时,来自该连接老的重复分组都已经在网络中消失了。这里可能有的人会有个疑问:客户端回复最后一个ACK之后,感觉一个MSL就可以所有分组消失了啊,为什么还要2MSL所有分组才消失呢
假设客户端发送ACK刚刚过了一个MSL时间,而服务端在收到这个ACK之前一瞬间刚好启动超时重传FIN,所以要等这个FIN也消失,就是2MSL了。文中所指的另一个方向的应答应该就是这个超时重传的FIN。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SO5IdFbO-1649154398322)(./pic/客户端-服务器的套接字函数.png)]
connect:调用connect函数将激发三次握手,仅在连接建立成功或出错时才返回
listen:listen函数把套接字从CLOSING转为LISTEN状态
accept:由TCP服务器调用,用于从已完成连接队列头返回下一个已完成连接。如果已完成连接队列为空,进程会睡眠(套接字默认阻塞),成功返回一个代表客户端的文件描述符。
为了处理客户端异常断线,常服务端/客户端 需要发送心跳,及时清理掉断线用户,但服务端的心跳时间间隔不要设置得太小,否则容易导致客户端掉线频率增加。具体抓包结果如下:
服务端A发送包x给客户端B,B回ACK,但由于网络原因ACK未到达A,A重发x,B回ACK,A又未收到,这样几次后,A重发次数超过了TCP最大重发次数,于是A发送RST给B复位此链接,导致B断线!!!因此,若心跳间隔太小,则A
发给客户端的包的频率越高,导致客户端异常断线的概率就越大
意外断开,是客户端(多指支持3G的移动设备)并没有正常关闭socket,双方并未按照协议上的四次挥手去断开连接,一般的处理办法都是利用保活机制。而保活机制分又可以让底层实现也可自己实现
一、双方拟定心跳(自实现)
一般由客户端发送心跳包,服务端并不回应心跳,只是定时轮询判断一下与上次的时间间隔是否超时(超时时间自己设定)。服务器并不主动发送是不想增添服务器的通信量,减少压力。
情况1:客户端由于某种网络延迟等原因很久后才发送心跳(它并没有断),这时服务器若利用自身设定的超时判断其已经断开,而后去关闭socket。若客户端有重连机制,则客户端会重新连接。若不确定这种方式是否关闭了原本正常的客户端,则在ShutDown的时候一定要选择send,表示关闭发送通道,服务器还可以接收一下,万一客户端正在发送比较重要的数据呢,是不?
情况2:客户端很久没传心跳,确实是自身断掉了。在其重启之前,服务端已经判断出其超时,并主动close,则四次挥手成功交互。
情况3:客户端很久没传心跳,确实是自身断掉了。在其重启之前,服务端的轮询还未判断出其超时,在未主动close的时候该客户端已经重新连接。
这时候若客户端断开的时候发送了FIN包,则服务端将会处于CLOSE_WAIT状态;
这时候若客户端断开的时候未发送FIN包,则服务端处还是显示ESTABLISHED状态;
而新连接上来的客户端(也就是刚才断掉的重新连上来了)在服务端肯定是ESTABLISHED;这时候就有个问题,若利用轮询还未检测出上条旧连接已经超时(这很正常,timer总有个间隔吧),而在这时,客户端又重复的上演情况3,那么服务端将会出现大量的假的ESTABLISHED连接和CLOSE_WAIT连接。
最终结果就是新的其他客户端无法连接上来,但是利用netstat还是能看到一条连接已经建立,并显示ESTABLISHED,但始终无法进入程序代码。个人最初感觉导致这种情况是因为假的ESTABLISHED连接和CLOSE_WAIT连接会占用较大的系统资源,程序无法再次创建连接(因为每次我发现这个问题的时候我只连了10个左右客户端却已经有40多条无效连接)。而最近几天测试却发现有一次程序内只连接了2,3个设备,但是有8条左右的虚连接,此时已经连接不了新客户端了。这时候我就觉得我想错了,不可能这几条连接就占用了大量连接把,如果说几十条还有可能。但是能肯定的是,这个问题的产生绝对是设备在不停的重启,而服务器这边又是简单的轮询,并不能及时处理,暂时还未能解决。
二、利用KeepAlive
在S和C建立连接后,若双方均不发送数据只保持连接,则再两小时后(这个时间我未测试过,但很多资料都显示是这个间隔,貌似是默认的)系统会自动启动保活机制向peer发送包,看对方是否回应ack,若可以收到则继续保持,否则无效。这也就能解释如下现象了:
现象1:
自己写的socket程序放在pc机上(2台),为了模拟意外断掉,将客户端的网线拔掉,点击任何一方去发送都发送不了(这肯定么),那么你认为这时连接已经断了吗?非也,tcp连接并没有断,因为双方还不知道对方是否已经断掉(保活机制还未启动呢),这时若查看netstat,发现状态很已然是ESTABLISHED;当过一段时间后将客户端的网线插好,则可以继续发送。
现象2:
根据现象1的描述,感觉当电脑意外断网的时候,在短时间内又恢复时,QQ的自动连接也应该是这样。
首先通过TCP的四次挥手过程分析确定两个状态的出现背景。TIMEWAIT是大量tcp短连接导致的,确保对方收到最后发出的ACK,一般为2MSL;CLOSEWAIT是tcp连接不关闭导致的,出现在close()函数之前。
被动断开连接的一方在发送完ACK包之后就会进入CLOSE_WAIT状态.
它需要服务器在发送完剩余数据之后,就调用close来关闭连接.此时服务器从CLOSE_WAIT状态变为LAST_ACK状态.
如果服务器或客户端存在大量的TIME_WAIT状态,这是一种可能是正常的情况,主动断开连接的一方会进入TIME_WAIT状态.
TIMEWAIT
主动连接端会占用本地端口,大量的TIME_WAIT状态的socket,会占用大量的本地端口,当本地端口不足时,tcp连接不能建立成功。可以通过以下两种方式来解决上述问题
可以通过设置SOCKET选项SO_REUSEADDR来重用处于TIMEWAIT的sock地址,对应于内核中的tcp_tw_reuse,这个参数不是“消除” TIME_WAIT的,而是说当资源不够时,可以重用TIME_WAIT的连接
修改ipv4.ip_local_port_range,增大可用端口范围,来承受更多TIME,但这样效果有限。
设置SOCK选项SO_LINGER选项,这样会直接消除TIMEWAIT
通常情况下,客户端的端口资源比较充足,应该让客户端主动断开连接,但在某些场景下,如tcp连接长时间没有IO操作,应该将此空闲tcp连接踢除,否则空闲tcp会占有系统各个资源却不干事,太浪费了
CLOSEWAIT
close_wait状态是在TCP四次挥手的时候收到FIN但是没有发送自己的FIN时出现的,服务器出现大量close_wait状态的原因有两种:
客户端主动关闭,而服务端没有close关闭连接,则服务端产生大量CLOSEWAIT;一般是业务代码问题,如还有数据需要发送;或者服务器的业务逻辑有问题,没有执行close()方法
当recv的返回值为0时(对端主动关闭连接),会跳出while(1)循环,此时正确做法是调用close关闭tcp连接,将close(connfd)这句代码注释掉,注释后服务器对于客户端发送的FIN包不会做回应,一直保持close_wait状态。
服务器的父进程派生出子进程,子进程继承了socket,收到FIN的时候子进程处理但父进程没有处理该信号,导致socket的引用不为0无法回收
处理方法:
访问不存在的端口
它将产生一个ICMP端口不可达的信息.
异常终止连接
终止一个连接的正常方式是发送FIN,所有排队数据都已发送后才发送FIN,正常情况没有数据丢失
异常释放:发送复位报文段而不是FIN来释放一个连接.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bWFScxBx-1649154398324)(/home/xiaohu/.config/Typora/typora-user-images/image-20200831152004735.png)]
检测半关闭连接
当某端崩溃退出,此时对端并不知道,若往对端发送数据,会响应一个RST复位报文段
若果一方已经关闭或异常终止连接而另一方还不知道,这样的TCP连接称为半打开…
只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测两一方已经出现异常.
close函数会关闭文件描述符,不会立马关闭网络套接字,除非引用计数为0,则会触发调用关闭TCP连接。
struct linger{
int l_onoff; //开启或关闭该选项
int l_linger; //滞留时间
}
shutdown没有采用引用计数的机制,会影响所有进程的网络套接字,可以只关闭套接字的读端或写端,也可全部关闭,用于实现半关闭,会直接发送FIN包
TCP协议保证数据传输可靠性的方式主要有:
校验和:包括首部和数据2部分
发送的数据包的二进制相加然后取反,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。
确认应答+序列号(累计确认+seq)
接收方收到报文就会确认(累积确认:对所有按序接收的数据的确认)
TCP给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层
超时重传
当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段
流量控制
TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。
接收方有即时窗口(滑动窗口),随ACK报文发送
拥塞控制
当网络拥塞时,减少数据的发送。
发送方有拥塞窗口,发送数据前比对接收方发过来的即使窗口,取小
慢启动、拥塞避免、拥塞发送、快速恢复
详见 TCP-IP详解:滑动窗口SlidingWindow和TCP滑动窗口
TCP的滑动窗口用来控制接收方和发送方的发送速率,避免拥塞的发生。滑动窗口其实就是接收端的缓冲区大小,用来告诉发送方对它发送的数据有多大的缓冲空间。在接收方的滑动窗口已知的情况下,当接收方确认了连续的数据序列之后,发送方的滑动窗口向后滑动,发送下一个数据序列。
接收方会在每个ACK数据包中附带自己当前的接受窗口(滑动窗口)的大小,方便发送方进行控制。
流量控制是为了控制发送方发送速率,保证接收方来得及接收。
如果发送方的发送速率太快,会导致接收方处理不过来,这时候接收方只能把处理不过来的数据存在缓存区里(失序的数据包也会被存放在缓存区里)
如果缓存区满了发送方还在疯狂着发送数据,接收方只能把收到的数据包丢掉,大量的丢包会极大着浪费网络资源
接收方每次收到数据包,可以在发送确定报文的时候,同时告诉发送方自己的缓存区还剩余多少是空闲的,我们也把缓存区的剩余大小称之为接收窗口大小,用变量win来表示接收窗口的大小。
发送方收到之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小,当发送方收到接收窗口的大小为0时,发送方就会停止发送数据,防止出现大量丢包情况的发生
发送方何时再继续发送数据?
当发送方收到接受窗口 win = 0 时,这时发送方停止发送报文,并且同时开启一个定时器,每隔一段时间就发个测试报文去询问接收方,打听是否可以继续发送数据了,如果可以,接收方就告诉他此时接受窗口的大小;如果接受窗口大小还是为0,则发送方再次刷新启动定时器。
发送窗口和接受窗口相等吗?
接收方在发送确认报文的时候,会告诉发送发自己的接收窗口大小,而发送方的发送窗口会据此来设置自己的发送窗口,但这并不意味着他们就会相等。首先接收方把确认报文发出去的那一刻,就已经在一边处理堆在自己缓存区的数据了,所以一般情况下接收窗口 >= 发送窗口。
接收方累积确认,发送方超时重传。
拥塞控制:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。
如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。
判断网络拥塞出现超时
慢开始思路:当主机开始发送数据时,并不清楚网络负载情况,如果立即把大量字节注入网络,会引起网络拥塞,较好方法从小到大逐渐增大发送窗口。
发送的最初执行慢开始,逐渐增大拥塞窗口值,令 cwnd = 1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 …
注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能性也就更高。
设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。
如果出现了超时,则令 ssthresh = cwnd / 2,同时拥塞窗口cwnd=1,然后重新执行慢开始。
在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。
快重传:在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。
在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行**快恢复,**令调整门限值ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。
慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。
几种经典的拥塞控制算法:
Vegas:Vegas将时延RTT的增加作为网络出现拥塞的信号,RTT增加,拥塞窗口减小,RTT减小,拥塞窗口增加
Reno:Reno将拥塞控制的过程分为四个阶段:慢启动、拥塞避免、快重传和快恢复,是现有的众多拥塞控制算法的基础
Cubic:Cubic是Linux内核2.6之后的默认TCP拥塞控制算法,使用一个立方函数
BBR:BBR是谷歌在2016年提出的一种新的拥塞控制算法
联系:Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据传输完毕后,Http会立即将TCP连接断开,这个过程是很短的。
区别:HTTP和TCP位于不同的网络分层。TCP是传输层的协议,定义的是数据传输和连接的规范,而HTTP是应用层的,定义的是数据的内容的规范。
建立一个TCP请求需要进行三次握手,而由于http是建立在tcp连接之上的,建立一个http请求通常包含请求和响应两个步骤
开启Keep-Alive的优缺点:
优点:Keep-Alive模式更加高效,因为避免了连接建立和释放的开销。
缺点:长时间的Tcp连接容易导致系统资源无效占用,浪费系统资源。
当保持长连接时,如何判断一次请求已经完成?
Content-Length
Content-Length表示实体内容的长度。浏览器通过这个字段来判断当前请求的数据是否已经全部接收。
所以,当浏览器请求的是一个静态资源时,即服务器能明确知道返回内容的长度时,可以设置Content-Length来控制请求的结束。但当服务器并不知道请求结果的长度时,如一个动态的页面或者数据,Content-Length就无法解决上面的问题,这个时候就需要用到Transfer-Encoding字段。
Transfer-Encoding
Transfer-Encoding是指传输编码,在上面的问题中,当服务端无法知道实体内容的长度时,就可以通过指定Transfer-Encoding: chunked来告知浏览器当前的编码是将数据分成一块一块传递的。当然, 还可以指定Transfer-Encoding: gzip, chunked表明实体内容不仅是gzip压缩的,还是分块传递的。最后,当浏览器接收到一个长度为0的chunked时, 知道当前请求内容已全部接收。
连接建立之后,如果客户端一直不发送数据,或者隔很长时间才发送一次数据,当连接很久没有数据报文传输时如何去确定对方还在线,到底是掉线了还是确实没有数据传输,连接还需不需要保持,这种情况在TCP协议设计中是需要考虑到的。
TCP协议通过一种巧妙的方式去解决这个问题,当超过一段时间之后,TCP自动发送一个数据为空的报文(侦测包)给对方,如果对方回应了这个报文,说明对方还在线,连接可以继续保持,如果对方没有报文返回,并且重试了多次之后则认为链接丢失,没有必要保持连接。
tcp keep-alive是TCP的一种检测TCP连接状况的保鲜机制。tcp keep-alive保鲜定时器,支持三个系统内核配置参数:
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_time = 1800
keepalive是TCP保鲜定时器,当网络两端建立了TCP连接之后,闲置(双方没有任何数据流发送往来)了tcp_keepalive_time后,服务器就会尝试向客户端发送侦测包,来判断TCP连接状况(有可能客户端崩溃、强制关闭了应用、主机不可达等等)。如果没有收到对方的回答(ack包),则会在 tcp_keepalive_intvl后再次尝试发送侦测包,直到收到对方的ack,如果一直没有收到对方的ack,一共会尝试 tcp_keepalive_probes次,每次的间隔时间在这里分别是15s, 30s, 45s, 60s, 75s。如果尝试tcp_keepalive_probes,依然没有收到对方的ack包,则会丢弃该TCP连接。TCP连接默认闲置时间是2小时,一般设置为30分钟足够了。
HTTP 协议老的标准是 HTTP/1.0 ,目前最通用的标准是 HTTP/1.1 。
HTTP1.0 只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接,但是最新的http/1.0加入了长连接,只需要在客户端给服务器发送的http报文头部加入Connection:keep-alive
HTTP 1.1 支持持久连接,默认进行持久连接,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟。
1XX:信息性状态码,表示接收的请求正在处理
100 Continue :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
2XX:表示服务器成功处理了客户端的请求
- 200 OK:表示一切正常,如果非HEAD请求,服务器返回的响应头会有body数据
- 204 No Content:请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
- 206 Partial Content :应用于 HTTP 分块下载或断电续传,表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容
3XX 重定向:表示客户端请求的资源发送了变动,需要客户端用新的 URL 重新发送请求获取资源
301 Moved Permanently表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。
302 Found表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。
301 和 302 都会在响应头里使用字段
Location
,指明后续要 跳转的 URL,浏览器会自动重定向新的 URL。
- 304 Not Modified不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,用于缓存控制。
4xx 类状态码:表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义
400 Bad Request :请求报文中存在语法错误。
401 Unauthorized :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
403 Forbidden :表示服务器禁止访问资源,并不是客户端的请求出错
404 Not Found:表示请求的资源在服务器上不存在或未找到,所以无法提供给客户
5xx
类状态码:表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码
- 500 Internal Server Error :服务器正在执行请求时发生错误。
- 503 Service Unavailable :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求
Host:客户端发送请求时,用来指定服务器的域名
Content-Length 字段:服务器在返回数据时,会有 Content-Length
字段,表明本次回应的数据长度
Connection:常用于客户端要求服务器使用 TCP 持久连接,以便其他请求复用
HTTP/1.1 版本的默认连接都是持久连接,但为了兼容老版本的 HTTP,需要指定
Connection
首部字段的值为Keep-Alive
Connection: keep-alive
一个可以复用的 TCP 连接就建立了,直到客户端或服务器主动关闭连接。但是,这不是标准字段
Content-Type
字段用于服务器回应时,告诉客户端,本次数据是什么格式
Content-Type: text/html; charset=utf-8
上面的类型表明,发送的是网页,而且编码是UTF-8
客户端请求的时候,可以使用 Accept
字段声明自己可以接受哪些数据格式
Accept: */*
Content-Encoding
字段说明数据的压缩方法。表示服务器返回的数据使用了什么压缩格式
Content-Encoding: gzip
表示服务器返回的数据采用了 gzip 方式压缩,告知客户端需要用此方式解压。
客户端在请求时,用 Accept-Encoding
字段说明自己可以接受哪些压缩方法
Accept-Encoding: gzip, deflate
put:由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。
head:和 GET 方法类似,但是不返回报文实体主体部分。主要用于确认 URL 的有效性以及资源更新的日期时间等
DELETE:与 PUT 功能相反,并且同样不带验证机制。
OPTIONS:查询针对请求URL指定的资源支持的方法,会返回 Allow: GET, POST, HEAD, OPTIONS
这样的内容。
CONNECT:要求在与代理服务器通信时建立隧道,实现用隧道协议进行TCP通信。
主要使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。
TRACE:让Web服务器将之前的请求通信环回给客户端的方法
发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。
通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪)。
HTTP的请求方法包括GET,POST,PUT,DELETE四种基本方法。(四种方法中只有POST不是操作幂等性的)
Get 方法的含义是请求从服务器获取资源,这个资源可以是静态的文本、页面、图片视频等,POST方法则是相反操作,它向
URI指定的资源提交数据,数据就放在报文的 body 里
Get是不安全的,很可能你的一些操作会被第三方看到,而Post的所有操作多用户来说是不可见的。
Get传输的数据量小,长度限制为4k,主要是因为它受约于URL长度的限制,而Post可以传输大量的数据,所以我们在传文件的时候会用Post。
浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)但是不同浏览器不一定是产生两个TCP包
post可以传输二进制编码的信息,get的参数一般只支持ASCII
使用场景:
一般对于登录、注册等表单请求,不建议用GET方式请求,一般用POST,因为一些参数信息暴露出来会不安全。另外,对于博客、论坛、数据的上传下载等也最好用POST,因为论坛或者上传下载等前后都可能会产生数据的变化,故用POST。 而一般对于有响应速度要求,并且对信息相对不敏感的,比如查询、搜索等,可以使用GET
1、GET请求会向数据库发索取数据的请求,从而来获取信息,该请求就像数据库的select操作一样,只是用来查询一下数据,不会修改、增加数据,不会影响资源的内容,即该请求不会产生副作用。无论进行多少次操作,结果都是一样的。
2、PUT请求是向服务器端发送数据的,从而改变信息,该请求就像数据库的update操作一样,用来修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同。
3、POST请求同PUT请求类似,都是向服务器端发送数据的,但是该请求会改变数据的种类等资源,就像数据库的insert操作一样,会创建新的内容。几乎目前所有的提交操作都是用POST请求的。
4、DELETE请求顾名思义,就是用来删除某一个资源的,该请求就像数据库的delete操作。
就像前面所讲的一样,既然PUT和POST操作都是向服务器端发送数据的,那么两者有什么区别呢。。。POST主要作用在一个集合资源之上的(url),而PUT主要作用在一个具体资源之上的(url/xxx),通俗一下讲就是,如URL可以在客户端确定,那么可使用PUT,否则用POST。
这里仅展示浏览器解析服务器响应的过程,URL解析和交互的完整过程在(9)
HTTP通信机制是在一次完整的HTTP通信过程中,Web浏览器与Web服务器之间将完成下列7个步骤:
概述
特点
HTTP工作原理
HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。
HTTP 请求/响应的步骤
- 客户端连接到Web服务器
一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.oakcms.cn。- 发送HTTP请求
通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。- 服务器接受请求并返回HTTP响应
Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。- 释放连接TCP连接
若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;- 客户端浏览器解析HTML内容
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
SSL 提供以下三个功能
(1) SSL 服务器鉴别 允许用户证实服务器的身份。具有 SS L 功能的浏览器维持一个表,上面有一些可信赖的认证中心 CA (Certificate Authority)和它们的公钥。
(2) 加密的 SSL 会话 客户和服务器交互的所有数据都在发送方加密,在接收方解密。
(3) SSL 客户鉴别 允许服务器证实客户的身份。
SLL的工作过程:
HTTPS:非对称加密算法(公钥和私钥)交换对称密钥+数字证书验证身份(验证公钥是否是伪造的)+利用对称
https包括非对称加密和对称加密两个阶段,在客户端与服务器建立连接的时候使用非对称加密,连接建立以后使用的是对称加密。
首先进行https请求的前提条件是首先服务器拥有数字证书签发机构(CA)颁发的数字证书,一般为收费
HTTP加密过程:
客户端的对称加密密钥其实是三个随机数的哈希(1. 客户端第一次给服务端发送请求时附带的随机数 2. 服务器返回时的随机数 3. 客户端收到返回时的随机数)
简单来说是验证两个问题:
1、证书是否是信任的有效证书。所谓信任:浏览器内置了信任的根证书,就是看看web服务器的证书是不是这些信任根发的或者信任根的二级证书机构颁发的。所谓有效,就是看看web服务器证书是否在有效期,是否被吊销了。
2、对方是不是上述证书的合法持有者。简单来说证明对方是否持有证书的对应私钥。验证方法两种,一种是对方签个名,我用证书验证签名;另外一种是用证书做个信封,看对方是否能解开。
以上的所有验证,除了验证证书是否吊销需要和CA关联,其他都可以自己完成。验证正式是否吊销可以采用黑名单方式或者OCSP方式。黑名单就是定期从CA下载一个名单列表,里面有吊销的证书序列号,自己在本地比对一下就行。优点是效率高。缺点是不实时。OCSP是实时连接CA去验证,优点是实时,缺点是效率不高。
SSL 安全套接字层协议
TLS 运输层安全协议,在SSL的基础上设计
显然每次请求都经历一次密钥传输过程非常耗时,那怎么达到只传输一次呢?用session就行。
HTTP/1. 一次请求-响应,建立一个连接,用完关闭;每一个请求都要建立一个连接;
HTTP/1.1 非流水线解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞;
HTTP/2多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行;
具体如图:
参考:https://blog.csdn.net/qq_40884473/article/details/78442377
Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的场景下,Token便应运而生
Token是服务端生成的一串字符串,也就是令牌,最大的特点就是随机性,不可预测
Token一般用在两个地方:
token 的身份验证流程:
注意:每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里
基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
实施 Token 验证的方法挺多的,还有一些标准方法,比如 JWT,读作:jot ,表示:JSON Web Tokens 。JWT 标准的 Token 有三个部分:
Token,我们称之为“令牌”,其最大的特点就是随机性,不可预测。一般黑客或软件无法猜测出来。那么,Token 有什么作用?又是什么原理呢?
Token 一般用在两个地方:
防止表单重复提交;
Anti CSRF 攻击(跨站点请求伪造)。
两者在原理上都是通过 session token 来实现的。当客户端请求页面时,服务器会生成一个随机数 Token,并且将 Token 放置到 session 当中,然后将 Token 发给客户端(一般通过构造 hidden 表单)。下次客户端提交请求时,Token 会随着表单一起提交到服务器端。
然后,如果应用于“Anti CSRF攻击”,则服务器端会对 Token 值进行验证,判断是否和session中的Token值相等,若相等,则可以证明请求有效,不是伪造的。不过,如果应用于“防止表单重复提交”,服务器端第一次验证相同过后,会将 session 中的 Token 值更新下,若用户重复提交,第二次的验证判断将失败,因为用户提交的表单中的 Token 没变,但服务器端 session 中 Token 已经改变了。
上面的 session 应用相对安全,但也叫繁琐,同时当多页面多请求时,必须采用多 Token 同时生成的方法,这样占用更多资源,执行效率会降低。因此,也可用 cookie 存储验证信息的方法来代替 session Token。比如,应对“重复提交”时,当第一次提交后便把已经提交的信息写到 cookie 中,当第二次提交时,由于 cookie 已经有提交记录,因此第二次提交会失败。不过,cookie 存储有个致命弱点,如果 cookie 被劫持(XSS 攻击很容易得到用户 cookie),那么又一次 game over,黑客将直接实现 CSRF 攻击。所以,安全和高效相对的,具体问题具体分析吧!
此外,要避免“加 token 但不进行校验”的情况,在 session 中增加了 token,但服务端没有对 token 进行验证,这样根本起不到防范的作用。还需注意的是,对数据库有改动的增、删、改操作,需要加 token 验证,对于查询操作,一定不要加 token,防止攻击者通过查询操作获取 token 进行 CSRF攻击。但并不是这样攻击者就无法获得 token,只是增大攻击成本而已。
HTTP协议作为无状态协议,对于HTTP协议而言,无状态指每次request请求之前是相互独立的,当前请求并不会记录它的上一次请求信息,如何将上下文请求进行关联呢?客户端(不同的浏览器)记录用户的状态通过cookie,服务器端(不同的网站)记录用户的状态通过session。
Cookie总是保存在客户端中,按在客户端中的存储位置,可分为内存Cookie和硬盘Cookie,它是服务器端存放在本地机器中的数据,随每一个请求发送给服务器,由于Cookie在客户端,所以可以编辑伪造,不是十分安全。
工作流程:
session是存放在服务器里的,所以session 里的东西不断增加会增加服务器的负担,我们会把一些重要的东西放在session里,不太重要的放在客户端cookie里
session失效
sessionID的传递方式
不同场景下的session
区别
sessionid是存储在cookie中的,解决方案如下:
Session URL重写,保证在客户端禁用或不支持COOKIE时,仍然可以使用Session
session机制。session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。
当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。 保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于 SEEESIONID。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。
经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。
还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。
比如:
<form name="testform" action="/xxx"> <input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764″> <input type="text"> </form>
URL重写:
http://www.test.com/test;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
HTTP协议首先要和服务器建立TCP连接,这需要三次握手。
请求一个万维网文档的时间
HTTP1.0非持久连接的缺点
HTTP1.1持久连接
持久连接的两种工作方式
非流水线方式:客户在收到前一个响应后才能发出下一个请求。服务器在发送一个对象后,TCP连接处于空闲状态,浪费服务器资源。
流水线方式:不用等待响应,直接发送下一个请求,但接收的时候必须按照顺序接收,如果有一个请求阻塞,则接收会全部阻塞
- 默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
- 流水线是在同一条长连接上发出连续的请求,而不用等待响应返回,这样可以避免连接延迟
[什么是TCP粘包?怎么解决这个问题] (https://blog.csdn.net/weixin_41047704/article/details/85340311 )
TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。
发送端为了将多个发往接收端的包,更加高效的的发给接收端,于是采用了优化算法(Nagle算法),将多次间隔较小、数据量较小的数据,合并成一个数据量大的数据块,然后进行封包。那么这样一来,接收端就必须使用高效科学的拆包机制来分辨这些数据。
发送方原因
TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量),而Nagle算法主要做两件事:
接收方原因
TCP接收到数据包时,并不会马上交到应用层进行处理,或者说应用层并不会立即处理。实际上,TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包
粘包、拆包发生原因
发生TCP粘包或拆包有很多原因,现列出常见的几点:
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭算法
只能在应用层数据协议上,加以控制。通常在制定传输数据时,可以使用如下方法:
TCP为了保证可靠传输并减少额外的开销(每次发包都要验证),采用了基于流的传输,基于流的传输不认为消息是一条一条的
保护消息边界:指传输协议把数据当做一条独立的消息在网上传输,接收端一次只能接受一条独立的消息
UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题
因为TCP为了减少额外开销,采取的是流式传输,所以接收端在一次接收的时候有可能一次接收多个包。而TCP粘包就是发送方的若干个数据包到达接收方的时候粘成了一个包。多个包首尾相接,无法区分。
导致TCP粘包的原因有三方面:
避免粘包的措施:
因为TCP是无边界的流传输,所以需要对TCP进行封包和拆包,确保发送和接收的数据不粘连。
要实现断点续传的功能,通常都需要客户端记录下当前的下载进度,并在需要续传的时候通知服务端本次需要下载的内容片段。
HTTP1.1协议(RFC2616)中定义了断点续传相关的HTTP头 Range和Content-Range字段,一个最简单的断点续传实现大概如下:
客户端下载一个1024K的文件,已经下载了其中512K
网络中断,客户端请求续传,因此需要在HTTP头中申明本次需要续传的片段:Range:bytes=512000-,这个头通知服务端从文件的512K位置开始传输文件
服务端收到断点续传请求,从文件的512K位置开始传输,并且在HTTP头中增加:Content-Range:bytes 512000-/1024000,并且此时服务端返回的HTTP状态码应该是206,而不是200。
数据发送时,将数据从应用层->传输层->网络层->数据链路层,其中传输层是TCP和UDP,网络层是IP协议。
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
从 HTTP/1.1 开始默认是长连接(持久连接)的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 Connection : close;
在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 Connection : Keep-Alive。
对称加密:加密和解密使用的密钥是同一个
公钥密码体制:加密密码和解密密码不同,需要公钥和私钥,公钥用来加密,私钥用来解密
在公钥密码体制中,加密密钥(即公钥) PK 是公开信息,而解密密钥(即私钥或秘钥) SK 是需要保密的。
加密算法 E 和解密算法 D 也都是公开的。虽然秘钥 SK 是由公钥 PK 决定的,但却不能根据 PK 计算出 SK。
数据加密标准 DES 属于常规密钥密码体制,是一种分组密码。
DES 的保密性仅取决于对密钥的保密,而算法是公开的
非对称加密的缺点:
为了应对上面非对称加密带来的问题,我们就引入了数字签名
数字签名必须保证以下三点:
报文鉴别——接收者能够核实发送者对报文的签名;
报文的完整性——发送者事后不能抵赖对报文的签名;
不可否认——接收者不能伪造对报文的签名。
现在已有多种实现各种数字签名的方法。但采用公钥算法更容易实现
使用公钥密码加密的一般流程:通过A的公钥对报文加密,发送给B,然后B拿A的私钥进行解密,得到报文.
注意:并不是每次传输报文的时候都要加数字签名,数字签名一般用于数字证书的验证,这样的话浏览器内置的CA拥有服务端的公钥和私钥。
数字签名
普通数字签名(能核实发送者,但无法保证报文完整性)
密码散列函数
报文摘要数字签名(核实发送者,保证报文完整性)
对报文本身加密可能是个耗时过程,比如这封Email足够大,那么私钥加密整个文件以及拿到文件后的解密无疑是巨大的开销
数字签名的作用
数字证书
明文和数字签名共同组成了数字证书,这样一份数字证书就可以颁发给网站了,由认证中心(CA)或者认证中心的下级认证中心颁发。通俗来说,A确认收到的公钥真的是B的公钥,而不是别人伪造的
详见:https://blog.csdn.net/weixin_39640298/article/details/84555814
SYN Flood攻击指的是一种常见的拒绝服务攻击。这种攻击可能对主机实施,阻止主机处理连接的工作。这里解释一下, TCP是传输控制协议,是大多数数据在互联网上传输的主要手段。为了使用TCP协议打开互联网上的一台主机的连接,在客户机和服务器之间将产生“三次握手"
SYN Flood攻击是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,常用假冒的IP或IP号段发来海量的请求连接的第一个握手包(SYN包),被攻击服务器回应第二个握手包(SYN+ ACK包),因为对方是假冒IP对方永远收不到包且不会回应第三个握手包。导致被攻击服务器保持大量SYN RECV状态的“半连接”,并且会重试默认5次回应第二个握手包,塞满TCP等待连接队列,资源耗尽(CPU满负荷或内存不足) ,让正常的业务请求连接不进来。
当开放了一个TCP端口后,该端口就处于Listening状态,不停地监视发到该端口的SYN报文,一旦接收到Client发来的SYN报文,就需要为该请求分配一个TCB(Transmission Control Block),通常一个TCB至少需要280个字节,在某些操作系统中TCB甚至需要1300个字节,并返回一个SYNACK报文,立即转为SYN-RECEIVED即半开连接状态,而某些操作系统在SOCKT的实现上最多可开启512个半开连接(如Linux2.4.20 内核)。这种过程如下图所示:
从以上过程可以看到,如果恶意的向某个服务器端口发送大量的SYN包,则可以使服务器打开大量的半开连接,分配TCB,从而消耗大量的服务器资源,同时也使得正常的连接请求无法被相应。而攻击发起方的资源消耗相比较可忽略不计。
syn flood防御方法:
SYN Cache技术:
这种技术是在收到SYN数据报文时不急于去分配TCB,而是先回应一个SYN ACK报文,并在一个专用HASH表(Cache)中保存这种半开连接信息,直到收到正确的回应ACK报文再分配TCB。在FreeBSD系统中这种Cache每个半开连接只需使用160字节,远小于TCB所需的736个字节。在发送的SYN ACK中需要使用一个己方的Sequence Number,这个数字不能被对方猜到,否则对于某些稍微智能一点的SYNFlood攻击软件来说,它们在发送SYN报文后会发送一个ACK报文,如果己方的Sequence Number被对方猜测到,则会被其建立起真正的连接。因此一般采用一些加密算法生成难于预测的Sequence Number。
syn cookie/proxy
syn cookie 它的防御对象是syn flood,类别是:DOS攻击方式的防范手段,防范原理:修改TCP服务器端的三次握手协议
syn cookie 是对TCP服务器端的三次握手协议做了一些修改,专门用来防范SYN Flood 攻击的手段。它的原理是:在TCP服务器收到TCP syn包并返回TCP SYN+ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值,在收到TCP ACK包时,TCP 服务器在根据那个cookie值检查这个TCP ACK包的合法性。如果合法,再分配专门的数据区进行处理未来的TCP连接。
XSS 攻击:跨站脚本攻击(Cross Site Scripting),恶意攻击者往 Web 页面里插入恶意 Script 代码,当用户浏览该页之时,嵌入其中 Web 里面的 Script 代码会被执行,从而达到恶意攻击用户的目的。
XSS (Cross Site Scripting),即跨站脚本攻击,是一种常见于 Web 应用中的计算机安全漏洞。恶意攻击者往 Web 页面里嵌入恶意的客户端脚本,当用户浏览此网页时,脚本就会在用户的浏览器上执行,进而达到攻击者的目的。比如获取用户的 Cookie、导航到恶意网站、携带木马等
- XSS 漏洞是由于对用户提交的数据没有经过严格的过滤处理造成的,所以防御的原则就是不相信用户输入的数据,对输入进行过滤,对输出进行编码。
CSRF 攻击:CSRF(Cross-site request forgery)跨站请求伪造,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与 XSS 非常不同,XSS 利用站点内的信任用户,而 CSRF 则通过伪装来自受信任用户的请求来利用受信任的网站。与 XSS 攻击相比,CSRF 攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比 XSS 更具危险性。
CSRF攻击攻击原理及过程如下:
- 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
CSRF是一种夹持用户在已经登陆的web应用程序上执行非本意的操作的攻击方式。相比于XSS,CSRF是利用了系统对页面浏览器的信任,XSS则利用了系统对用户的信任。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j2r05fds-1649154398330)(./pic/CSRF攻击原理图.jpg)]
可以知道构成CSRF攻击是有条件的:
任何一台机器都可以轻松的发送ARP广播,来宣称自己的IP和自己的MAC。这样收到的机器都会在自己的ARP表格中建立一个他的ARP项,记录他的IP和MAC地址。如果这个广播是错误的其他机器也会接受。
ARP欺骗的关键所在,伪造了一个假的网关地址,并进行广播,因为局域网中的主机要进行通信的话,必经过网关,那么若被欺骗后,该主机上发送的数据就会被转发到攻击者的主机上,这时攻击者可对流量进行分析,再把数据转发给真正的网关,那么受害者的数据就会被攻击者所窃取。
ARP 攻击,对内网的PC进行攻击,使内网PC机的ARP列表混乱,导致内网某些PC机无法上网。目前路由器无法抵挡这种病毒的攻击,因为他们的攻击方式是攻击内网的PC机,只有请用户从内网的PC机下手防范。
P2P就是这样的技术,可以使得不同局域网的内网设备可以直连。P2P(peer to peer)点对点技术又称对等互联网络技术,是一种网络新技术,依赖网络中参与者的计算能力和带宽,而不是把依赖都聚集在较少的几台服务器上。
根据客户端的不同,客户端之间进行P2P传输的方法也略有不同,P2P主要有中继、逆向连接、打洞(hole punching)等技术。
中继最可靠但也是最低效的一种P2P通信实现,其原理是通过一个有公网IP的服务器中间人对两个内网客户端的通信数据进行中继和转发,如图 1所示:
当服务端连接的客户端比较少,且网络流量不大时,效果还不错。但是如果有很多客户端连接并且网络流量很多,服务端的压力就会很大。
此种方法在两个端点中有一个不存在中间件(如NAT)的时候有效。例如,Client A在NAT之后而Client B拥有全局IP地址
Client A内网地址为10.0.0.1,使用TCP,端口为1234。A和Server S建立了一个连接,Server的IP地址为18.181.0.31,监听1235端口。NAT A给Client A分配了TCP端口62000,地址为NAT的公网IP地址155.99.25.11,作为Client A对外当前会话的临时IP和端口。因此Server S认为Client A就是155.99.25.11:62000。而Client B由于有公网地址,所以对Server S来说Client B就是138.76.29.7:1234。
当Client B想要主动发起对Client A的P2P连接时,需要指定目的地址及端口为155.99.25.11:62000。由于NAT工作的原理问题,NAT A会拒绝将收到的对Client A的请求转发给Client A。拒绝该请求主要有如下原因:
NAT A没有映射过62000端口,NAT A不知道该请求是给谁的
NAT A映射过62000端口,但是需要首先从Client A发起请求,然后才能转发应答
在直接连接Client A失败之后,Client B可以通过Server S向Client A中继一个连接请求,从而从Client A方向“逆向“地建立起Client A- Client B之间的点对点连接(因为Client A连接到了Server S)。
很多当前的P2P系统都实现了这种技术,但其局限性也是很明显的,只有当其中一方有公网IP时连接才能建立。越来越多的情况下,通信的双方都在NAT之后,因此就要用到打洞技术了
对于UDP打洞,考虑如下的两张场景:
两 个Client 处在两个不同的NAT
两个Client 在同一个NAT
端点处于不同NAT
Client A和Client B的地址都是内网地址,且在不同的NAT后面。Client A、Client B上运行的P2P应用程序和Server S都使用了UDP端口1234,Client A和Client B分别初始化了与Server S的UDP通信
假设Client A打算与Client B直接建立一个UDP通信会话。如果Client A直接给Client B的公网地址138.76.29.7:31000发送UDP数据,NAT B很可能会无视进入的数据(除非是Full Cone NAT),Client B往Client A直接发信息也类似。
为了解决上述问题,在Client A开始给Client B的公网地址发送UDP数据的同时,Client A给Server S发送一个中继请求,要求Client B开始给Client A的公网地址发送UDP信息。Client A往Client B的输出信息会导致NAT A打开一个Client A的内网地址与Client B的外网地址之间的通讯会话,Client B往Client A亦然。当两个方向都打开会话之后,Client A和Client B就能直接通讯,而无须再通过Server S了。
UDP打洞技术有许多有用的性质。一旦一个的P2P连接建立,连接的双方都能反过来作为“引导服务器”来帮助其他中间件后的客户端进行打洞,极大减少了服务器的负载。应用程序不需要知道中间件是什么(如果有的话),因为以上的过程在没有中间件或者有多个中间件的情况下也一样能建立通信链路。
端点处于相同的NAT
如果Client A和Client B正好在同一个NAT(而且可能他们自己并不知道),Client A和Server S建立了一个UDP会话,NAT为此分配了公网端口62000,Client B同样和Server S建立会话,分配到了端口62001
假设Client A和Client B使用了上节介绍的UDP打洞技术来建立P2P通路,那么交互流程就是这样了:
Client A和Client B得到由Server S观测到的对方的公网IP和端口号,然后给对方的地址发送信息
当Client A发送一个UDP数据包给Client B的公网地址,数据包最初有源IP地址和端口地址10.0.0.1:1234和目的地址155.99.25.11:62001
NAT收到包后,将其转换为源155.99.25.11:62000(Client A的公网地址)和目的10.1.1.3:1234,NAT发现目的公网地址和其本身的公网地址相同,此时有两种做法:
如果NAT支持回环传输(NAT允许同一个内网的主机间相互通信),那么直接将数据发给Client B
如果NAT不支持回环传输,有可能将该数据丢弃
因为从内部到达NAT的数据会被“回送”到内网中而不是转发到外网。即便NAT支持回环传输,这种转换和转发在此情况下也是没必要的,且有可能会增加A与B的对话延时和加重NAT的负担。
解决上述问题也很简单:
当Client A和Client B最初通过Server S交换地址信息时,它们包含自身的IP地址和端口号(从自己看),同时也包含从服务器看的自己的地址和端口号
Client 向对方的公网地址及内网地址同时发生数据,并使用第一个成功通信的地址作为对方地址。如果两个Client 在同一个NAT后,发送到对方内网地址的数据最有可能先到达,从而可以建立一条不经过NAT的通信链路
如果两个Client 在不同的NAT之后,发送给对方内网地址的数据包根本就到达不了对方,但仍然可以通过公网地址来建立通路。
值得一提的是,虽然这些数据包通过某种方式验证,但是在不同NAT的情况下完全有可能会导致Client A往Client B发送的信息发送到其他Client A内网网段中无关的结点上去的。
TCP打洞
建立穿越NAT设备的P2P的TCP连接只比UDP复杂一点点,TCP协议的 “打洞”从协议层来看是与UDP的“打洞”过程非常相似的。尽管如此,基于TCP协议的打洞至今为止还没有被很好的理解,这也造成了的对其提供支持的NAT设备不是很多。在NAT设备支持的前提下,基于TCP的“打洞”技术实际上与基于UDP的“打洞”技术一样快捷、可靠。实际上,只要NAT设备支持的话,基于TCP的P2P技术的健壮性将比基于UDP技术的更强一些,因为TCP协议的状态机给出了一种标准的方法来精确的获取某个TCP session的生命期,而UDP协议则无法做到这一点。
套接字和TCP端口的重用
实现基于TCP协议的P2P打洞过程中,最主要的问题不是来自于TCP协议,而是来自于应用程序的API接口。这是由于标准的伯克利(Berkeley)套接字的API是围绕着构建Client /服务器程序而设计的,API允许TCP套接字通过调用connect()函数来建立向外的连接,或者通过listen()和accept函数接受来自外部的连接,但是,API不提供类似UDP那样的,同一个端口既可以向外连接,又能够接受来自外部的连接。而且更糟的是,TCP的套接字通常仅允许建立1对1的响应,即应用程序在将一个套接字绑定到本地的一个端口以后,任何试图将第二个套接字绑定到该端口的操作都会失败。
为了让TCP打洞能够顺利工作,我们需要使用一个本地的TCP端口来监听来自外部的TCP连接,同时建立多个向外的TCP连接(通过SO_REUSEADDR、SO_REUSEADDR)。
打开P2P的TCP流
假定Client A希望建立与Client B的TCP连接(Client A和Client B已经与公网上的服务器建立了TCP连接),步骤为:
Client A使用其与服务器的连接向服务器发送请求,要求服务器协助其连接Client B;
服务器将Client B的公网和内网的TCP地址的二元组信息返回给A,同时,服务器将Client A的公网和内网的地址二元组也发送给Client B;
Client A和Client B使用连接服务器的端口异步地发起向对方的公网、内网地址二元组的TCP连接,同时监听各自的本地TCP端口是否有外部的连接联入;
Client A和Client B开始等待向外的连接是否成功,检查是否有新连接联入。如果向外的连接由于某种网络错误而失败,如:“连接被重置”或者“节点无法访问”,Client 只需要延迟一段时间(例如延迟一秒钟),然后重新发起连接即可,延迟的时间和重复连接的次数可以由应用程序编写者来确定;
TCP连接建立起来以后,Client 之间应该开始鉴权操作,确保目前联入的连接就是所希望的连接。如果鉴权失败,Client 将关闭连接,并且继续等待新的连接联入。Client 通常采用“先入为主”的策略,只接受第一个通过鉴权操作的Client ,然后将进入P2P通信过程不再继续等待是否有新的连接联入。
与UDP不同的是,因为使用UDP协议的每个Client 只需要一个套接字即可完成与服务器的通信,而TCP Client 必须处理多个套接字绑定到同一个本地TCP端口的问题。现在来看实际中常见的一种情景,A与B分别位于不同的NAT设备后面,如图 3所示,并且假定图中的端口号是TCP协议的端口号,而不是UDP的端口号。图中向外的连接代表Client A和Client B向对方的内网地址二元组发起的连接,这些连接或许会失败或者无法连接到对方。如同使用UDP协议进行“打洞”操作遇到的问题一样,TCP的“打洞”操作也会遇到内网的IP与“伪”公网IP重复造成连接失败或者错误连接之类的问题。
Client 向彼此公网地址二元组发起连接的操作,会使得各自的NAT设备打开新的“洞”允许Client A与Client B的TCP数据通过。如果NAT设备支持TCP“打洞”操作的话,一个在Client 之间的基于TCP协议的流通道就会自动建立起来。如果Client A向Client B发送的第一个SYN包发到了Client B的NAT设备,而Client B在此前没有向Client A发送SYN包,Client B的NAT设备会丢弃这个包,这会引起A的“连接失败”或“无法连接”问题。而此时,由于Client A已经向Client B发送过SYN包,Client B发往Client A的SYN包将被看作是由Client A发往Client B的包的回应的一部分,所以Client B发往Client A的SYN包会顺利地通过Client A的NAT设备,到达Client A,从而建立起Client A与Client B的P2P连接。
TCP同时打开
有一种特殊的TCP P2P打洞场景:假定各终端的TCP连接启动时间比较巧合,使得他们各自发送的 SYN报文,在到达对方的NAT设备之前,对方的SYN报文都已经 穿越NAT设备,并在NAT设备上生成TCP映射关系。在这种“幸 运”的情况下,NAT设备不会拒绝SYN报文,双方的SYN报文都 能通过终端间的NAT设备,到达对方。此时,终端会发现TCP连 接同时打开,每个终端的TCP返回SYN-ACK报文,报文的SYN 部分必须与之前发送的SYN报文一样,而ACK部分告知对方到达的SYN信息。
为什么这种方式可以建立TCP连接呢?这是因为TCP三次握手中有一种特殊的场景:通信双方同时在相同端口打开连接的场景,如图 6所示:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。