一个Http请求的流程
- 首先你要知道,当你在浏览器的地址栏输入一个URL地址然后按下回车,这整个过程往细了讲能讲一本书,如果想深入了解计算机网络,建议看自顶向下这本书!
负责域名解析的 DNS 服务
首先,用户访问一个域名,会经过DNS解析 DNS(Domain Name System),它和 HTTP 协议一样是位于应用层的协议,主要提供域名到 IP 的解析服务。我们其实不用域名也可以访问目标主机的服务,但是 IP 本身不是那么容易 记,所以使用域名进行替换使得用户更容易记住。
加速静态内容访问速度的 CDN
CDN其实就是一种网络缓存技术,能够把一些相对稳定的资源放到距离最终用户较近的地方,一方面可以节省整个广域网的带宽消耗,另外一方面可以提升用户的访问速度,改进用户体验。我们一般会把静态的文件(图片、脚本、静态页面)放到CDN中。 如果引入了CDN,那么解析的流程可能会稍微复杂一点,大家有空自己去了解一下。比如阿里云就提供了cdn的功能。
HTTP 协议通信原理
- 域名被成功解析以后,客户端和服务端之间,是怎么建立连接并且如何通信的呢?
说到通信,大家一定听过tcp和udp这两种通信协议,以及建立连接的握手过程。而http协 议的通信是基于tcp/ip协议之上的一个应用层协议,应用层协议除了http还有哪些呢(FTP、 DNS、SMTP、Telnet等)
涉及到网络协议,我们一定需要知道OSI七层网络模型和TCP/IP四层概念模型
OSI七层网络模型包含(应用层、表示层、会话层、传输层、网络层、数据链路层、物理层)
TCP/IP四层概念模型包含(应用层、传输层、网络层、数据链路层)。
- 以TCP/IP四层模型为例,请求发起过程中,网络模型中所做的事情。
当应用程序用T C P传送数据时,数据被送入协议栈中,然后逐个通过每一层直到被当作一串比特流送入网络。其中每一层对收到的数据都要增加一些首部信息(有时还要增加尾部信息)
- TCP头和IP头是什么不需要多讲了
MAC地址
MAC地址也叫物理地址、硬件地址,由网络设备制造商生产时烧录在网卡(Network lnterface Card)的EPROM(一种闪存芯片,通常可以通过程序擦写)。只要不更改自己的MAC地址,MAC地址在世界是唯一的。形象地说,MAC地址就如同身份证上的身份证号码,具有唯一性。
如何找到MAC地址
ARP 协议,这个协议简单来说就是已知目标机器 的ip,需要获得目标机器的mac地址。(发送一个广播消息,这个ip是谁的,请来认领。认 领ip的机器会发送一个mac地址的响应)
MAC地址和IP地址公用
我们知道MAC地址是唯一的,那么为什么还需要IP地址呢?我们也说了MAC地址可以比喻为人的身份证,你有了身份证想要在全中国找到这个人是不是很麻烦?所以IP地址就相当于他所在的城市、地区,有了地区再根据身份证就能很轻松找到这个人。
- 接收端收到数据包以后的处理过程
当目的主机收到一个以太网数据帧时,数据就开始从协议栈中由底向上升,同时去掉各层协议加上的报文首部。每层协议都要去检查报文首部中的协议标识,以确定接收数据的上层协议。
TCP/IP 的分层管理
- 分层模式是不是很熟悉!就和我们平常写的 Controller-Service-Dao 很像,主要目的就是分工明确
TCP/IP协议按照层次分为4层:应用层、传输层、网络层、数据链路层。比如 docker,也是基于分层来实现。所以我们会发现,复杂的程序都需要分层,这个是软件设计的要求,每一层专注于当前领域的事情。如果某些地方需要修改,我们只需要把变动的层替换掉就行,一方面改动影响较少,另一方面整个架构的灵活性也更高。 最后,在分层之后,整个架构的设计也变得相对简单了。
负载均衡
其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务。
负载均衡构建在原有网络结构之上,它提供了一种透明且廉价有效的方法扩展服务器和网络设备的带宽、加强网络数据处理能力、增加吞吐量、提高网络的可用性和灵活性。
二层负载
二层负载是针对 MAC,负载均衡服务器对外依然提供一个 VIP(虚 IP),集群中不同的机器 采用相同IP地址,但是机器的MAC地址不一样。当负载均衡服务器接受到请求之后,通过改写报文的目标MAC地址的方式将请求转发到目标机器实现负载均衡
二层负载均衡会通过一个虚拟 MAC 地址接收请求,然后再分配到真实的 MAC 地址
三层负载
三层负载是针对 IP,和二层负载均衡类似,负载均衡服务器对外依然提供一个 VIP(虚 IP), 但是集群中不同的机器采用不同的IP地址。当负载均衡服务器接受到请求之后,根据不同的负载均衡算法,通过IP将请求转发至不同的真实服务器
三层负载均衡会通过一个虚拟 IP 地址接收请求,然后再分配到真实的 IP 地址
四层负载
四层负载均衡工作在 OSI 模型的传输层,由于在传输层,只有 TCP/UDP 协议,这两种协议中除了包含源IP、目标IP以外,还包含源端口号及目的端口号。四层负载均衡服务器在接受 到客户端请求后,以后通过修改数据包的地址信息(IP+端口号)将流量转发到应用服务器。
四层通过虚拟 IP + 端口接收请求,然后再分配到真实的服务器
七层负载
七层负载均衡工作在OSI模型的应用层,应用层协议较多,常用http、radius、dns等。七层负载就可以基于这些协议来负载。这些应用层协议中会包含很多有意义的内容。比如同一个 Web 服务器的负载均衡,除了根据 IP 加端口进行负载外,还可根据七层的 URL、浏览器类 别来决定是否要进行负载均衡
七层通过虚拟的 URL 或主机名接收请求,然后再分配到真实的服务器。
TCP/IP 协议的深入分析
通过前面的分析,基本清楚了网络的通信流程,在http协议中,底层用到了tcp的通信协议,我们接下来给大家简单介绍一下tcp的通信协议原理。
TCP 三次握手
所以TCP消息的可靠性首先来自于有效的连接建立,所以在数据进行传输前,需要通过三次握手建立一个连接,所谓的三次握手,就是在建立TCP链接时,需要客户端和服务端总共发送3个包来确认连接的建立。
-
第一次握手 (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 域里,同时将确认序号 (Acknowledgeme nt Number)设置为 客户的 ISN 加 1, 即X+1。 发送完毕后,服务器端进入 SYN_RCVD 状态。
-
第 三 次 握 手 (ACK=1 , ACKnum=y+1) 客户端再次发送确认包(ACK),SYN标志位为 0,ACK 标 志位为1,并且把服 务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写 ISN 发完毕后,客户端进入 ESTABLISHED 状态,当服务器接收到这个包时,也进入ESTABLISHED 状态,TCP握手结束
那 TCP 在三次握手的时候,IP 层和 MAC 层在做什么呢?当然是 TCP 发送每一个消息, 都会带着 IP 层和 MAC 层了。因为,TCP 每发送一个消息,IP 层和 MAC 层的所有机制都 要运行一遍。而你只看到 TCP 三次握手了,其实,IP 层和 MAC 层为此也忙活好久了。
SYN 攻击
在三次握手过程中, Server 发送 SYN - ACK 之后,收到 Client 的 ACK 之前的 TCP 连接称为 半连接( half - open connect ), 此 时 Server 处于 SYN_RCVD 状态,当收到 ACK 后, Server 转 入 ESTABLISHED 状态。 SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 回复确认包,并等待 Client 的确认,由于源地址是不存在的,因此, Server 需要不断重发直至超时,这些伪造的 SYN 包将产时间占用未连接队 列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。 SYN 攻击时一种典型的 DDOS 攻击,检测 SYN 攻击的方式非常简单,即当 Server 上有大量半连接 状态且源 IP 地址是随机的,则可以断定遭到 SYN 攻击了
TCP 四次挥手
四次挥手表示 TCP 断开连接的时候,需要客户端和服务端总共发送4 个包以确认连接的断开; 客户端或服务器均可主动发起挥手动作(因为 TCP 是一个全双工协议),在 socket 编程中, 任何一方执行 close() 操作即可产生挥手操作。
- 全双工:客户端和服务端能够同时发送和接收请求。
- 第一次挥手(FIN=1,seq=x) 假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据 可以发送了,但是仍然可以接受数据。发送完毕后,客户端进入 FIN_WAIT_1 状态。
- 第二次挥手(ACK=1,ACKnum=x+1) 服务器端确认客户端的 FIN包,发送一个确认包,表明自己接受到了客户端关闭连接的请求, 但还没有准备好关闭连接。发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这 个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。
- 第三次挥手(FIN=1,seq=w) 服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN置为1。发送完毕后,服务器 端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。
- 第四次挥手(ACK=1,ACKnum=w+1) 客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT 状态,服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。 客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime) 之后,进入 CLOSED状态。
为什么要等带2MSL后再关闭
虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个 ACK 丢失。所以 TIME_WAIT 状态就是用来重发可能丢失的ACK报文。
为什么关闭的时候比连接的时候多发了一次报文
三次握手是因为因为当 Server 端收到 Client 端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时, 当 Server 端收到 FIN 报文时,很可能并不会立即关闭 SOCKET(因为可能还有消息没处理完),所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
SOCKET
socket 是一种抽象层,应用程序通过它来发送和接收数据,就像应用程序打开一个文件句柄,把数据读写到磁盘上一样。使用socket可以把应用程序添加到网络中,并与处于同一个网络中的其他应用程序进行通信。
tcp的底层通信过程
首先,对于 TCP 通信来说,每个 TCP Socket 的内核中都有一个发送缓冲区和一个接收缓冲区,TCP的全双工的工作模式及TCP的滑动窗口就是依赖于这两个独立的Buffer和该Buffer 的填充状态。
接收缓冲区把数据缓存到内核,若应用进程一直没有调用 Socket 的 read 方法进行读取,那么该数据会一直被缓存在接收缓冲区内。不管进程是否读取Socket,对端发来的数据都会经 过内核接收并缓存到Socket的内核接收缓冲区。
read所要做的工作,就是把内核接收缓冲区中的数据复制到应用层用户的Buffer里。
进程调用Socket的send发送数据的时候,一般情况下是将数据从应用层用户的Buffer里复制到Socket的内核发送缓冲区,然后send就会在上层返回。换句话说,send返回时,数据不一定会被发送到对端。
滑动窗口协议
这个过程中涉及到了TCP的滑动窗口协议,滑动窗口(Sliding window)是一种流量控制技术。早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题;发送和接受方都会维护一个数据帧的序列,这个序列被称作窗口
网络阻塞
网络阻塞就是服务端不能同时处理所有的连接请求,我们假设他一次只能处理一个,那么剩下的所有连接请求都只能排队等待上一个处理完。这种形式在实际当中并不可取。
逆推解决网络阻塞
- 使用多线程
为每个客户端创建一个线程实际上会存在一些弊端,因为创建一个线程需要占用CPU的资 源和内存资源。另外,随着线程数增加,系统资源将会成为瓶颈最终达到一个不可控的状态,所以我们还可以通过线程池来实现多个客户端请求的功能,因为线程池是可控的。
虽然能够解决问题,但是频繁的进行上下文切换会消耗大量的时间,所以并不可取。
- 使用NIO-同步非阻塞 IO
这种方法虽然比多线程要好,但是还是要进程不断地进行轮询尝试,还是很浪费性能。
- 使用NIO-多路复用
I/O多路复用的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。
- select
select:进程可以通过把一个或者多个 fd传递给 select系统调用,进程会阻塞在 select操作上,这样select可以帮我们检测多个fd是否处于就绪状态
由于他能够同时监听多个文件描述符,假如说有1000个,这个时候如果其中一个fd 处于就绪状态了,那么当前进程需要线性轮询所有的 fd,也就是监听的 fd 越多,性能开销越大
select在单个进程中能打开的fd是有限制的,默认是1024,对于那些需要支持单机上万的TCP连接来说确实有点少
- epoll
linux还提供了epoll的系统调用,epoll是基于事件驱动方式来代替顺序扫描,因此性能相对来说更高,主要原理是,当被监听的 fd 中,有 fd 就绪时,会告知当前进程具体哪一个fd就绪,那么当前进程只需要去从指定的fd上读取数据即可另外,epoll所能支持的fd上线是操作系统的最大文件句柄,这个数字要远远大于1024
真正的异步
其实就算是epoll也仅仅是一个伪异步模型,因为当进程把需要进行的操作都交给内核管理时,如果数据准备完毕后那么会通知进程拿走数据并返回给用户。而真正的异步是当数据准备完毕后,不需要通知进程直接返回给进程然后返回给用户。
举个例子:我们点外卖,伪异步就相当于你下单后,当外卖准备好了,你需要自己去店里拿。 真正的异步就相当于你在家里等着外卖员送上来就行了。