Computer Networks
Published:
This is my personal notes for Computer Networks.
TCP/IP协议各层的作用
应用层(Application):应用层的任务是通过应用进程之间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则。这里的进程是指主机中正在与运行的程序。对于不同的网络应用需要有不同的应用层协议。在互联网中的应用层协议有很多,如域名系统DNS,支持万维网应用的HTTP协议,支持电子邮件的SMTP协议,等等。
传输层(Transport):传输层的任务是负责向两台主机中进程之间的通信提供通用的数据传输服务。应用进程利用该服务传送应用层报文。主要有TCP协议和UDP协议。
网络层(Network):网络层的任务是为分组交换网上的不同主机提供通信服务。在发送数据的时候,网络层把运输层产生的报文段或者用户数据报封装成分组或包进行传送。主要使用IP协议。
网络接口层(Link):操作系统中的设备驱动和计算机对应的网络接口,它们一起处理与传输媒介的物理接口细节。主要有ARP协议和RARP协议。
TCP和UDP的区别
连接:UDP是无连接的,发送数据之前无需建立连接,发送数据结束后也无需释放连接。TCP是面向连接的,在发送数据之前需要通过三次握手建立连接,发送数据结束后需要通过四次挥手释放连接。
交付:UDP使用尽最大努力交付,即不保证可靠交付,主机不需要维持复杂的连接状态表。TCP提供可靠交付,即通过TCP连接传送的数据,无差错、不丢失、不重复,并且按序到达。
数据:UDP是面向报文的,UDP对于应用层交下来的报文,添加首部后就向下交给IP层,既不合并,也不拆分,一次交付一个完整的报文。TCP是面向字节流的,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据看出成一连串无结构的字节流。TCP不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小关系。
通信双方:UDP支持一对一、一对多、多对一、多对多的交互通信。TCP连接是点对点(一对一),每一条TCP连接只能有两个端点。
拥塞:UDP没有拥塞控制,网络的拥塞不会使源主机的发送速率降低。TCP通过慢开始、拥塞避免、快重传、快恢复等算法进行拥塞控制。
首部:UDP首开销小,只有8个字节(byte)。TCP首部是20个字节(byte)。
SYN: 在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。
ACK: 当 ACK=1 时确认号字段有效,否则无效。在连接建立后所有传送的报文段都必须把 ACK 置 1。
FIN: 用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。
Sequence Number: 用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。
Acknowledgement Number: 期望收到的下一个报文段的序号。
Window Size: Flow control 中指定的sliding window的size。
UDP如何实现可靠传输
UDP它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。
实现确认机制、重传机制、窗口确认机制。
如果你不利用Linux协议栈以及上层socket机制,自己通过抓包和发包的方式去实现可靠性传输,那么必须实现如下功能:
发送:包的分片、包确认、包的重发
接收:包的调序、包的序号确认
目前有如下开源程序利用udp实现了可靠的数据传输。分别为RUDP、RTP、UDT。
TCP的三次握手与四次挥手
三次握手(我要和你建立链接,你真的要和我建立链接么,我真的要和你建立链接,成功)
第一次握手(SYN=1, seq=x):
客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。
发送完毕后,客户端进入 SYN_SEND 状态。
第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):
服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。 发送完毕后,服务器端进入 SYN_RCVD 状态。
第三次握手(ACK=1,ACKnum=y+1)
客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1
发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。
四次挥手 (我要和你断开链接;好的,断吧。我也要和你断开链接;好的,断吧)
第一次挥手(FIN=1,seq=x)
假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。
发送完毕后,客户端进入 FIN_WAIT_1 状态。
第二次挥手(ACK=1,ACKnum=x+1)
服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。
发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。
第三次挥手(FIN=1,seq=y)
服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。
发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。
第四次挥手(ACK=1,ACKnum=y+1)
客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。
服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。
为什么TCP链接需要三次握手,而不是四次/两次?
关键点: TCP是有序可靠的, 需要通信双方互相确认对方的序列号
- 四次握手:
- 1.1 client 发送 SYN = 1, seq = x (client的序列号)
- 1.2 server 发送 ACK = 1, ack = x+1 (准备接收的序列号)
- 1.3 server 发送 SYN = 1, seq = y (server的序列号)
- 1.4 client 发送 ACK = 1, seq = x+1, ack = y+1 (准备接收的序列号)
很明显, 1.2和1.3可以合并, 提高连接速度和效率
- 两次握手:
- 1.1 client 发送 SYN = 1, seq = x
- 1.2 server 发送 ACK = 1, ack = x+1, SYN = 1, seq = y
此时, client并没有确认server的序列号,不符合TCP有序可靠的要求
- 三次握手:
- 1.1 client 发送 SYN = 1, seq = x
- 1.2 server 发送 ACK = 1, ack = x+1, SYN = 1, seq = y
- 1.3 client 发送 ACK = 1, seq = x+1, ack = y+1
此时, 双方互相确认了对方的序列号
- 补充:
- 1 如果第1个包丢失, client的SYN没有到达server:
client会周期性超时重传, 直到收到server的确认 - 2 如果第2个包丢失, server的ACK+SYN没有到达client:
server会周期性超时重传, 直到收到client的确认 - 3 如果第3个包丢失, client的ACK没有到达server:
如果双方都没有发送数据, server会周期性超时重传 SYN+ACK, 直到收到client的确认, 建立连接
如果此时client有数据发送, server收到client的 Data+ACK, 接收数据并建立连接
TCP不会为没有数据的ACK超时重传
- 1 如果第1个包丢失, client的SYN没有到达server:
为什么TCP链接需要四次挥手?
四次挥手的原因
客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。
TIME_WAIT
客户端接收到服务器端的 FIN 报文后进入TIME_WAIT状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL(最大报文存活时间)。这么做有两个理由:
(1) 确保最后一个确认报文能够到达。如果 server 没收到 client 发送来的最后一个确认报文,那么就会重新发送FIN,A 等待一段时间就是为了处理这种情况的发生。
(2) 等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。
TCP协议如何来保证传输的可靠性
TCP提供一种面向连接的、可靠的字节流服务。其中,面向连接意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据之前必须先建立一个TCP连接。在一个TCP连接中,仅有两方进行彼此通信;而字节流服务意味着两个应用程序通过TCP链接交换8bit字节构成的字节流,TCP不在字节流中插入记录标识符。
对于可靠性,TCP通过以下方式进行保证:
数据包校验:目的是检测数据在传输过程中的任何变化,若校验出包有错,则丢弃报文段并且不给出响应,这时TCP发送数据端超时后会重发数据;
对失序数据包重排序:既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。TCP将对失序数据进行重新排序,然后才交给应用层;
丢弃重复数据:对于重复数据,能够丢弃重复数据;
应答机制:当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒;
超时重发:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段;
流量控制:TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据,这可以防止较快主机致使较慢主机的缓冲区溢出,这就是流量控制。TCP使用的流量控制协议是可变大小的滑动窗口协议。
TCP的流量控制 (Flow Control)
发送方的sliding window size (sws):
允许发送的最大seq - 上一次ack的seq <= ssw接收方的sliding window size (sws):
允许接收的最大seq - 上一次收到的seq <= ssw
注意: receiver发送的ack是cumulative ack, 即1,2,4,5,6只会重复发送ack2, 即数据丢失时, sender无法收到更大的ack, window即无法往前移动, 一直等到对应丢失的数据超时重传。
TCP的拥塞控制 (Congestion Control)
计算机网络中的带宽、交换结点中的缓存及处理机等都是网络的资源。在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就会变坏,这种情况就叫做拥塞。拥塞控制就是 防止过多的数据注入网络中,这样可以使网络中的路由器或链路不致过载。注意,拥塞控制和流量控制不同,前者是一个全局性的过程,而后者指点对点通信量的控制。拥塞控制的方法主要有以下四种:
- 1 慢开始:不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小;
- 2 拥塞避免:拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍,这样拥塞窗口按线性规律缓慢增长。
- 3 快重传:快重传要求接收方在收到一个 失序的报文段 后就立即发出 重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。
- 4 快恢复:快重传配合使用的还有快恢复算法,当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半,但是接下去并不执行慢开始算法:因为如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。
TCP的粘包拆包
问题背景
TCP是面向字节流的协议,把上层应用层的数据看成字节流,所以它发送的不是固定大小的数据包,TCP协议也没有字段说明发送数据包的大小。而且TCP不保证接受方应用程序收到的数据块和发送应用程序发送的数据块具有对应的大小关系。比如发送方应用程序交给发送方TCP 10个数据块,接受方TCP可能只用了4个数据块就完整的把接受到的字节流交给了上层应用程序。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
问题说明
粘包
当我们发送两个完整包到接收端的时候:
正常情况会接收到两个完整的报文。但也有以下的情况:
接收到的是一个报文,它是由发送的两个报文组成的,这样对于应用程序来说就很难处理了(粘包)
拆包
还有可能出现上面这样的虽然收到了两个包,但是里面的内容却是互相包含,对于应用来说依然无法解析(拆包)
问题原因
- 应用程序写入字节的大小大于socket发送缓冲区大小,此时会发送拆包
- 应用程序发送数据大小大于MSS(最大数据报文段),此时会进行MSS大小的TCP分段
- 以太网帧playload大于MTU(通信协议某一层最大允许通过的数据包大小)进行IP分片。如果IP层有数据要传,而且数据长度比链路层的MTU大,那么IP层就会进行数据分片,每个数据片的大小不超过链路层的MTU
TCP粘包/拆包解决策略
由于TCP无法理解上一层的业务数据特点,所以TCP是无法保证发送的数据包不发生粘包和拆包,这个问题只能通过上层的协议栈设计来解决,解决思路有一下几种:
- a 消息定长。每个发送的数据包大小固定,比如100字节,不足100字节的用空格补充,接受方取数据的时候根据这个长度来读取数据
- b 消息末尾增加换行符来表示一条完整的消息。接收方读取的时候根据换行符来判断是否是一条完整的消息。如果消息的内容也包含换行符,那么这种方式就不合适了。
- c 将消息分为消息头和消息尾两部分,消息头指定数据长度,根据消息长度来读取完整的消息。例如UDP协议是这么设计的,用两个字节来表示消息长度,所以UDP不存在粘包和拆包问题。
UDP是否会出现粘包拆包?
不会。
UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。
而TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;另外从TCP的帧结构也可以看出,在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。
Http, Https, Http2.0
Http
Http协议运行在TCP之上,明文传输,客户端与服务器端都无法验证对方的身份;
请求行
请求行由方法字段、URL 字段 和HTTP 协议版本字段 3 个部分组成,他们之间使用空格隔开。常用的 HTTP 请求方法有 GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT。 GET:当客户端要从服务器中读取某个资源时,使用GET 方法。如果需要加传参数的话,需要在URL之后加个”?”,然后把参数名字和值用=连接起来,传递参数长度受限制,通常IE8的为4076,Chrome的为7675。POST:当客户端给服务器提供信息较多时可以使用POST 方法,POST 方法向服务器提交数据,比如完成表单数据的提交,将数据提交给服务器处理。GET 一般用于获取/查询资源信息,POST 会附带用户数据,一般用于更新资源信息。POST 方法将请求参数封装在HTTP 请求数据中,以名称/值的形式出现,可以传输大量数据;
主要特点
无状态性 当客户端访问完一次服务器再次访问的时候,服务器是无法知道这个客户端之前是否已经访问过了。优点是不需要先前的信息,能够更快的应答,缺点是每次连接传送的数据量增大。这种做法不利于信息的交互,随后,Cookie和Session就应运而生,至于它俩有什么区别,可以看看COOKIE和SESSION有什么区别?。
长连接 HTTP1.1 使用持久连接keepalive,所谓持久连接,就是服务器在发送响应后仍然在一段时间内保持这条连接,允许在同一个连接中存在多次数据请求和响应,即在持久连接情况下,服务器在发送完响应后并不关闭TCP连接,客户端可以通过这个连接继续请求其他对象。
其他
支持客户/服务器模式、简单快速(请求方法简单Get和POST)、灵活(数据对象任意)
Https
Https是身披SSL(Secure Socket Layer)外壳的Http,运行于SSL上,SSL运行于TCP之上,是添加了加密和认证机制的HTTP。二者之间存在如下不同:
端口不同:Http与Http使用不同的连接方式,用的端口也不一样,前者是80,后者是443;
资源消耗:和HTTP通信相比,Https通信会由于加减密处理消耗更多的CPU和内存资源;
开销:Https通信需要证书,而证书一般需要向CA购买;
Https的加密机制是一种共享密钥加密和公开密钥加密并用的混合加密机制。
Http2.0
相较于HTTP1.1,HTTP2.0的主要优点有采用二进制帧封装,传输变成多路复用,流量控制算法优化,服务器端推送,首部压缩,优先级等特点。
二进制帧
Http2.0中所有加强性能的核心是二进制传输, HTTP1.x的解析是基于文本的,基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多。而HTTP/2会将所有传输的信息分割为更小的消息和帧,然后采用二进制的格式进行编码(只有0和1),HTTP1.x的头部信息会被封装到HEADER frame,而相应的Request Body则封装到DATA frame里面。不改动HTTP的语义,使用二进制编码,实现方便且健壮。
多路复用
所有的请求都是通过一个 TCP 连接并发完成。HTTP/1.x 虽然通过 pipeline 也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求。同时,流还支持优先级和流量控制。当流并发时,就会涉及到流的优先级和依赖。即:HTTP2.0对于同一域名下所有请求都是基于流的,不管对于同一域名访问多少文件,也只建立一路连接。优先级高的流会被优先发送。图片请求的优先级要低于 CSS 和 SCRIPT,这个设计可以确保重要的东西可以被优先加载完。
流量控制
TCP协议通过sliding window的算法来做流量控制。发送方有个sending window,接收方有receive window。http2.0的flow control是类似receive window的做法,数据的接收方通过告知对方自己的flow window大小表明自己还能接收多少数据。只有Data类型的frame才有flow control的功能。对于flow control,如果接收方在flow window为零的情况下依然更多的frame,则会返回block类型的frame,这张场景一般表明http2.0的部署出了问题。
服务器端推送
服务器端的推送,就是服务器可以对一个客户端请求发送多个响应。除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。当浏览器请求一个html,服务器其实大概知道你是接下来要请求资源了,而不需要等待浏览器得到html后解析页面再发送资源请求。
首部压缩
HTTP 2.0 在客户端和服务器端使用”首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只 需发送一次。事实上,如果请求中不包含首部(例如对同一资源的轮询请求),那么 首部开销就是零字节。此时所有首部都自动使用之前请求发送的首部。 如果首部发生变化了,那么只需要发送变化了数据在Headers帧里面,新增或修改的首部帧会被追加到”首部表”。首部表在 HTTP 2.0 的连接存续期内始终存在,由客户端和服务器共同渐进地更新 。本质上,当然是为了减少请求啦,通过多个js或css合并成一个文件,多张小图片拼合成Sprite图,可以让多个HTTP请求减少为一个,减少额外的协议开销,而提升性能。当然,一个HTTP的请求的body太大也是不合理的,有个度。文件的合并也会牺牲模块化和缓存粒度,可以把”稳定”的代码or 小图 合并为一个文件or一张Sprite,让其充分地缓存起来,从而区分开迭代快的文件。
连接管理
1 短连接和长连接
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问的 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 Connection : close; 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 Connection : Keep-Alive。
2 流水线
默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。流水线是在同一条长连接上连续发出请求,而不用等待响应返回,这样可以减少延迟。
对称加密与非对称加密
对称加密
对称密钥加密是指加密和解密使用同一个密钥的方式,这种方式存在的最大问题就是密钥发送问题,即如何安全地将密钥发给对方非对称加密
非对称加密是指使用一对非对称密钥,即公钥和私钥,公钥可以随意发布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息后,使用自己的私钥进行解密。对称加密 + 非对称加密
由于非对称加密的方式不需要发送用来解密的私钥,所以可以保证安全性;但是和对称加密比起来,它非常的慢,所以我们还是要用对称加密来传送消息,但对称加密所使用的密钥我们可以通过非对称加密的方式发送出去。
客户端不断进行请求链接会怎样?DDos(Distributed Denial of Service)攻击?
1、DDos 攻击
客户端向服务端发送请求链接数据包
服务端向客户端发送确认数据包
客户端不向服务端发送确认数据包,服务器一直等待来自客户端的确认2、DDos 预防 ( 没有彻底根治的办法,除非不使用TCP )
限制同时打开SYN半链接的数目
缩短SYN半链接的Time out 时间
关闭不必要的服务
Get与POST的区别
GET与POST是我们常用的两种HTTP Method,二者之间的区别主要包括如下五个方面:
1 从功能上讲,GET一般用来从服务器上获取资源,POST一般用来更新服务器上的资源;
2 从REST服务角度上说,GET是幂等的,即读取同一个资源,总是得到相同的数据,而POST不是幂等的,因为每次请求对资源的改变并不是相同的;进一步地,GET不会改变服务器上的资源,而POST会对服务器资源进行改变;
3 从请求参数形式上看,GET请求的数据会附在URL之后,即将请求数据放置在HTTP报文的 请求头 中,以?分割URL和传输数据,参数之间以&相连。特别地,如果数据是英文字母/数字,原样发送;如果是中文/其他字符,则直接把字符串用BASE64加密,得出如:%E4%BD %A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII);而POST请求会把提交的数据则放置在是HTTP请求报文的 请求体 中。
4 就安全性而言,POST的安全性要比GET的安全性高,因为GET请求提交的数据将明文出现在URL上,而且POST请求参数则被包装到请求体中,相对更安全。
5 从请求的大小看,GET请求的长度受限于浏览器或服务器对URL长度的限制,允许发送的数据量比较小,而POST请求则是没有大小限制的。
从输入网址到获得页面的过程
1 浏览器查询 DNS,获取域名对应的IP地址:具体过程包括浏览器搜索自身的DNS缓存、搜索操作系统的DNS缓存、读取本地的Host文件和向本地DNS服务器进行查询等。对于向本地DNS服务器进行查询,如果要查询的域名包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析(此解析具有权威性);如果要查询的域名不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析(此解析不具有权威性)。如果本地域名服务器并未缓存该网址映射关系,那么将根据其设置发起递归查询或者迭代查询;
2 浏览器获得域名对应的IP地址以后,浏览器向服务器请求建立链接,发起三次握手;
3 TCP/IP链接建立起来后,浏览器向服务器发送HTTP请求;
4 服务器接收到这个请求,并根据路径参数映射到特定的请求处理器进行处理,并将处理结果及相应的视图返回给浏览器;
5 浏览器解析并渲染视图,若遇到对js文件、css文件及图片等静态资源的引用,则重复上述步骤并向服务器请求这些资源;
6 浏览器根据其请求到的资源、数据渲染页面,最终向用户呈现一个完整的页面。
Session和Cookie
Cookie和Session都是客户端与服务器之间保持状态的解决方案
Cookie
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。
Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。Session
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。
简单来说,cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。
两者区别:
- 1 面向对象: session 在服务器端,cookie 在客户端(浏览器)
- 2 运行机制: session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)
- 3 大小限制:Cookie有大小限制并且浏览器对每个站点也有cookie的个数限制,Session没有大小限制,理论上只与服务器的内存大小有关
- 4 安全性:Cookie存在安全隐患,通过拦截或本地文件找得到cookie后可以进行攻击,而Session由于保存在服务器端,相对更加安全;
- 5 服务器资源消耗:Session是保存在服务器端上会存在一段时间才会消失,如果session过多会增加服务器的压力。
保持会话的方式
由于http是无状态协议, 需要保持用户的会话状态就需要用到 Session / Cookie / Token
session机制保持会话
(1)首先用户在客户端浏览器发起登陆请求
(2)登陆成功后,服务端会把用户信息保存在服务端,并返回一个唯一的 sessionId 给客户端浏览器。
(3)客户端浏览器会把这个唯一的 sessionId 保存在起来
(4)以后再次访问 web 应用时,客户端浏览器会把这个唯一的 sessionId 带上,这样服务端就能根据这个唯一标识找到用户信息。
存在的问题
高并发情况下,造成服务器压力
分布式(一个业务分成几个子业务,部署在多个服务器)或者集群(一个业务部署在多个服务器)的时候,session不能共享。
解决方案
高并发的时候可以将session存储到redis,如果用户长时间没有访问,将session存储到redis,就减少了服务器的压力。
分布式或者集群的时候,先通过redis来判断用户状态也可以实现session共享.
cookie机制保持会话
(1)首先用户在客户端浏览器向服务器首次发起登陆请求
(2)登陆成功后,服务端会把登陆的用户信息设置在cookie 中,并将cookie返回给客户端浏览器(可能是加密后的)
(3)客户端浏览器接收到 cookie 请求后,会把 cookie 保存到本地(可能是内存,也可能是磁盘,看具体使用情况而定)
(4)以后再次访问该 web 应用时,客户端浏览器就会把本地的 cookie 带上,这样服务端就能根据 cookie 获得用户信息了
存在的问题
每次访问都提交cookie,增加请求量
其他访问可能需要cookie(比如说购物车的信息存放在cookie),浏览器对每个域存储的cookie的大小有限制,那么需要控制加密后的凭证。
token机制保持会话
cookie 和session依赖于浏览器,如果客户端不是浏览器,那么需要手动添加token(和cookie类似,也是登录凭证),将token添加到http header或者做为参数添加到url
(1) 用户登录时, 将用户的登录信息保存在服务器端, 这里我们可以采用 redis。 Key为用户的UserId, Value为为用户生成的UUID。
(2) 将UUID即Token返回给前端, 前端将Token缓存起来, 每次访问后端时需要带上Token, 可以放在http header / URL 中
(3) 后端对比前端的 userToken 和 redis 中的 userToken, 确定用户的登陆状态了。 比如后端token失效被抹掉,说明用户登录过期需要重新登录。比如后端token和前端token不一致,说明用户在另外的设备上登录, 可以限制用户只能在一台设备上登录。
存在的问题
每次访问的时候手动添加token
和cookie 的方式一样增加了请求量
怎样在多台Web服务器上共享Session
一、将本该保存在web服务器磁盘上的session数据保存到cookie中
即用cookie会话机制替代session会话机制,将session数据保存到客户端浏览器的cookie中,这样同一个用户访问同一网站时,无论负载均衡到哪台web服务器,都不用再去服务器请求session数据,而直接获取客户端cookie中的session数据。如此,同一个用户的登录状态就不会丢失了。
但这样做,有三大弊端:
把session数据放到客户端的cookie中,一般都是重要数据(如用户id、昵称等),会存在安全问题,但可以将session数据加密后,再存放到cookie中,来降低安全风险。
浏览器对单个cookie的数据量大小限制为4K左右,因此会存在数据量的限制问题。
影响带宽性能,降低了页面的访问速度。在高访问量的情况下,用户每次请求时,都要将客户端cookie中的session数据发送到服务器,要占用较多的带宽,进而影响访问速度,服务器带宽成本增高。
二、将本该保存在web服务器磁盘上的session数据保存到MySQL数据库中
sessionid还是利用cookie机制存储到客户端,但session数据却存放在MySQL服务器上。(需要建立sessionid和session数据行的对应关系)
但这样做,只适合访问量比较小的网站。如果网站的访问量比较大,对MySQL服务器会造成很大压力。因为每次用户请求页面(即使是刷新页面)都要查询MySQL数据库中的session数据表,进而判断用户的登录状态和读取用户相关信息,势必会对数据库服务器造成很大压力,这样就会降低服务器的响应速度,影响用户体验。
三、将本该保存在web服务器磁盘上的session数据保存到内存数据库(memcache或redis)中
memcache或redis是基于内存存储数据的,性能很高,尤其是高并发的情况下尤为合适。主要是因为从内存中读取数据要比从磁盘读取数据快很多。
内存数据库还支持数据过期失效的机制,正好与session的过期机制对应,推荐使用redis内存数据库,因为它比memcache支持更多的数据类型,且支持内存数据备份到磁盘。
SQL 注入
SQL注入就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
- SQL注入攻击的总体思路
- (1). 寻找到SQL注入的位置
- (2). 判断服务器类型和后台数据库类型
- (3). 针对不通的服务器和数据库特点进行SQL注入攻击
SQL注入攻击实例
比如,在一个登录界面,要求输入用户名和密码,可以这样输入实现免帐号登录:
用户名: ‘or 1 = 1 -- 密 码:
用户一旦点击登录,如果没有做特殊处理,那么这个非法用户就很容易登录进去了。Why?
从理论上说,后台认证程序中会有如下的SQL语句:select * from user_table where username=’“+userName+”’ and password=’“+password+”‘
因此,当输入了上面的用户名和密码,上面的SQL语句变成:
SELECT * FROM user_table WHERE username=’’or 1 = 1 –- 'and password=’’
分析上述SQL语句, username=’’ or 1=1 为True, 然后后面加两个-,这意味着注释,后面的语句不起作用。这样,上述语句永远为True,用户轻易骗过系统,获取合法身份。
应对方法
(1). 参数绑定
使用预编译手段,绑定参数是最好的防SQL注入的方法。目前许多的ORM框架及JDBC等都实现了SQL预编译和参数绑定功能,攻击者的恶意SQL会被当做SQL的参数而不是SQL命令被执行。
在mybatis的mapper文件中,对于传递的参数我们一般是使用”#”和”$”来获取参数值。
当使用”#”时,变量是占位符,就是一般我们使用javajdbc的PrepareStatement时的占位符,所以可以防止sql注入;
当使用”$”时,变量就是直接追加在sql中,一般会有sql注入问题。举例: #{videoId}为占位符
<update id="reduceVideoLikeCount" parameterType="String"> update videos set like_counts=like_counts-1 where id=#{videoId} </update>
- (2). 使用正则表达式过滤传入的参数
TCP和UDP分别对应的常见应用层协议
(1) TCP对应的应用层协议
HTTP:从Web服务器传输超文本到本地浏览器的传送协议。
FTP:定义了文件传输协议,使用21端口。常说某某计算机开了FTP服务便是启动了文件传输服务。下载文件,上传主页,都要用到FTP服务。
Telnet:它是一种用于远程登陆的端口,用户可以以自己的身份远程连接到计算机上,通过这种端口可以提供一种基于DOS模式下的通信服务。如以前的BBS是-纯字符界面的,支持BBS的服务器将23端口打开,对外提供服务。
SMTP:定义了简单邮件传送协议,现在很多邮件服务器都用的是这个协议,用于发送邮件。如常见的免费邮件服务中用的就是这个邮件服务端口,所以在电子邮件设置-中常看到有这么SMTP端口设置这个栏,服务器开放的是25号端口。
POP3:它是和SMTP对应,POP3用于接收邮件。通常情况下,POP3协议所用的是110端口。也是说,只要你有相应的使用POP3协议的程序(例如Fo-xmail或Outlook),就可以不以Web方式登陆进邮箱界面,直接用邮件程序就可以收到邮件(如是163邮箱就没有必要先进入网易网站,再进入自己的邮-箱来收信)。
(2) UDP对应的应用层协议
DNS:用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。
SNMP:简单网络管理协议,使用161号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。
TFTP(Trival File Transfer Protocal):简单文件传输协议,该协议在熟知端口69上使用UDP服务。
OSI网络体系结构与TCP/IP协议模型对比
HTTP常见状态码
1XX 正常
100 Continue :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
2XX 成功
200 OK
204 No Content :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
206 Partial Content :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。
3XX 重定向
301 Moved Permanently :永久性重定向
302 Found :临时性重定向
303 See Other :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。
注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。
304 Not Modified :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。
307 Temporary Redirect :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。
4XX 客户端错误
400 Bad Request :请求报文中存在语法错误。
401 Unauthorized :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
403 Forbidden :请求被拒绝。
404 Not Found
5XX 服务器错误
500 Internal Server Error :服务器正在执行请求时发生错误。
503 Service Unavailable :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求
I/O 模型
- 阻塞与非阻塞
线程访问资源的一种处理方式- 阻塞: 线程请求资源, 一直等到资源可用再离开, 不可以干别的事情
- 非阻塞: 线程请求资源, 如果资源不可用, 跳过并请求下一个资源, 可以去干别的事情
- 同步和异步
线程访问数据的一种机制- 同步: 线程主动请求数据并等待通知, 直到获得当前通知, 主动获取通知
- 异步: 线程主动请求数据并离开, 去发起其他的请求, 等待服务器完成操作后通知自己, 被动获取通知
BIO (Blocking I/O)
同步阻塞I/O, 并发能力低, 线程访问资源耗时长
发起请求 -> 一直阻塞 -> 处理完成
NIO (Non-Block I/O) (Netty)
同步非阻塞I/O
selector主动轮询channel(资源) -> 处理请求 -> 处理完成
AIO (Asynchronous I/O)
异步非阻塞I/O
发起请求 —————> 通知回调
NIO 之 Refactor线程模型
单线程模型: 所有请求和I/O都由一个线程处理
多线程模型: 单线程处理请求, 一组线程池处理I/O
主从线程模型:一组线程池处理请求, 一组线程池处理I/O
Reactor三种模式形象比喻:
单Reactor单线程模型:接待员和服务员是同一个人,一直为顾客服务。客流量较少适合
单Reactor多线程模型:一个接待员,多个服务员。客流量大,一个人忙不过来,由专门的接待员在门口接待顾客,然后安排好桌子后,由一个服务员一直服务,一般每个服务员负责一片中的几张桌子
主从线程模型:多个接待员,多个服务员。这种就是客流量太大了,一个接待员忙不过来了