TCP/IP按照层次划分,可以分为以下四层 :
①网络接口层:对应物理层和数据链路层
②网络层
③传输层
④应用层:包括会话层、表示层、应用层
各层举例如下表所示:
网络接口层 | 控制操作系统,硬件设备驱动.网络适配器等等硬件设备 |
---|---|
网络层 | IP |
传输层 | TCP(传输控制协议),UDP(用户数据报协议) |
应用层 | FTP(文件传输协议),DNS(域名系统),HTTP(超文本传输协议) |
物理层 : 在物理信道中实现原始的比特流的传输(物理层协议例如以太网,IEEE 808.2等等)
数据链路层 : 实现将数据帧(将网络层的数据封装成数据帧),从一个节点传送到另一个节点
网络层 : 实现将数据分组从源站通过网络传送到目的站,即我们说的网络上一台主机与另一台主机的数据传输,其中IP,ARP协议即为网络层协议
传输层 : 实现源端到目的端数据的传输,即某主机的某个进程与另一个主机的某一个进程之间的数据传输,TCP,UDP
应用层 : 是用户能够访问网络,为各类应用提供相应的服务,提供各种用户接口支持服务
在七层模型中,还多出来了两层 :
表示层 : 确保各种通信设备能够互相操作,不及考虑其数据的内部表示。即确保即使各种通信设备其数据的内部表示不同,但仍然能相互正确操作。(SSL等协议)
会话层:实现在不同机器上用户建立、维护和终止会话关系。即会话层对会话提供控制管理服务、会话同步服务等。(ZIP, ASP, SSH 等协议)
TCP和UDP是TCP/IP体系结构运输层中的两个重要协议
TCP(Transmission Control Protocol):传输控制协议
UDP(User Datagram Protocol):用户数据报协议
(1)TCP:面向有连接,可靠的,速度慢,效率低。
(2)UDP:面向无连接, 不可靠,速度快,效率高。
当进程需要传输可靠的数据时应使用TCP,当进程需要高效传输数据,可以忽略可靠性时应使用UDP协议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2M2v5aZL-1632465301638)(.\imgs\TCP和UDP的区别.jpg)]
注意 : UDP的不可靠性就在于它的面向无连接的,不需要建立连接,就可以发送数据,即UDP只会把想发的数据报文一股脑的丢给对方,并不在意数据有无安全完整到达.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-adxLq68d-1632465301641)(.\imgs\UDP.gif)]
因为udp是面向无连接的,不能保证传输的可靠性,此时我们只能在应用层实现可靠传输
目前有如下开源程序利用udp实现了可靠的数据传输。分别为RUDP、RTP、UDT
RUDP
RUDP 提供一组数据服务质量增强机制,如拥塞控制的改进、重发机制及淡化服务器算法等,从而在包丢失和网络拥塞的情况下, RTP 客户机(实时位置)面前呈现的就是一个高质量的 RTP 流。在不干扰协议的实时特性的同时,可靠 UDP 的拥塞控制机制允许 TCP 方式下的流控制行为。
RTP
实时传输协议(RTP)为数据提供了具有实时特征的端对端传送服务,如在组播或单播网络服务下的交互式视频音频或模拟数据。应用程序通常在 UDP 上运行 RTP 以便使用其多路结点和校验服务;这两种协议都提供了传输层协议的功能。但是 RTP 可以与其它适合的底层网络或传输协议一起使用。如果底层网络提供组播方式,那么 RTP 可以使用该组播表传输数据到多个目的地。
RTP 本身并没有提供按时发送机制或其它服务质量(QoS)保证,它依赖于底层服务去实现这一过程。 RTP 并不保证传送或防止无序传送,也不确定底层网络的可靠性。 RTP 实行有序传送, RTP 中的序列号允许接收方重组发送方的包序列,同时序列号也能用于决定适当的包位置,例如:在视频解码中,就不需要顺序解码。
UDT
基于UDP的数据传输协议(UDP-basedData Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等
流量控制:让发送方的发送速率不要太快,要让接收方来得及接收
利用滑动窗口机制可以很方便地在TCP连接上实现对发送方的流量控制
假设A发送的每个TCP数据报文段可携带100字节数据;
在A和B建立连接时,B告诉A:我的接收窗口为400;
于是,A将自己的发送窗口也设置为400,也就是能发送1-400的数据
当B又有存储空间时,会向A发送窗口值,则A又可以继续发送数据了
但是如果B发送的这个窗口值丢失,就会造成死锁
解决:
当A收到的窗口值为0时就开启一个持续计时器,当持续计时器超时,会向B发送一个零窗口探测报文
如果B返回的窗口还是0,A就重新启动持续计时器
零窗口探测报文也会丢失,解决:零窗口探测报文也存在一个重传计时器
在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络性能就要变坏,这种情况就叫做网络拥塞.
若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络性能就会变坏,这种情况叫网络拥塞。
而我们所说的TCP的拥塞控制和流量控制的原理是相关的 :
流量控制在数据链路层对一条通信路径上的流量进行控制,其的是保证发送者的发送速度不超过接收者的接收速度,它只涉及一全发送者和一个接收者,是局部控制。
拥塞控制是对整个通信子网的流量进行控制,其目的是保证通信子网中的流量与其资源相匹配,使子网不会出现性能下降和恶化、甚至崩溃,是全局控制。
开环控制: 开环控制的思想是通过良好的设计避免拥塞问题的出现,确保拥塞问题在开始时就不可能发生。开环控制方法包括何时接受新的通信何时丢弃包、丢弃哪些包。其特点是在作出决定时不考虑网络当前的状态
闭环控制: 闭环控制的思想是反馈控制。即通过将网络工作的动态信息反馈给网络中节点的有关进程,节点根据网络当前的动态信息,调整转发数据包的策略。闭环控制过程包括三部分:
① 监视系统 检测网络发生或将要发生拥塞的时间和地点
② 报告 将监视中检测到的信息传送到可以进行拥塞控制的节点
③ 决策 调整系统的操作行为,以解决问题
发送方维护一个拥塞窗口的状态变量cwnd,其值取决于网络的拥塞程度,且动态变化。发送方将拥塞窗口作为发送窗口swnd = cwnd。发送方维护一个慢开始门限ssthresh状态变量。
拥塞窗口的维护原则:只要网络没有出现拥塞,拥塞窗口就增大;出现拥塞,拥塞窗口就减小。
判断是否出现网络拥塞的依据:判断有没有按时收到确认报文。
cwnd < ssthresh慢开始
cwnd >=ssthresh拥塞控制
初始拥塞窗口cwnd=1,发送方只能发送1个数据报文(cwnd是几,就发送几个),接收方收到报文,发送1个确认报文;
拥塞窗口变为cwnd=2,发送方发送2个数据报文,接收方收到报文,发送2个确认报文;
拥塞窗口cwnd=4。发送方发送4个报文数据,接收方收到报文,发送4个确认报文;
拥塞窗口cwnd=8 …
…
拥塞窗口cwnd=16(cwnd按指数增长)
当拥塞窗口值>=慢开始门限时,改为拥塞控制算法
拥塞控制:cwnd只能每次线性+1。
拥塞窗口cwnd=16。发送方发送16个报文数据,接收方收到报文,发送1个确认报文;
拥塞窗口cwnd=17。发送方发送17个报文数据,接收方收到报文,发送1个确认报文;
拥塞窗口cwnd=18。发送方发送18个报文数据,接收方收到报文,发送1个确认报文;
…
拥塞窗口cwnd=18(cwnd线性增长)
如果发送方发送了24个报文,但是只接收到了20个确认报文,丢失的4个报文的重传计时器超时了,
发送方判断出现拥塞,重置ssthresh=cwnd/2;cwnd=1
快重传-快恢复:使发送方尽快重传,而不是等重传计时器超时再重传。
(发送M1,确认M1;发送M2,确认M2;发送M3,M3丢失;发送M4,确认M2;发送M5,确认M2;发送M6,确认M2;)收到3个重复的确认,立即重传。
快恢复:ssthresh=cwnd/2;cwnd=ssthren(丢失了3个报文段,拥塞窗口可以不置为1)
所谓三次握手,是指建立一个TCP连接时,需要客户端和服务器总共发送 3个包
三次握手的目的是: 客户端连接服务器到指定的端口,建立TCP连接.并同步连接双方的序列号和确认号并交换TCP窗口大小信息.
在Socket编程中,客户端执行connect()时,将触发三次握手
首先我们需要知道以下四个概念:
序列号seq: 占4个字节,用来标记数据段的顺序,就是这个报文段中的第一个字节的数据编号
确认号ack: 占4个字节,期待收到对方下一个报文段的第一个数据字节的序号
同步SYN: 连接建立时用于同步序号。
当SYN=1,ACK=0时表示:这是一个连接请求报文段。
若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。
SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。
确认ACK: 占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效
滞留在网络中的请求到达服务器,会导致服务器给客服发送请求,并进入已建立连接状态;
服务器不理睬该请求,仍为关闭状态
服务器处于已建立连接状态会等待客户端的请求,造成服务器资源浪费
客户端调用connect的时候,就是发一个syn包
服务端accept的时候,实际上是从内核的accept队列里面取一个连接,如果这个队列为空,则进程阻塞(阻塞模式下)。如果accept返回则说明成功取到一个连接,返回到应用层。
大致的过程是客户端发一个syn之后,服务端将这个连接放入到backlog队列,在收到客户端的ack之后将这个请求移到accept队列。所以accept一定是发生在三次握手之后,connect只是发一个syn而已
Accept根本不参与三次握手,服务器只要Listen,客户端connect是与服务器内核握手,connect完成之后,服务器都不需要写Accept,客户端就已经可以发送数据了,你完全可以让这批数据在服务器里躺一年之后,再Accept也可以
tcp三次握手的过程,accept发生在三次握手哪个阶段?
第一次握手:客户端发送syn包(syn=j)到服务器。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个ASK包(ask=k)。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1)。
三次握手完成后,客户端和服务器就建立了tcp连接。这时可以调用accept函数获得此连接
TCP通信双方都可以释放连接
TCP的连接的拆除需要发送四个包,因此称为四次挥手。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
以第一次挥手为例各个标志位的含义:
FIN:表明这是一个TCP连接释放报文段
ACK:对之前接收的报文段进行确认
ack:服务端传送过来的字节的最后一个字节序号+1
seq:客户端发送字节的最后一个字节序号+1
客户端时间等待状态是否有必要?
如果客户端最后一次挥手发送过程中丢失,会导致服务器一直重传TCP连接释放,无法进入关闭状态
添加等待状态可以确保服务器收到最后一个TCP确认报文段而进入关闭状态,另外,客户端在发完最后一个TCP确认报文段后再经过2MLS,可以使本次连接持续时间内所产生的所有报文段都从网络中消失,使下一个TCP连接中不会出现旧连接中的报文段。
如果客户端出现故障,客户端如何发现?
TCP服务器每次收到一次TCP客户端进程的数据,就重新设置并启动保活计时器(2h),若保活周期内未收到TCP客户端的数据,TCP服务器就像TCP客户端发送探测报文段,以后每75s发送一次,若一脸发送10个仍没收到TCP客户端的响应,TCP服务端就认为TCP客户端出现了故障,接着就关闭这个链接。
当客户端最后一次发送消息时并没有直接进入close状态而是进入TIME_WAIT状态,这是因为TCP是面向连接的协议,每一次发送都需要确认对方是否收到消息。客户端最后一次发送消息时可能会由于网络等其他原因导致服务器收不到消息,服务器就会选择从新给客户端发送一个FIN的包,如果客户端处于关闭状态将永远也收不到服务器发给它的消息了。至于这个时间要等多久才能确认对方收到了消息呢。
报文在网络中有一个最大生存时间MSL超过这个时间就会被丢弃并通知源主机。
TIME_WAIT 要等待 2MSL 才会进入 CLOSED 状态。
ACK包到达服务器需要MSL时间,服务器重传 FIN 包也需要MSL时间,2MSL 是数据包往返的最大时间,通常为2min,如果2MSL后还未收到服务器重传的FIN 包,就说明服务器已经收到了ACK包,此时由TIME_WAIT状态转变为CLOSED状态.
TCP的keepalive的来源
双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启等各种意外,当这些意外发生之后,这些TCP连接并未来得及正常释放,那么,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用TCP的保活报文来实现。
TCP的keepalive的作用
利用保活探测功能,可以探知这种对端的意外情况,从而保证在意外发生时,可以释放半打开的TCP连接。
三次握手状态:
(1) 第一次握手[同步已发送状态,SYN-SENT]
(2) 第二次握手[同步收到状态,SYN-REVD]
(3) 第三次握手[已建立连接状态,ESTABLISHED]
四次挥手状态:
(1) FIN-WAIT-1(终止等待1)状态[客户端]
(2) CLOSE-WAIT(关闭等待)状态[服务端]
(3) FIN-WAIT-2(终止等待2)状态[客户端]
(4) LAST-ACK(最后确认)状态[服务端]
(5) TIME-WAIT(时间等待)状态[客户端] 这个是考点!!!
(6) CLOSED状态[服务端]
TCP连接为什么是三次的问题?
TCP协议为了实现可靠传输,通信双方需要判断自己已经发送的数据包是否都被接收方收到, 如果没收到, 就需要重发。 为了实现这个需求,很自然地就会引出序号(sequence number) 和 确认号(acknowledgement number) 的使用。
故为了可靠传输,发送方和接收方始终需要同步SYN序号,不仅客户端向服务器端发送SYN包请求连接,并且服务器端发送ACK包(确认包)同意请求,也需要向客户端发送SYN包请求连接,并得到客户端的ACK包(确认包),只有双方都发送并接收响应,才算连接成功!
TCP断开连接为什么是四次的问题?
TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。
TCP是全双工模式,这就意味着,当客户端发出
FIN
报文段给服务器端时,只是表示客户端已经没有数据要发送了,即表示它的数据已经全部发送完毕了;但是,这个时候客户端还是可以接受来自服务器端的数据的;当服务器端返回ACK
报文段时,表示它已经知道了客户端没有数据发送了,但是服务器还是可以发送数据到客户端的;当服务器也发送了FIN
报文段时,这个时候就表示客户端也没有数据要发送了,就等于告诉我也没有数据要发送了,之后等到客户端返回ACK
报文段的时候,彼此才会真正的,愉快的中断这次TCP连接。
我们说在建立TCP连接的过程中,服务器发送SYN-ACK包之后,收到客户端的ACK包之前的状态叫做半连接状态,此时服务器处于SYN-RECV状态,当服务器收到ACK包后,称为ESTABLISHED状态
**SYN攻击指的是攻击客户端,在短时间内伪造出大量的不存在的IP地址,向服务器端进行发送SYN包,此时服务器回复确认包(ACK),并等待客户端的确认,由于很大的IP地址是不存在的,导致服务器需要不断地进行重复发送直至超时!**而这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者将引起网络堵塞甚至系统瘫痪.
解决方案:
在Linux系统下可以如下命令检测是否被Syn攻击
netstat -n -p TCP | grep SYN_RECV
一般较新的TCP/IP协议栈都对这一过程进行修正来防范Syn攻击,修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等等,但是都不能完全地防范SYN攻击.
首先我们需要知道几个概念:
第一次握手:建立连接时,客户端发送SYN包(seq=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到SYN包,将SYN和ACK标识都置为1,然后设置(seq=y,ack=x+1),向客户端发送一个SYN+ACK包,此时服务器进入SYN_RECV状态.
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包,将ACK置为1,S此时无需SYN,(seq=x+1,ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
所以 :
这题中,第三次的seq = x + 1 = 2000,ack = y + 1 = 1000,说明第二次的seq = y = 1999,ack = x + 1 = 1000
TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。
(1) 发送方原因
TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量),而Nagle算法主要做两件事:
- 只有上一个分组得到确认,才会发送下一个分组
- 收集多个小分组,在一个确认到来时一起发送
Nagle算法造成了发送方可能会出现粘包问题
(2) 接收方原因
TCP接收到数据包时,并不会马上交到应用层进行处理,或者说应用层并不会立即处理。实际上,TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包。
(1)发送方
对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭算法。
(2)接收方
接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。
(2)应用层
应用层的解决办法简单可行,不仅能解决接收方的粘包问题,还可以解决发送方的粘包问题。
解决办法:循环处理,应用程序从接收缓存中读取分组时,读完一条数据,就应该循环读取下一条数据,直到所有数据都被处理完成,但是如何判断每条数据的长度呢?
TCP为了保证可靠传输并减少额外的开销(每次发包都要验证),采用了基于流的传输,基于流的传输不认为消息是一条一条的,是无保护消息边界的(保护消息边界:指传输协议把数据当做一条独立的消息在网上传输,接收端一次只能接受一条独立的消息)。
UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。
举个例子:有三个数据包,大小分别为2k、4k、6k,如果采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,但是使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕。
XSS攻击的核心原理是:不需要你做任何的登录认证,它会通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等)
最后导致的结果可能是:盗用Cookie破坏页面的正常结构,插入广告等恶意内容D-doss攻击
(1)服务器发送给客户端一个token;
(2)客户端提交的表单中带着这个token。
(3)如果这个 token 不合法,那么服务器拒绝这个请求。
隐藏令牌:把 token 隐藏在 http 的 head头中。方法二和方法一有点像,本质上没有太大区别,只是使用方式上有区别。
Referer 验证:Referer 指的是页面请求来源。意思是,只接受本站的请求,服务器才做响应;如果不是,就拦截。
简单的理解,SQL注入就是我们将SQL命令插入到WEB表单或者是页面请求url的请求字符串,最终达到欺骗服务器的执行恶意的SQL命令的目的。
总结来说,SQL注入就是指通过构建特殊的输入作为参数传入WEB应用程序,而这些输入大多数情况下都是SQL语法中的一些组合,通过执行SQL语句进而执行了攻击者携带的操作。
导致SQL注入的主要原因是没有细致地过滤用户输入的数据,致使非法数据侵入系统
案例 : 我们在用户登录的时候,需要输入用户名和密码进行登录,此时如果我们没有防范SQL注入,在用户登录信息中输入以下信息,将会免密登录,这导致对我们的系统造成了极大的不安全性!
用户名 :or 1 = 1 –
密码 :
首先我们来说一下后台验证用户名和密码的SQL语句为 :
select * form user_table where username = 'username' and password = 'password';
此时我们输入上面的用户名和密码以后 :
select * form user_table where username = '' or 1 = 1 -- and password = 'password';
这里就会出现问题了:
我们说在SQL语句中, – 表示注释,此时 1 = 1为true,前面用 ‘or‘进行连接,则该条件一定成立,故我们登陆成功
此时如果我们注入的语句不是登陆,而是删除数据表结构,那如果没有对SQ注入进行防范,这样的危害是及其大的!!!
1. 对于普通用户和系统管理员用户的权限进行严格的划分
如果是对数据库的一些删除,建立等操作,我们需要系统管理用户才能进行操作,即我们在设计数据库时,把系统管理员的用户与普通用户区分开来。
2.强迫使用参数化语句
如果在编写SQL语句的时候,用户输入的变量不是直接嵌入到SQL语句。而是通过参数来传递这个变量的话,那么就可以有效的防治SQL注入式攻击
3. 加强对用户输入的验证
加强对用户输入内容的检查与验证
4. 使用预编译(PreStatement)
PreparedStatement会对SQL进行了预编译,在第一次执行SQL前数据库会进行分析、编译和优化,同时执行计划同样会被缓存起来,它允许数据库做参数化查询。在使用参数化查询的情况下,数据库不会将参数的内容视为SQL执行的一部分,而是作为一个字段的属性值来处理,这样就算参数中包含破环性语句(or ‘1=1’),也只能作为参数,而不能作为SQL语句内容,故不会被执行。
大部分的SQL框架,例如: Hibernate,Mybatis,JPA等都支持预编译,故能够防范SQL注入问题
HTTP是超文本传输协议的缩写,它是用来传送WWW方式的数据,按层次划分,它属于应用层.HTTP协议采用了请求/响应模型.
TCP是指传输控制协议,是一种面向连接的,可靠的,基于字节流的传输层通信协议. 其中用户数据报协议(UDP)也是传输层另一种重要的传输协议(后面会进行比较) ,他属于传输层。
IP指的是网络之间互连的协议,也就是为计算机网络相互连接进行通信而设计的协议,它属于网络层.
如今的IP网络使用32位地址,以点分十进制表示,如192.168.0.1
地址格式为:IP地址 = 网络地址+主机地址 或 IP地址 = 网络地址 + 子网地址 + 主机地址
一个HTTP请求报文由四个部分组成 : 请求行、请求头部、空行和请求数据(请求体)
一个HTTP响应报文也由四个部分组成:状态行、响应头部,空行和响应体
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。
简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTP协议安全
总结两者主要的区别如下所示:
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
1. 长连接(Persistent Connection)
HTTP1.1支持长连接和请求的流水线处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启长连接keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。HTTP1.0需要使用keep-alive参数来告知服务器端要建立一个长连接。
2. 节约带宽
HTTP1.0中存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能。HTTP1.1支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,客户端接收到100才开始把请求body发送到服务器;如果返回401,客户端就可以不用发送请求body了节约了带宽。
3. HOST域
在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname),HTTP1.0没有host域。随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都支持host域,且请求消息中如果没有host域会报告一个错误(400 Bad Request)。
4. 缓存处理
在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
5. 错误通知的管理
在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
虽然说非对称加密安全,但是由于和对称加密比较来说,速度较慢(指的是加密和解密的速度而言),故我们https采用了将对称加密和非对称加密结合的方式,即我们将对称加密的秘钥使用非对称加密的公钥进行加密,然后发送出去,接收方使用非对称的私钥进行解密得到对称加密的秘钥,此时双方就可以使用对称加密的秘钥进行通信沟通了,这样既保证了安全性,又增加了通信的速度,即加密和解密的速度.
对称加密:快速简单,加密和解密都使用同样的秘钥。
非对称加密:安全,使用一对密钥,公钥/私钥。私钥由一方安全保管,公钥可以发给任何请求他的人,发送请求时通过公钥对消息进行加密,只有拥有私钥的人才能对你的消息进行解密,安全性大大提高。
SSL加密过程:
1.客户端向服务端发送请求
2.服务端返回给客户端公钥
3.客户端使用公钥对信息加密后在想服务端发送请求
4.服务端通过私钥对信息解密
RSA加密算法:
一种公钥密码体制,公钥公开,私钥保密,它的加密解密算法是公开的。 RSA的这一对公钥、私钥都可以用来加密和解密,并且一方加密的内容可以由并且只能由对方进行解密。
数字签名
为了防止证书颁发过程中被人修改,出现了数字签名。
CA机构将证书内容用hash算法生成hash字符串1,并用CA私钥加密发送给服务端
服务端用CA公钥解密得到hash字符串1,同时也将证书内容用hash算法生成hash字符串2,如果hash字符串1与hash字符串2相同,说明证书没被修改过
1.多路复用
HTTP2.0使用了多路复用的技术,做到**同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。**HTTP1.1也可以多建立几个TCP连接,来支持处理更多并发的请求,但是创建TCP连接本身也是有开销的。
2. 头部数据压缩
HTTP1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快。
3. 服务器推送
服务端推送是一种在客户端请求之前发送数据的机制。网页使用了许多资源:HTML、样式表、脚本、图片等等。在HTTP1.1中这些资源每一个都必须明确地请求。这是一个很慢的过程。浏览器从获取HTML开始,然后在它解析和评估页面的时候,增量地获取更多的资源。因为服务器必须等待浏览器做每一个请求,网络经常是空闲的和未充分使用的.
为了改善延迟,HTTP2.0引入了server push,它允许服务端推送资源给浏览器,在浏览器明确地请求之前,免得客户端再次创建连接发送请求到服务器端获取。这样客户端可以直接从本地加载这些资源,不用再通过网络。
下面这张图是由TMX总结 :
超文本传输协议,基于TCP/IP通信协议来传递数据
HTTP/0.9是第一个版本的HTTP协议,已过时。它的组成极其简单,只允许客户端发送GET这一种请求,且不支持请求头。由于没有协议头,造成了HTTP/0.9协议只支持一种内容,即纯文本。
HTTP/0.9具有典型的无状态性,每个事务独立进行处理,事务结束时就释放这个连接。由此可见,HTTP协议的无状态特点在其第一个版本0.9中已经成型。一次HTTP/0.9的传输首先要建立一个由客户端到Web服务器的TCP连接,由客户端发起一个请求,然后由Web服务器返回页面内容,然后连接会关闭。如果请求的页面不存在,也不会返回任何错误码。
相对于HTTP/0.9增加了如下主要特性:
开始支持客户端通过POST方法向Web服务器提交数据,支持GET、HEAD、POST方法
请求与响应支持头域
响应对象不只限于超文本
响应对象以一个响应状态行开始
支持长连接(但默认还是使用短连接),缓存机制,以及身份认证
是目前使用最广泛的协议版本。相对于HTTP/1.0新增了以下内容:
HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection:keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。
提供了范围请求功能,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。这是支持文件断点续传的基础。
提供了虚拟主机的功能,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。
HTTP/1.1在1.0的基础上加入了一些cache的新特性,引入了实体标签,一般被称为e-tags,新增更为强大的Cache-Control头。
在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
相对于HTTP/1.1新增了以下内容:
HTTP 2.0 的所有帧都采用二进制编码
帧:客户端与服务器通过交换帧来通信,帧是基于这个新协议通信的最小单位。
消息:是指逻辑上的 HTTP 消息,比如请求、响应等,由一或多个帧组成。
流:流是连接中的一个虚拟信道,可以承载双向的消息;每个流都有一个唯一的整数标识符(1、2 … N);
多路复用允许同时通过单一的HTTP/2.0 连接发起多重的请求-响应消息。有了新的分帧机制后,HTTP/2.0不再依赖多个TCP 连接去处理更多并发的请求。每个数据流都拆分成很多互不依赖的帧,而这些帧可以交错(乱序发送),还可以分优先级。最后再在另一端根据每个帧首部的流标识符把它们重新组合起来。HTTP 2.0 连接都是持久化的,而且客户端与服务器之间也只需要一个连接(每个域名一个连接)即可。
HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。HTTP/2.0 要求通讯双方各自缓存一份首部字段表,从而避免了重复传输。
浏览器可以在发现资源时立即分派请求,指定每个流的优先级,**让服务器决定最优的响应次序。**这样请求就不必排队了,既节省了时间,也最大限度地利用了每个连接。
把客户端所需要的资源伴随着index.html一起发送到客户端,省去了客户端重复请求的步骤。正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。
快速UDP互联网连接
2个主要特征:
基于TCP的HTTP/2,尽管从逻辑上来说,不同的流之间相互独立,不会相互影响,但在实际传输方面,数据还是要一帧一帧的发送和接收**,一旦某一个流的数据有丢包,则同样会阻塞在它之后传输的流数据传输**。而基于UDP的QUIC协议则可以更为彻底地解决这样的问题,让不同的流之间真正的实现相互独立传输,互不干扰。
当前移动端的应用环境,用户的网络可能会经常切换,比如从办公室或家里出门,WiFi断开,网络切换为3G或4G。基于TCP的协议,由于切换网络之后,IP会改变,因而之前的连接不可能继续保持。而基于UDP的QUIC协议,则可以内建与TCP中不同的连接标识方法,从而在网络完成切换之后,恢复之前与服务器的连接。
get参数包含在URL中,长度有限制,会保留在历史记录里,post通过request body传递参数,长度没有限制,不会保留
回退时,get请求会从缓存中读取数据,而post会重新发送请求
get请求的URL地址可以被收藏,post不可以
get请求只能进行url编码,post支持多种编码方式
GET和POST本质上没有区别
HTTP是基于TCP/IP的关于数据如何在万维网中如何通信的协议。HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。不同的浏览器(发起http请求)和服务器(接受http请求)就是不同的运输公司,他们对get/post请求的数据处理不同。
GET和POST还有一个重大区别
GET产生一个TCP数据包;POST产生两个TCP数据包。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
状态码 | 类别 | 原因短语 |
---|---|---|
1XX | Informational(信息性状态码,服务器给客户端的信息) | 接收的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器无法进行处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
状态码 | 描述 |
---|---|
200 OK | 请求已正常处理 |
301 Moved Permanently | 永久性重定向 : 请求的资源已经被分配了新的URI,以后应使用资源现在所指的URI |
302 Found | 临时性重定向 : 和301相似,但302代表的资源不是永久性移动,只是临时性性质的。换句话说,已移动的资源对应的URI将来还有可能发生改变 |
303 See Other | 303状态码和302状态码有着相同的功能,但303状态码明确表示客户端应当采用GET方法获取资源,这点与302状态码有区别 |
304 Not Modified | 该状态码表示客户端发送附带条件的请求时(采用GET方法的请求报文中包含If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since中任一首部)服务端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回304.。 |
400 Bad Request | 服务器端无法理解客户端发送的请求,请求报文中可能存在语法错误((前端提交到后台的数据应该是json字符串类型,但是前端没有将对象JSON.stringify转化成字符串)) |
401 Unauthorized | 该状态码表示发送的请求需要有通过HTTP认证(BASIC认证,DIGEST认证)的认证信息 |
403 Forbidden | 不允许访问那个资源。该状态码表明对请求资源的访问被服务器拒绝了。(权限,未授权IP等) |
404 Not Found | 服务器上没有请求的资源。路径错误等 |
500 Internal Server Error | 该状态码表明服务器端在执行请求时发生了错误。也有可能是web应用存在bug或某些临时故障 |
503 Service Unavailable | 服务器暂时处于超负载或正在停机维护,现在无法处理请求 |
502 | 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。 |
504 | 作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应。 |
如果在cookie中设置了HttpOnly,那么通过程序(js脚本)将无法读取到cookie信息,这样能有效防止XSS攻击
表示来源,正确英语拼法是Referrer,为了向后兼容,将错就错
在www.google.com里有一个
www.baidu.com
链接,那么点击这个www.baidu.com
,它的header信息里就有:Referer=http://www.google.com,可以利用这个来防止盗链了,比如我只允许我自己的网站访问我自己的图片服务器,那我的域名是www.google.com
,那么图片服务器每次取到Referer来判断一下是不是我自己的域名www.google.com
,如果是就继续访问,不是就拦截。直接在地址栏中输入URL的地址是不会包含referer字段的,因为他不是藏一个地方链接过去的
允许 Referer 为空,意味着你允许比如浏览器直接访问,就是空
是一个计算机通信协议,该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程.它是建立在Socket之上
RPC实现远程调用的策略:
(1) 采用何种网络通讯协议?
比较流行的RPC框架,采用的都是TCP作为底层传输协议
(2)数据传输的格式是怎么样的?
不同于HTTP,RPC远程过程调用需要让调用者和被调用者采用相同的数据传输格式,就好比两个人使用同一种语言进行交流沟通一样.
下面是RPC的调用流程图:
HTTP称为超文本传输协议,是一种应用层的协议,规定了网络传输的请求格式、响应格式、资源定位和操作的方式等。但是底层采用什么网络传输协议,并没有规定,不过现在都是采用TCP协议作为底层传输协议.
客户端通过HTTP向服务器端发送请求,并接受响应的流程:
常用的RPC框架: webservie(cxf)、dubbo
HTTP客户端工具: HTTPClient
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。建立网络通信连接至少要一对端口号(Socket)。Socket本质是编程接口(API).
当然我们也可以理解为: Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
在高并发短连接的server端,当server处理完client的请求后立刻close socket此时会出现time_wait状态,然后如果client再并发2000个连接,此时部分连接就连接不上了,用linger强制关闭可以解决此问题,但是linger会导致数据丢失,linger值为0时是强制关闭,无论并发多少多能正常连接上,如果非0会发生部分连接不上的情况!(可调用setsockopt设置套接字的linger延时标志,同时将延时时间设置为0。)
TIME_WAIT是TCP连接断开时必定会出现的状态。是无法避免掉的,这是TCP协议实现的一部分。
在WINDOWS下,可以修改注册表让这个时间变短一些,time_wait的时间为2msl,默认为4min.你可以通过改变TcpTimedWaitDelay的量,把它缩短到30s.TCP要保证在所有可能的情况下使得所有的数据都能够被投递。当你关闭一个socket时,主动关闭一端的socket将进入TIME_WAIT状态,而被动关闭一方则转入CLOSED状态,这的确能够保证所有的数据都被传输。但是这样也会给我们带来两个问题 :
(1) 我们没有任何机制保证最后的一个ACK能够正常传输
(2) 网络上仍然有可能有残余的数据包(wandering duplicates),我们也必须能够正常处理
由于TIME_WAIT状态所带来的相关问题,我们可以通过设置SO_LINGER标志来避免socket进入TIME_WAIT状态,这可以通过发送RST而取代正常的TCP四次握手的终止方式。但这并不是一个很好的主意,TIME_WAIT对于我们来说往往是有利的,下面我们来谈谈TIME_WAIT的作用
1. 可靠地实现TCP全双工连接的终止;
如果服务器最后发送的ACK因为某种原因丢失了,那么客户一定会重新发送FIN,这样因为有TIME_WAIT的存在,服务器会重新发送ACK给客户,如果没有TIME_WAIT,那么无论客户有没有收到ACK,服务器都已经关掉连接了,此时客户重新发送FIN,服务器将不会发送ACK,而是RST,从而使客户端报错。也就是说,TIME_WAIT有助于可靠地实现TCP全双工连接的终止。
2. 允许老的重复分节在网络中消逝。
如果没有TIME_WAIT,我们可以在最后一个ACK还未到达客户的时候,就建立一个新的连接。那么此时,如果客户收到了这个ACK的话,就乱套了,必须保证这个ACK完全死掉之后,才能建立新的连接。也就是说,TIME_WAIT允许老的重复分节在网络中消逝。
ACK包到达服务器需要MSL时间,服务器重传 FIN 包也需要MSL时间,2MSL 是数据包往返的最大时间,通常为2min,如果2MSL后还未收到服务器重传的FIN 包,就说明服务器已经收到了ACK包,此时由TIME_WAIT状态转变为CLOSED状态.
如果 TIME_WAIT 状态保持时间不足够长(比如小于2MSL),第一个连接就正常终止了。第二个拥有相同相关五元组的连接出现,而第一个连接的重复报文到达,干扰了第二个连接。TCP实现必须防止某个连接的重复报文在连接终止后出现,所以让TIME_WAIT状态保持时间足够长(2MSL),连接相应方向上的TCP报文要么完全响应完毕,要么被丢弃。建立第二个连接的时候,不会混淆。
每个应用程序都是一个进程,每个进程都有自己独立的一块内存空间,一个进程可以有多个线程。
进程的三种状态:就绪态(以获得除cpu外的所有资源),阻塞态,执行态
进程中的一个执行任务(控制单元)。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据。
根本区别:进程是操作系统分配资源的基本单位,线程是处理器任务调度和执行的基本单位
资源开销:每个进程都有独立的代码和数据空间,程序之间切换开销大;线程是轻量级的线程,切换开销少
内存分配:进程的地址空间和资源是相互独立的,线程共享本进程的地址空间和资源
1.1匿名管道(半双工,数据只能单向移动,面向字节流,允许具有血缘关系的进程通信)
两个文件描述符指向管道的两端
1.2有名管道(半双工,允许无亲缘关系的进程通信)
(发送进程添加消息到队列的末尾,接收进程在队列头部接收消息,消息一旦被接收就会从队列中删除,类似FIFO)
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。
信号量工作原理 :
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)
共享内存就是允许两个或多个进程共享一定的存储区。共享内存没有任何的同步与互斥机制,所以要使用信号量来实现对共享内存的存取的同步
可用于不同机器间的进程通信
如上图所示,在浏览器的地址栏中输入url : 此时执行过程简单地介绍如下所示
第一步 : 我们的DNS域名解析器会对url进行域名解析,找到对应的IP节点进行返回
第二步 : 拿到我们访问的IP,我们就要与对应的服务器建立TCP连接(三次握手)
第三步 : TCP连接建立成功,我们就可以向服务器发送HTTP请求了,当服务器获取我们的请求,进行处理,然后将请求的响应返回给我们
第四步 : 关闭TCP连接(四次挥手)
DNS是进行域名和与之相对应的IP地址转换的服务器
浏览器会首先搜索浏览器自身的DNS缓存,如果浏览器自身的缓存里面没有找到对应的条目,
那么浏览器会搜索操作系统自身的DNS缓存,操作系统域名解析的过程是读取hosts文件(位于C:\Windows\System32\drivers\etc),如果在hosts
文件中也没有找到对应的条目,
浏览器就会找TCP/IP参数中设置的首选DNS服务器(本地DNS服务器)发起一个DNS的系统调用(运营商dns-->
根(13台)域名服务器-->
顶级域名服务器-->
域名注册商服务器)
从客户端到本地DNS服务器是属于递归查询,而DNS服务器之间就是的交互查询就是迭代查询。
将URL对应的各种资源,通过浏览器渲染引擎,输出可视化的图像
渲染引擎主要包括:html解析器,css解析器,布局,js引擎
html引擎:将html文本解释成DOM树(文档对象模型)
css解释器:为dom中的各个元素对象加上样式信息(CSS对象模型)
布局:将dom和css样式信息结合起来,计算他们的大小及位置信息,构建(渲染树)
js引擎:解释js代码并把代码逻辑对应的dom和css改动信息应用到布局中去
输入一个网址以后,首先会进行DNS域名解析,找到目标的IP地址,然后和目标地址建立TCP连接,连接成功以后,发送HTTP请求,目标服务器处理完请求以后,返回响应结果,最后关闭TCP连接
主机A首先查看自己的ARP缓存,检查是否含有主机B的IP到MAC地址的映射,如果存在映射,则构造报文,目的IP为主机B的IP,源IP为主机A的IP,目的MAC为主机B的MAC,源MAC为主机A的MAC,将报文发送给交换机C.交换机C进行MAC地址表学习,将主机A和MAC和报文入端口号记录下来,然后交换机C查看自己的MAC转发表,检查是否含有主机B的MAC到端口的映射,如果有对应的端口,则获取对应的端口,将报文从此端口转发出去,报文到达主机B.如果交换机C没有主机B的MAC转发映射表,则采用洪泛的形式广播报文,主机B收到报文后向主机A进行回复,交换机C进行MAC表学习,将主机B的MAC和报文入端口号记录下来
如果主机A没有主机B的ARP映射,主机A需要发送ARP请求,以获取主机B的MAC,将报文发往交换机C,交换机C采用洪泛的形式广播报文,主机B收到广播报文后,在自己的ARP缓存表中写入主机A的IP到MAC的映射,将自己的MAC封装到ARP回复报文中,单播给主机A,主机A获取到主机B的MAC后,在自己的ARP缓存表中写入主机B的IP到MAC的映射,构造报文发送给主机B,过程同上。
主机B向主机A回复报文的过程类似。
主机A查看自己的ARP缓存表,检查是否有路由器E的IP到MAC的映射,如果有映射,获取路由器E的MAC,构造报文,目的IP为主机B的IP,源IP为主机A的IP,目的MAC为路由器E的MAC,源MAC为主机A的MAC,将报文通过交换机C发往路由器E,过程同上。 如果主机A没有路由器E的IP到MAC的映射,需要发送ARP请求,获取路由器E的MAC,过程同上。路由器E收到主机A的报文后,剥离报文的MAC帧头,查询路由表,发现目标主机B所在的网络是直连的,查看自己的ARP缓存表,如果有主机B的IP到MAC的映射关系,获取主机B的MAC,封装报文MAC帧头,目的MAC为主机B的MAC,源MAC为路由器E的MAC,将报文通过交换机D发往主机B,如果路由器E没有主机B的IP到MAC的映射关系,需要发送ARP请求,获取主机B的MAC,过程同上。
主机B向主机A回复报文的过程类似。
注意 :
ARP协议指的是地址解析协议(Address Resolution Protocol),是根据**IP地址获取物理地址(MAC地址)**的一个TCP/IP协议.
在计算机间通信的时候,计算机要知道目的计算机是谁(就像我们人交流一样,要知道对方是谁),这中间需要涉及到MAC地址,而MAC是真正的电脑的唯一标识符。
我们通过两个主机之间的ping连接,来看一下简单的ARP请求应答
PC1依据OSI模型 :
① 依次从上至下对数据进行封装,包括对ICMP Date加IP包头的封装,但是到了封装MAC地址的时候
② PC1首先查询自己的ARP缓存表,发现没有IP2和他的MAC地址的映射,这个时候MAC数据帧封装失败。我们使用ping命令的时候,是指定PC2的IP2的,计算机是知道目的主机的IP地址,能够完成网络层的数据封装,因为设备通信还需要对方的MAC地址,但是PC1的缓存表里没有,所以在MAC封装的时候填入不了目的MAC地址。
那么PC1为了获取PC2的MAC地址 :
③ PC1要发送询问信息,询问PC2的MAC地址,询问信息包括PC1的IP和MAC地址、PC2的IP地址,这里我们想到一个问题,即使是询问信息,也是需要进行MAC数据帧的封装,那这个询问信息的目的MAC地址填什么呢,规定当目的MAC地址为ff-ff-ff-ff-ff-ff时,就代表这是一个询问信息,也即使后面我要说的广播。
PC2收到这个询问信息后,将这里面的IP1和MAC1(PC1的IP和MAC)添加到本地的ARP缓存表中,然后 :
④ PC2发送应答信息,对数据进行IP和MAC的封装,发送给PC1,因为缓存表里已经有PC1的IP和MAC的映射了呢。这个应答信息包含PC2的IP2和MAC2。PC1收到这个应答信息,理所应当的就获取了PC2的MAC地址,并添加到自己的缓存表中。
经过这样交互式的一问一答,PC1和PC2都获得了对方的MAC地址,值得注意的是,目的主机先完成ARP缓存,然后才是源主机完成ARP缓存。之后PC1和PC2就可以真正交流了。
对于交换机而言,它也具有记忆功能,会基于源MAC地址建立一个CAM缓存表(记录MAC对应接口的信息),理解为当PC1发送消息至交换机的Port1时,交换机会把源MAC(也就是MAC1)记录下来,添加一条MAC1和Port1的映射,之后交换机可以根据MAC帧的目的MAC进行端口转发
它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
(1)外形上:
交换机通常端口比较多看起来比较笨重,而路由器的端口就少得多体积也小得多
(2)工作层次不同:
最初的交换机工作在数据链路层,而路由器则工作网络层
(3)数据的转发对象不同:
交换机是根据MAC地址转发数据帧,而路由器则是根据IP地址来转发IP数据。
(4)”分工“不同
交换机主要是用于组建局域网,而路由器则是负责让主机连接外网。
(5)冲突域和广播域
交换机分割冲突域,但是不分割广播域,而路由器分割广播域。
(6)工作原理
交换机的原理比较简单,一般都是采用硬件电路实现数据帧的转发
路由器工作在网络层,肩负着网络互联的重任,要实现更加复杂的协议,具有更加智能的转发决策功能,一般都会在在路由器中跑操作系统,实现复杂的路由算法,更偏向于软件实现其功能。
(7)安全性能
路由器一般有防火墙的功能,能够对一些网络数据包选择性过滤。现在的一些路由器都具备交换机的功能,一些交换机具备路由器的功能,被称为3层交换机,广泛使用。相比较而言,路由器的功能较交换机要强大,但是速度也较慢,价格昂贵,三层交换机既有交换机的线性转发报文的能力,又有路由器的良好的路由功能因此得到广泛的使用
由于我们使用本地域名解析器进行解析,获取源服务器IP导致每一次获取数据,都要去源服务器进行访问获取,造成性能的损失的降低,故我们在网络各处部署节点服务器,构建CDN系统,当我们使用本地DNS域名系统进行解析的时候,DNS系统会将域名解析交给CNAME指向的专用的DNS服务器,通过CDN的DNS服务器获取到一台合适的缓存服务器IP给我们,这里的合适性指的是(服务器距离我们的距离位置,该服务器上是否存在我们需要的内容等等…),此时我们就与该缓存服务器建立TCP连接,已经后续的HTTP请求获取数据,得到响应等过程,如果该节点服务器不能满足我们的需求,我们会层层向它的上级服务器进行访问,直到源服务器为止.
(Content Delivery Network)
就近访问原理:采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。cdn就是用来加速的,他能让用户就近访问数据,这样就更更快的获取到需要的数据。
举个例子,现在服务器在北京,深圳的用户想要获取服务器上的数据就需要跨越一个很远的距离,这显然就比北京的用户访问北京的服务器速度要慢。但是现在我们在深圳建立一个cdn服务器,上面缓存住一些数据,深圳用户访问时先访问这个cdn服务器,如果服务器上有用户请求的数据就可以直接返回,这样速度就大大的提升了。
代售点优势:大部分请求在CDN边缘完成,起到了分流的作用,减轻源站的负载。
我们怎么知道用户的所在位置从而给他分配最佳的cdn节点呢。这就需要dns服务来进行定位了。
当我们在浏览器中输入一个域名时,首先需要将域名转换为ip地址,再将ip地址转换为mac地址,这样才能在网络上找到该服务器。当我们向dns服务器发起解析域名的请求时,dns服务器首先会查询自己的缓存中有没有该域名,如果缓存中存在该域名,则可以直接返回ip地址。如果缓存中没有,服务器则会以递归的方式层层访问。例如,我们要访问www.baidu.com,首先我们会先向全球13个根服务器发起请求,询问com域名的地址,然后再向负责com域名的名称服务器发送请求,找到baidu.com,这样层层递归,最终找到我们需要的ip地址。
DNS劫持又称为域名劫持,是一种恶意攻击,黑客或其他方通过使用流氓DNS服务器或其他策略来重定向用户,该策略更改了Internet用户被重定向到的IP地址。
DNS污染最常见的就是篡改host主机文件,通过篡改DNS主机文件ip地址和网址将不再一一对应,打开的网站将不是最初想要访问的网站。
DNS区域传输的时候使用TCP协议:
辅域名服务器会定时向主域名服务器进行查询以便了解数据是否变动。如有变动,会执行一次区域传送,进行数据同步。
域名解析时使用UDP协议:
客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过三次握手,这样DNS服务器负载更低,响应更快。
cdn中缓存了服务器上的部分资源。
服务器主动去更新缓存,cdn节点被动接受
另一种方式是当用户请求的资源不存在时,cdn服务器向上游服务器发起请求,更新缓存,然后将数据返回给用户,这种方式是cdn服务器主动,源站服务器被动。(一般采用这种方式)
当我们与DNS域名解析器解析的IP服务器建立TCP连接以后,此时向其发送请求时,我们可以在其中建立Nginx反向代理,可以实现
负载均衡
,屏蔽真实地址,安全管理
,解决跨域问题
,节约有效的IP资源
,减少web服务器压力,提高响应速度
Nginx是一个轻量级/高性能的反向代理Web服务器,他实现非常高效的反向代理、负载均衡
.
企业内部所有的网站共享一个在Internet中注册的IP地址,这些服务器分配私有地址,采用虚拟主机的方式对外提供服务。
反向代理就是通常所说的web服务器加速,它是一种通过在繁忙的web服务器和外部网络之间增加一个高速的web缓冲服务器来降低实际的web服务器的负载的一种技术。反向代理服务器会强制将外部网络对要代理的服务器的访问经过它,它会将从源服务器上获取到的静态内容缓存到本地,以便日后再收到同样的信息请求时,直接将本地缓存的内容发给客户端,减少后端web服务器的压力,提高响应速度。
其他优点
请求的统一控制,包括设置权限,过滤规则等。
区分动态和静态可缓存内容。
实现负载均衡,内部可以采用多台服务器来组成服务器集群,外部还是可以采用一个地址访问。
解决ajax跨域问题。
作为真实服务器的缓存,解决瞬间负载量大的问题。
配置简单
占内存小,可实现高并发连接,处理响应快
处理动态页面很慢
因为他的事件处理机制:异步非阻塞事件处理机制(运用了epoll模型,提供了一个队列,排队解决)
nginx接收一个请求后,首先由listen和server_name指令匹配server模块,再匹配server模块里的location,location就是实际地址
server {
# 第一个Server区块开始,表示一个独立的虚拟主机站点
listen 80; # 提供服务的端口,默认80
server_name localhost; # 提供服务的域名主机名
location / {
# 第一个location区块开始
root html; # 站点的根目录,相当于Nginx的安装目录
index index.html index.htm; # 默认的首页文件,多个用空格分开
} # 第一个location区块结果
正向代理类似一个跳板机,代理访问外部资源。(正向代理就是一个人发送一个请求直接就到达了目标的服务器)
比如我们国内访问谷歌,直接访问访问不到,我们可以通过一个正向代理服务器,请求发到代理服,代理服务器能够访问谷歌,这样由代理去谷歌取到返回数据,再返回给我们,这样我们就能访问谷歌了
2.反向代理(Reverse Proxy)对外就表现为一个服务器。(反方代理就是请求统一被Nginx接收,nginx反向代理服务器接收到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了)
实际运行方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器
[root@localhost ~]# tree /usr/local/nginx
/usr/local/nginx
├── client_body_temp
├── conf # Nginx所有配置文件的目录
│ ├── fastcgi.conf # fastcgi相关参数的配置文件
│ ├── fastcgi.conf.default # fastcgi.conf的原始备份文件
│ ├── fastcgi_params # fastcgi的参数文件
│ ├── fastcgi_params.default
│ ├── koi-utf
│ ├── koi-win
│ ├── mime.types # 媒体类型
│ ├── mime.types.default
│ ├── nginx.conf # Nginx主配置文件,很重要!!!
│ ├── nginx.conf.default
│ ├── scgi_params # scgi相关参数文件
│ ├── scgi_params.default
│ ├── uwsgi_params # uwsgi相关参数文件
│ ├── uwsgi_params.default
│ └── win-utf
├── fastcgi_temp # fastcgi临时数据目录
├── html # Nginx默认站点目录
│ ├── 50x.html # 错误页面优雅替代显示文件,例如当出现502错误时会调用此页面
│ └── index.html # 默认的首页文件
├── logs # Nginx日志目录
│ ├── access.log # 访问日志文件
│ ├── error.log # 错误日志文件
│ └── nginx.pid # pid文件,Nginx进程启动后,会把所有进程的ID号写到此文件
├── proxy_temp # 临时目录
├── sbin # Nginx命令目录
│ └── nginx # Nginx的启动命令
├── scgi_temp # 临时目录
└── uwsgi_temp # 临时目录
答: 首先当前我们的网络存在很多的恶意攻击,例如XSS,CSRF等等,我们客户端给服务器发送请求,访问服务器时,服务器需要对我们的请求进行相应的认证,同时对于我们客户端授予不同的权限,拥有相应的权限才能访问服务器,发送相应的请求信息。
jwt(Json web token) : 是为了在网络应用环境中传递声明而执行的一种基于JSON的开放标准(RFC 7519)
其中对应生成的token被设计成紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token可直接被用于认证,也可被加密。
JWT生成编码以后是以下的样子:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q
JWT由三个部分组成:
头部(header):
HMAC算法介绍:
完整的头部head信息就像下面的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后我们会对头部信息进行加密,构成第一部分:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷(payload):
载荷是存放有效信息的地方,有效信息包括三个部分:
其中,标准中注册的声明(建议但是不强制使用) :
公共的声明:
私有声明:
我们这里定义一个payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
进行加密处理以后,构成第二部分:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
签证(signature):
jwt的第三部分是一个签证信息,一般由三个部分组成:
UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q
其中密钥是保存在服务端的,服务端会根据这个密钥生成token和验证,所以必须要保存好!!!
最后一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输入产生的输出总是不一样的。对于两个不同的输入,产生同样的输出的概率极其地小.
所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的.
服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名。那么服务器应用是怎么知道我们用的是哪一种算法呢?别忘了,我们在JWT的头部中已经用alg字段指明了我们的加密算法了。
如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。
注意:
一般会在请求头中加入Authoriation,并加入Bearer标注:
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
服务端会验证token,如果验证通过就会返回相应的资源!
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
它的流程如下所示:
这个token值必须在每次请求时传递给服务端,它应该保存在请求头中,另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了(Access-Control-Allow-Origin)
方案1 : 适当减短token的有效期,让token尽快失效
方案2 : 使用redis (即使用JWT还要使用redis的场景)
(1) 我们在用户登录时,生成JWT
(2) 把JWT的id存储到redis中,只有redis数据库中有id的JWT,才是有效的JWT
(3) 退出登录时,将对应的id从redis中删除,即间接解决了token的注销问题
答:JWT设计为了实现无状态的登录,因此token无法修改,难以实现异地登录的判断,或者强制让登录token失效。
因此如果有类似需求, 就不应选择JWT作为登录方案,而是使用传统session登录方案。
但是,如果一定要用JWT实现类似要过,就需要在Redis中记录登录用户的JWT的token信息,这样就成了有状态的登录,但是这样还不如一开始就选择Session方案.
我们一般选择在网关中实现认证和登录.
我们的微服务隐藏在网关的后面,而且整个服务被Nginx反向代理,用户只能看到nginx的地址,微服务暴露的可能性很低。
然后,即便真的暴露了,我们的微服务都做了严格的服务间鉴权处理,任何对微服务的访问都会被验证是否有授权,如果没有则会被拦截。具体实现:
当一个用户打开一个浏览器或者访问一个网站时,只要不关闭这个浏览器,不管该用户点击多少个超链接,访问多少个资源,直到该用户关闭浏览器或者服务器关闭.这样一个过程称为一个会话.
那么在会话过程中需要解决一些问题 : 主要就是保存用户在访问过程中产生的数据
当浏览器第一次访问服务器,给服务器发送请求时,服务器端会创建一个键值对形式的Cookie,该Cookie中会包含当前用户的信息,然后通过响应(响应头set-cookie)返回给浏览器端,cookie会保存在浏览器端
下次访问的时候,浏览器会携带服务器端创建的cookie(请求头cookie),服务器端就可以拿到这些cookie携带的数据区分不同的用户,并拿到对应的信息
注意:
session的生命周期
创建 : 第一次访问服务器,调用getSession()方法获取session会话的时候
销毁 : (1) 服务器非正常关闭 (2) 设置超时时间(默认tomat服务器是30分钟) (3)浏览器关闭(手动销毁)
session:
关闭浏览器,只会使浏览器中的sessionid消失,但不会使服务端的session对象消失。因此服务端需要设置一个过期时间,当距离客户上一次使用session的时间超过了这个失效时间时就认为客户端已经停止了活动,从而删除session。session.setMaxInactiveInterval(3600);
cookie:
Cookies.set('name', 'value', {
expires: 7, //设置失效时间为7天
});
var millisecond = new Date().getTime();
var expiresTime = new Date(millisecond + 60 * 1000 * 15); //设置过期时间为15分钟后
Cookies.set('name', 'value', {
expires: expiresTime, //如果设置一个过去的时间点会直接删除
});
sessionStorage和localStorage均用于客户端本地存储数据
不能设置过期时间。生命周期是永久性的,即使关闭浏览器也不会让数据消失,除非主动去删除数据。
if(window.localStroage){
//判断浏览器是否支持localstroage
window.localStroage.setItem('name','zs') //存储数据
window.localStroage.getItem('name') //获取数据
window.localStroage.removeItem('name') //移除数据
}
生命周期是在浏览器关闭前,在浏览器关闭前,其数据一直存在。使用方法与localStroage一致。
当变量进入环境时,例如:在一个函数中声明一个变量,就将这个变量标记为“进入环境”;当变量离开时,则将其标记为离开环境。
(从逻辑上讲,永远不能释放进入环境的变量所占的内存,因为只要执行流进入相应的环境,就可能还会用到它们)
跟踪记录每个值被引用的次数。
当申明了一个变量并将一个引用类型值A赋给该变量时,则这个引用类型值A的引用次数就是1。如果同一个引用类型值A被赋给另一个变量,则这个引用类型值A的引用次数+1;相反,如果包含这个引用类型值A的变量又取得了另外一个引用类型值B,则这个引用类型值A的引用次数-1;当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。
循环引用
function circularReference() {
let obj1 = {
};
let obj2 = {
b: obj1
};
obj1.a = obj2;
}
//当obj1这个变量指向obj1这个对象时,obj1这个对象的引用计数为1
//当obj2这个变量指向obj2这个对象时,obj2这个对象的医用计数为1;obj2中的b属性指向obj1,obj1这个对象的引用计数变为2
//obj1的a属性指向obj2,obj2这个对象的引用计数变为2
//当代码执行完毕,会将obj1和obj2赋值为null,但此时obj1对象和obj2对象的引用计数仍为1,不为0,所以不会进行垃圾回收,造成垃圾泄露。
//这两个对象已经没什么作用了,在函数外部也访问不到他们。
解决问题的关键也就是可以将这些引用存储起来并在发现引用时返回被引用过的对象,从而结束递归的调用。
原理:
利用uniqueList存储拷贝过的对象,如果之前拷贝过就把他的拷贝结果返回,否则遍历该对象的各个属性,是对象的话继续深拷贝,不是对象就直接赋值
//arr中存储了多个对象
//[{source:xxx, target:xxx},{source:xxx, target:xxx},]
function find(arr,item){
for(var i=0; i<arr.length; i++){
if(arr[i].source === item){
return arr[i] //返回的是这整个对象{source:xxx, target:xxx}
}
}
return null;
}
function isObject(obj) {
return typeof obj === 'object' && obj != null;
}
function deepClone(source,uniqueList){
//被拷贝的source必须是对象
if(!isObject(source)) return source;
if(!uniqueList) uniqueList = []; //初始化数据
//存储结果
var target = Array.isArray(source) ? [] : {
};
var uniqueData = find(uniqueList,source); //在初始化数据中查看是否存在source
if(uniqueData) return uniqueData.target;
uniqueList.push({
source:source, //拷贝源
target:target //拷贝结果
});
for(var key in source){
if(Object.prototype.hasOwnProperty.call(source,key)){
if(isObject(source[key])){
target[key] = deepClone(source[key], uniqueList) // 传入数组
}else{
target[key] = source[key];
}
}
}
return target;
}
var a = {
name:"key1",
eat:[
"苹果",
"香蕉"
]
}
a.eat[2] = "桃";
a.d = a;
console.log(a);
//{name: "key1", eat: Array(3), d: {
// {name: "key1", eat: Array(3), d: {…}}
//}}
b = deepClone(a);
console.log(b);
//{name: "key1", eat: Array(3), d: {
// {name: "key1", eat: Array(3), d: {…}}
//}}
同源策略是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。
同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。
缺点:只能实现get一种请求
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
</script>
服务端返回如下(返回时即执行全局函数):
handleCallback({
"status": true, "user": "admin"})
后端node.js代码示例:
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
var params = qs.parse(req.url.split('?')[1]);
var fn = params.callback;
// jsonp返回设置
res.writeHead(200, {
'Content-Type': 'text/javascript' });
res.write(fn + '(' + JSON.stringify(params) + ')');
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
主要是后端设置,如果前端发送请求需要携带cookie,前端才需要设置
前端设置:
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
后端设置
/*
* 导入包:import javax.servlet.http.HttpServletResponse;
* 接口参数中定义:HttpServletResponse response
*/
// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com");
// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
response.setHeader("Access-Control-Allow-Credentials", "true");
// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
前端请求:
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();
代理服务器处理请求:
#proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
后台:
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var params = qs.parse(req.url.substring(2));
// 向前台写cookie
res.writeHead(200, {
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取
});
res.write(JSON.stringify(params));
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
//在webpack.config.js中配置
module.exports = {
entry: {
},
module: {
},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.domain2.com:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些https服务报错时用
cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
}],
noInfo: true
}
}
提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader(“Access-Control-Allow-Headers”, “Content-Type,X-Requested-With”);
### nginx代理
> 同源策略是浏览器的安全策略,不是HTTP协议的一部分。**服务器端调用HTTP接口**只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就**不存在跨越问题。**
前端请求:
```js
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();
代理服务器处理请求:
#proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
后台:
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var params = qs.parse(req.url.substring(2));
// 向前台写cookie
res.writeHead(200, {
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取
});
res.write(JSON.stringify(params));
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
//在webpack.config.js中配置
module.exports = {
entry: {
},
module: {
},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.domain2.com:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些https服务报错时用
cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
}],
noInfo: true
}
}