目录
OSI七层架构
TCP/IP协议
1. TCP
(1). 三次握手
- SYN Flood攻击
- TCP报文头
- 为什么要进行三次握手
(2). 四次挥手
- TIME_WAIT
- CLOSE_WAIT
- 为什么要进行四次挥手
(3). 滑动窗口
(4). TCP和UDP的区别
2. HTTP
(1) 请求结构
(2) 响应结构
(3) 请求/响应步骤
(4) 浏览器键入URL经历的流程
(5) 状态码
(6) GET和POST的区别
(7) Cookie和Session的区别
(8) HTTP和HTTPS的区别
3. Socket
一、网络基础知识详解
1. OSI开放式互联参考模型
- 第七层:应用层
- 第六层:表示层
- 第五层:会话层
- 第四层:传输层
- 第三层:网络层
- 第二层:数据链路层
- 第一层:物理层
第一层:物理层
机械、电子、定时接口通信信道上的原始比特流传输。
首先要解决两台物理机的通信需求,具体就是机器A往机器B发送比特流,机器B收到比特流。
物理层主要定义了物理设备的标准,如网线的类型,光纤的接口类型,各种传输介质的传输速率等。它的主要作用是传输比特流,即我们所谓的0101二进制数据,将他们转化为电流强弱来进行传输,到达目的地后再转化为0101机器码,也就是我们常说的数模转换与模数转换。
这一层的数据叫做比特。网卡工作在这一层。
第二层:数据链路层
物理寻址,同时将原始比特流转变为逻辑传输线路。
在传输比特流的过程中,会产生错传,数据传输不完整的可能。数据链路层应运而生,数据链路层定义了如何格式化数据以进行传输以及如何控制对物理介质的访问。这一层通常还提供错误检测和纠正,以确保数据传输的可靠性。
本层将比特数据组成了帧。交换机工作在这一层。对帧解码,并根据帧中包含的信息把数据发送到正确接收方。
第三层:网络层
控制子网的运行,如逻辑编址,分组传输,路由选择。
随着网络节点的不断增加,点对点通信是要经过多个节点的,那么如何找到目标节点,如何选择最佳路径,变成了首要需求,此时便有了网络层。
主要功能是将网络地址翻译为对应的物理地址,并决定如何将数据从发送发路由到接受方。网络层通过综合考虑发送优先权,网络拥塞程度,服务质量以及可选路由的花费来决定从一个网络中节点A到另一个网络中节点B的最佳路径。
由于网络层处理并智能指导数据传送路由器连接网络各段,路由器属于网络层。此层的数据被称为数据包。本层需要关注的协议是TCP/IP协议里面的IP协议。
第四层:传输层
接受上一层的数据,在必要的时候把数据进行分割,并将这些数据交给网络层,切保证这些数据段有效到达对端。
随着网络通信需求的进一步扩大,通信过程中需要发送大量的数据。如海量文件传输等,可能需要很长时间。而网络在通信的过程中会中断好多次,此时为了保证传输大量文件时的准确性,需要对发送出去的数据进行切分,切割为一个一个的段落,即segment进行发送,那么其中一个段落丢失了该怎们办,要不要重传,每个段落要按照顺序到达么?这个便是传输层需要考虑的问题了。
传输层解决了主机间的数据传输,数据间的传输可以是不同网络的,并且传输层解决了传输质量的问题,该层成为OSI模型中最重要的一层。传输协议同时进行流量控制,或是基于可接受方接受数据的快慢程度,规定适当的发送速率,除此之外,传输层按照网络能处理的最大尺寸,将较长的数据包进行强制分割。例如,以太网无法接受大于1500字节的数据包,发送方节点的传输层将数据分割成较小的数据片,同时对每一数据片安排一序列号,以便数据到达接受方节点的传输层时能以正确的顺序重组,该过程称为排序。传输层中我们需要关注的协议有TCP/IP协议中的TCP协议和UDP协议。
第五层:会话层
不同机器上的用户之间建立及管理会话。
现在我们已经保证给正确的计算机发送正确的封装过后的信息了,现在需要建立一个自动收发包自动寻址的功能,于是发明了会话层。
会话层的作用就是建立和管理应用程序之间的通信。
第六层:表示层
信息的语法语义以及他们的关联,如加密解密、转换翻译、压缩与解压缩。
现在我能保证应用程序自动收发包和寻址,但我要用linux给windows发包,两个系统语法不一致,于是需要表示层,帮我们解决不同系统之间的通信语法问题。在表示层,数据将按照网络能理解的方案进行格式化,这种格式化也因所使用的网络类型不同而不同。
第七层:应用层
此时,虽然发送方知道自己发送的是什么东西,转化成字节数据后有多长,但接受方肯定不知道,所以应用层的网络协议诞生了。他规定发送方和接收方必须使用一个固定长度的消息头,消息头必须使用某种固定的组成。而且消息头里必须记录消息长度等一系列信息,以方便接收方能够正确的解析发送方发送的数据。应用层旨在让你更方便的应用同一网络中接收到的数据,至于数据的传递,没有该层,你也可以直接在两台电脑间进行,只是传来传去只是1和0组成的数组。
该层需要我们重点关注的是与之相对应的TCP/IP协议中的HTTP协议。
2. 数据解析
先自上而下,后自下而上处理数据头部
3. TCP/IP
OSI是一个定义良好的协议规范集,并有许多可选部分完成类似的任务,它定义了开放系统的层次结构,层次之间相互关系以及各层所包括的可能任务。是作为一个框架来协调和组织各层所提供的服务。
OSI的 “实现” :TCP/IP四层架构参考模型
先自上而下,后自下而上处理数据头部
二、TCP的三次握手
1. IP协议与TCP协议
IP协议是无连接的通信协议,它不会占用两台正在通信的计算机之间的通信线路,这样IP就降低了对网络线路的需求,每条线可以同时满足许多不同计算机之间,IP的通信需要,通过IP消息或者其他数据会被分割为较小的独立的包,并通过因特网在计算机之间传送,IP负责将每个包路由至它的目的地,但是IP协议没有做任何事情来确认数据包是否按顺序发送,或者包是否被破坏,所以IP数据包是不可靠的,需要由它的上层协议来做出控制。
2. 传输控制协议TCP(传输层的协议)
- 面向连接的、可靠的、基于字节流的传输层通信协议。
- 将应用层的数据流分割成报文段并发送给目标节点的TCP层。
报文段的长度通常受该计算机连接的网络的数据链路层的最大传输单元(MTU)的限制。
TCP将结果包传输给IP层,由它来通过网络将包传输给目标节点的TCP层。 - 数据包都有序号,对方收到则发送ACK确认,未收到则重传。
- 使用校验和来校验数据在传输过程中是否有误。
(使用奇偶校验头函数)
3. TCP报文头
Source Port:源端口 2字节
-
Destination Port:目的端口 2字节
注释:两个进程在计算机内部进行通信的方法:- 管道
- 内存共享
- 信号量
- 消息队列
ip 地址加协议加端口号唯一标识网络中的一个进程,称为套接字
Sequence Number:Seq序号 4字节
注释: TCP流中传输的每个字节都按顺序编号,例如一段报文的序号字段值是107,而携带的数据共有100个字段,如果有下一个报文段的话,它的序号值就是107+100=207。Acknoledgment Number: ACK确认号 4字节
注释:期望收到对方下一个报文的第一个数据字节的序号,例如B收到了A发送过来的报文,其序列号字段为301,数据长度是200字节,这表明了B正确的收到了A发送的到序号500为止的数据,B期望收到A的下一个数据序号是501,于是B在发送给A的确认报文段中会把ACK确认号置为501。Offset:数据偏移
注释:由于头部有可选字段,长度不固定,因此它指出TCP报文的数据距离TCP报文的起始处有多远。Resverved: 保留域、
注释:保留今后使用的,到目前都会被标为0-
TCP Flags:控制位
注释:主要由八个标志位来组成,每一个标志位表示一个控制功能。CEUAPRSF- URG:紧急指针标志,为1表示紧急指针有效,为0则忽略紧急指针
- ACK: 确认序号标志,为1表示确认号有效,为0表示报文中不含确认信息,忽略确认号信息,上确认号是否有效就是通过该标志位控制的。
- PSH:push标志,为1表示是带有push标志的数据,指示接收方在接到该报文以后应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
- RST:重置链接标志,用于重置因为主机崩溃或其他原因而出现错误的链接,或者用于拒绝非法的报文段和拒绝链接请求。
- SYN:同步序列号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而链接应答捎带一个确认即SYN=1和ACK=1。
- FIN:finish标志,用于释放连接,为1表示数据方已经没有数据发送了,即关闭本方数据流。
Windows: 窗口
注释:*滑动窗口的大小,用来告知发送端和接收端的缓存大小,以此控制发送发送数据我的速率,从而达到流量控制。Checksum:校验和
注释:指的是奇偶校验,此校验和是对整个TCP报文段包括TCP头部和TCP数据以16进行计算所得,又发送方计算存储,并由接收端进行验证。Urgent Pointer:紧急指针
注释:URG为1时有效,指出本报文中紧急数据的字节数。TCP Options:可选项
注释:长度可变,定义一些其他的可选参数。
当应用程序希望通过TCP与另一个应用程序通信时,它会发送一个通信请求,这个请求必须被送达一个确切的地址,在双方握手之后,TCP将在两个应用之间建立一个全双工的通信,将占用两个计算机之间的的通信线路直到他被一方或双方关闭为止。
4. 说说TCP的三次握手
“握手”是为了建立连接,TCP三次握手的流程如下:
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送SYN包(seq = x)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到SYN包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此时包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
- 客户端和服务器都出去CLOSED的状态。
- 客户端主动打开。
- 服务端被动打开,创建传输控制块TCB,时刻准备其他客户进程发送过来的连接请求,此时服务端进入LISTEN监听状态。
- 客户端创建传输控制块TCB,然后向服务器发出链接请求报文,即SYN=1,并选择一个初识序列号seq=x,x是任意的正整数值,客户端进入SYN-SENT同步已发送状态。此时发送过去的数据包即报文段会被称为SYN报文段,不能携带数据,但是要消耗掉一个序号,这是第一次握手。
- 服务器接收的请求报文之后,如果同意链接,则发出确认报文,确认报文中包含TCP Flags中两个位的字段,一个是ACK=1,另一个是SYN=1,这里的ack=x+1,并初始化一个序列号号seq=y,此时服务器就进入了SYN-RCVD同步已收到的状态,这个报文也不能携带数据,并且同样需要消耗一个序号,这是第二次握手。
- 客户端接收到确认报文之后,还要向服务器给出一个确认,确认报文的ACK=1,ack=y+1,seq=x+1,并且该ACK报文段可以携带数据,客户端就进入ESTAB-LISHED已建立连接的状态。
- 服务器接收到确认后也会进入到ESTAB-LISHED状态,双方就可以开始通信。
5. 为什么需要三次握手才能建立起来连接
为了初始化Sequence Number的初识值。通信的双方要互相通对方自己的初始化的Sequence Number,这个号要作为以后通信的序号,以保证应用层接收到的数据不会因为网络上的传输问题而乱序,即TCP会用这个序号来拼接数据,因此在服务器回发它的Sequence Number即第二次握手之后,还需要发送确认报文给服务器告知服务器说客户端已经收到你发送的Sequence Number了。
6. 首次握手的隐患---SYN超时
问题起因分析
- Server收到Client的SYN,回复SYN-ACK的时候,客户端掉线,未收到ACK确认,此连接将处于中间状态,即没有成功也没有失败。
- Server不断重试直至超时,Linux默认等待63秒才断开连接(1s => 2s => 4s => 8s => 16s => 32s)。
这会导致SYN Flood风险,恶意程序会给服务器发送SYN报文,发完就下线,于是服务器需要默认等63秒才会断开连接,这样攻击者就会将服务器的SYN连接的队列耗尽,让正常的连接请求不能处理。
针对SYN Flood的防护措施
- SYN队列满后,通过tcp_syncookies参数回发SYN Cookie
源地址端口,目标地址端口,和时间戳造出一个特别的Sequence Number回发回去,这个Sequence Number简称SYN Cookie - 若为正常连接则client会回发SYN Cookie,直接建立连接
7. 建立连接后,Client出现故障怎么办
保活机制
- 向对方发送保活探测报文,如果未收到响应则继续发送(经过一个提前设定好的包活时间间隔)
- 尝试次数达到保活探测次数仍未收到响应则中断连接
三、TCP的四次挥手
1. TCP采用四次挥手来释放连接
“挥手“是为了终止连接,TCP四次挥手的流程图如下(假设客户端主动触发CLOSE):
- 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态;
- 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到的序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态;
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态;
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到的序号+1,Server进入CLOSE状态,完成四次挥手。
2. 为什么会有TIME_WAIT(2MSL)状态
原因
- 确保有足够的时间让对方收到ACK包,如果对方没有收到ACK,对方就会触发被动端重发FIN包,一来一去正好是两个MSL。
- 避免新旧连接混淆,有些路由器会缓存ip数据包,如果连接被重用了,那么这些延迟收到的包就会跟新连接混在一起。
4. 为什么需要四次握手才能断开连接
全双工的意思是允许数据在两个方向上同时传输,即在同一时间服务器可以发送数据给客户端,客户端也可以发送数据给服务器。
因为全双工,发送方和接受方都需要FIN报文和ACK报文,也就是发送方和接受方各只需两次挥手即可,只不过有一方是被动的,所以就成了四次挥手。
5. 服务器出现大量CLOSE_WAIT状态的原因
问题的其中一个表现是客户端一直在请求,但是返回给客户端的信息是异常的,服务器压根也没有收到请求。
服务器保持大量的CLOSE-WAIT只有一个原因,就是在对方发送一个FIN报文之后,程序这边没有进一步发送ACK,或者FIN报文已确认,换句话说,就是对方关闭socket连接,我方忙于读或写,没有及时关闭连接。
- 检查代码,特别是释放资源的bug。
- 检查配置,特别是处理请求的线程配置。
一旦CLOSE-WAIT很多,就需要排查问题,因为对于linux服务器来说,会为每个用户分配优先的文件句柄数,而我们的连接也是和文件句柄一一对应的,而CLOSE-WAIT状态如果一直被保持,那么以为着对应数目的通道一直被占用着,一旦达到上限,则新的请求就无法被处理,接着就就是大量的too many open files异常,接着就是软件服务器的崩溃,极有可能让我们的tomcat,nginx,apache服务器崩溃掉。
四、TCP和UDP的区别
1. UDP简介
用户数据协议(User Datagram Protoco)
UDP报文结构
- Source Port:源端口
- Destination:目标端口
- Length:数据包长度
- Checksum:奇偶校验值
- data octets:用户数据
UDP的特点
简单的报文结构意味着UDP不像TCP那样支持错误重传,滑动窗口等精细控制。
- 面向非连接
UDP是一个非连接的协议,传输数据之前源端和终端无建立连接,当它想传送时,就简单的去抓取来自应用程序的数据,并尽可能快的把它扔到网络上,在发送端,UDP传送数据的速度,仅仅是受应用程序生成数据的速度,计算机的能力和传输带宽的限制,在接收端,UDP把每个消息段放到队列中,应用程序每次从应用中读取一个消息段。 - 不维护连接状态,支持同时像多个客户端传输相同的消息
由于传输数据不建立连接,因此,也就不需要维护连接状态,包括收发状态等,因此一台服务器可同时向多个客户机传输相同的消息。 - 数据包包头只有8个字节。额外开销较小
UPD信息包的包头很短,只有8个字节,相对于TCP的20个字节信息包的额外开销小很多。 - 吞吐量只受限于数据生成速率、传输速率以及机器性能。
吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。 - 尽最大努力交付,不保证可靠交付,不需要维持复杂的连接状态表
- UDP面向报文,不对应用程序提交的报文信息进行拆分或者合并
发送方对应用程序交下来的报文在添加首部后就向下交付给IP层,既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小,也就是说UDP将绝大多数的控制交由我们的上层去解决。
2. TCP和UDP的区别
TCP和UDP是OSI中的运输层协议,TCP提供可靠的通信传输;UDP常被用于让网络和细节控制交给应用层的通信传输。
- 面向连接 VS 无连接
TCP有三次握手的过程;UDP适合消息的多播发布,从单个点向多个点传输信息 - 可靠性
TCP是可靠的,利用握手、确认和重传机制提供了可靠性保证;而UDP则可能会丢失,不知道到底有没有被接收 - 有序性
TCP利用序列号保证了消息包的顺序交付,到达可能无序,但TCP最终会排序;而UDP不具备有序性。 - 速度
TCP速度比较慢,因为要创建连接,保证消息的可靠性和有序性等,需要做额外的很多事情;UDP则更适合做对速度比较敏感的应用,比如在线视频媒体,电视广播,多人在线游戏等。 - 量级
TCP属于重量级的;UDP属于轻量级;体现在源数据的头大小。
五、TCP的滑动窗口
1. RTT和RTO
RTT:发送一个数据包到收到对应的ACK,所花费的时间
简单来说就是我发送一个数据包,对方回一个ACK,当我接到ACK之后就能计算出从我发出包到接到回应过了多久,这个时间就是RTT,RTT的计算很简单,就是一个时间差。
RTO:重传时间间隔
TCP在发送一个数据包之后,会启动一个重传定时器,而RTO就是这个定时器的重传时间。 再通俗的讲就是我一开始预先算一个定时器时间,如果你回复了ACK,那重传定时器就自动失效,也就是说不用重传了,如果没有回复给我ACK,然后RTO定时器的时间又到了,我就重传。
由于RTO是本次发送当前数据包所预估的超时时间,那么RTO就需要一个很好的算法来统计,来更好的预测这次超时时间,RTO不是固定写死的配置,而是经过RTT计算出来的,有了RTT才能计算出RTO。基于RTO,我们便有了重传机制,才能支撑起滑动窗口。
2. 滑动窗口
前面我们提到,TCP要将数据拆分成段进行发送,出于效率和传输速度的考虑,我们不可能等一段一段数据去发送,等到上一段数据被确认之后再发送下一段数据,这个效率是非常低的,我们是要实现对数据的批量发送,TCP必须要解决可靠传输以及包乱序的问题,所以TCP需要知道网络实际的数据处理带宽或是数据处理速度,这样,才不会引起网络拥塞,导致丢包。
TCP使用滑动窗口做流量控制与乱序重排
- 保证TCP的可靠性
- 保证TCP的流控特性
前面我们学的TCP报文头里面有一个字段叫做Window,用于接收方通知发送方自己还有多少缓冲区可以接受数据,发送方根据接受方的处理能力来发送数据,不会导致接收方处理不过来,这便是流量控制。
同时,滑动窗口机制还体现了TCP面向字节流的设计思路。
窗口数据的计算过程
如图所示,左图是TCP协议的发送端缓冲区,右图是接收端缓冲区,左边往右边发数据,两个图中下面的长方形表示要发送的数据流,里面假设装满了数据,并且需要按照顺序从左向右发送或者接收,假设对应的数据段位置序号也是从左到右去增长的。
对于发送方来讲
- LastByteAcked指向收到的连续最大的ACK的位置,也就是从左端算起连续已经被接收端的程序发送ACK回执确认已收到的SeqNum
- LastByteSent指向已发送的最后一个字节的位置,该位置只是发出去了但是还没有收到ACK的回应
- LastByteWritten指向上层应用已写完的最后一个位置,即当前程序已经准备好的需要发送的最新的一个数据段。
对于接受方来讲
- LastByteRead指向上层应用已经读完的最后一个字节的位置
- 而NextByteExpected指向收到的连续最大的Seq的位置,即已经收到但是还没有发送回执
- 而LastByteRcvd指向已收到的最后一个字节的位置
可以看到NextByteExpected和LastByteRcvd中间有一些Seq还没有到达,对应的是空白的区域,此时,咱们可以根据上面的数值计算出接收方的AdvertisedWindow的大小,之后回发给发送方让其计算出发送方的剩余可发送的数据大小,即EffectiveWindow的大小。
- AdvertisedWindow=MaxRcvBuffer-(LastByteRcvd - LastByteRead)
- MaxRcvBuffer: 接收方能接收的最大数据量,接收端缓存池的大小。
- LastByteRcvd - LastByteRead: 接收方已为接收到的数据或者还没有接收到的预订数据留出来的空间,当前这些空间已经占据了一定的缓存。
我们将最大的缓存减去这些已经占据的缓存就得出我们还能够接收的数据量,继而我们就能够将AdvertisedWindow告知发送端,而发送方根据ACK中的AdvertisedWindow的值就需要保证LastByteSent-LastByteAcked<=AdvertisedWindow,即已发送且待确认的数据量要小于接收方的Window大小
- EffectiveWindow = AdvertisedWindow - (LastByteSent - LastByteAcked)
3. TCP的滑动窗口基本原理
TCP会话的发送方
对于TCP会话的发送方,任何时候在其发送缓存内的数据都可以分为4类
- 已经发送并且得到端的回应的。
- 已经发送但还没有得到端的回应的
- 未发送但对端允许发送的
- 未发送且由于达到了window的大小对端不允许发送的数据
2,3组成的连续空间称为发送窗口
当收到接收方新的ACK对于发送窗口中后续字节的确认时,发送窗口就会进行滑动,滑动原理如图所示。
我们先从原滑动窗窗口说起,由虚线部分组成,前面我们已经知道,滑动窗口里面包含了已经发送但是还没有收到接收端已经确认的数据,以及还没有发送,但是允许向接收方发送的数据,咱们假设原先的滑动窗口的边界是从32到51,我们假设已发送但未被确认的序号是32到40,而41到51未发送但对端允许发送的,此时只有收到对端大于32的序号的ACK,即收到32到40之间的某个ACK序号回执的时候,咱们的滑动窗口才会发生移动,而此时序号大于或者等于52的数据,即窗口外的数据是不能发送的,这里我们假设收到了对端序号为36的ACK回执,则滑动窗口会向右移动4位到36这个地方,继而我们的程序就能发送序号为52到55的数据了。
TCP会话的接收方
对于TCP会话的接收方来讲,在某一时刻,在他的接收缓存内会存在三种状态
- 已接收并且已发送回执的状态
- 未接收但是可以接收也就是准备接收的状态
- 未接收并且不能接收的状态,因为达到了窗口的阈值了
由于ACK直接由TCP栈回复,默认没有应用延迟,不存在已接收但是未回复ACK的这种状态,其中未接收并且准备接收的这段空间就称为接收窗口,由于接收窗口的滑动机制和前面发送方的一致,这里不作重复讲解。
TCP最基本的传输可靠性来源于确认重传机制,TCP的滑动窗口的可靠性也是建立在确认重传基础上的,发送窗口只有收到接收端对于本段发送窗口内字节的ACK确认才会移动滑动窗口的左边界,接收窗口只有在前面所有段都确认的情况下,才会移动左边界,当在前面还有字节未接收但收到后面字节的情况下,窗口是不会移动的,并不对后续字节确认,以此确保对端会对这些数据进行重传,以上便是滑动窗口的基本原理。
滑动窗口的大小可以依据一定的策略进行动态调整,例如会根据自身处理能力的变化通过本端TCP接收窗口大小的控制来实现对端的发送窗口进行流量限制。
六、HTTP相关
1. HTTP简介
超文本传输协议,是属于应用层的协议,是一个基于响应与请求无状态的应用层的协议,常基于TCP的连接方式。HTTP1.1中给出一种持续连接的方式,keeplive,绝大多数的web开发都数是构建在HTTP协议之上的web应用。
超文本传输协议的主要特点
-
支持客户/服务器模式
HTTP协议工作于客户端/服务端架构之上,浏览器作为HTTP客户端通过url向HTTP服务端向Web服务器发送所有请求,Web服务器根据接收到的请求向客户端发送响应信息。
- 简单快速
客户端向服务器请求服务的时候,只需传送请求方法和路径,请求方法常用的有GET,POST等,每种方法规定了客户端与服务器联系的类型不同,由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。 - 灵活
HTTP允许传输任意类型的数据对象,正在传输的类型由Content-type加以标记。 - 无连接
限制每次连接只处理一个请求,服务器处理完客户的请求并收到客户的应答之后即断开连接,采用这种方式可以节省传输时间,从HTTP1.1起,默认使用了长连接,即服务器需要等待一定时间后,才断开连接,以保证连接特性,虽然目前的一些技术如keeplive使用了长连接优化效率,但这些都是属于HTTP请求之外的,也就是说在每个独立的HTTP请求中,你是无法知道当前的HTTP是否处于长连接的状态,你始终得认为HTTP请求在结束后连接就会关闭,这是HTTP的特性,至于下层实现是否在请求结束后关闭连接,都不会改变这个特性,长连接可以理解为下层实现对上层透明。 - 无状态
HTTP协议是无状态协议,无状态是指协议对于事物处理没有记忆能力,缺少状态意味着后续处理需要前面信息则必须被重传这样可能导致每次传送的数据量增大,另一方面在服务器不需要先前信息时,他的应答就较快。
2. HTTP请求结构
请求行,请求头部,空行,数据四个部分组成。
请求行
- 请求方法:POST,GET,PUT等等
- URL
- 协议版本:HTTP1.0、HTTP1.1等等
请求头部(若干个)
- 名字: 值
名字是大小写无关的,这些报头用于设置HTTP请求的一些参数,例如HOST表示被请求资源的主机和端口号,Content-Type等等
空行
必须要有幂级数数据为空,用空行来表示头部发送结束。
请求的数据
只在POST里面用到,表示要上传的数据体,和头部之间有个空行
3. HTTP响应结构
发送请求报文后,正常的情况下能收到其响应报文。服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息,即我们的响应报文。
状态行,响应头部,响应正文
状态行
- 协议版本
- 状态码
- 状态码描述
- 换行符
响应头部(若干个)
- 头部字段名: 值
响应正文
4. HTTP简介
HTTP协议定义了Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端,HTTP协议采用了请求响应模型,客户端向服务器发送一个请求报文,请求报文包含一个请求的方法,URL,协议版本,请求头部,和请求数据,服务器以一个状态行作为响应,响应的内容包括协议的版本,成功或者错误代码,服务器信息,响应头部和响应数据。
5. 请求/响应步骤
- 客户端连接到Web服务器;
- 发送HTTP请求;
- 服务器接收并返回HTTP响应;
- 释放连接TCP连接;
- 客户端浏览器解析HTML内容。
6. 在浏览器地址键入URL,按下回车之后经历的流程?
- DNS解析
首先浏览器会依据url逐层查询DNS服务器缓存,解析url中的域名所对应的ip地址,DNS从近到远依次是浏览器缓存->系统缓存->路由器缓存->IPS服务器缓存->根域名服务器缓存->顶级域名服务器缓存,从哪个缓存找对对应的ip则直接返回,不在查询后面的缓存。 - TCP连接
找到ip地址之后,会根据该ip地址和对应端口(默认80)和服务器建立TCP连接。 - HTTP请求
浏览器发出读取文件的HTTP请求,该请求将发送给服务器 - 服务器处理请求并返回HTTP报文
服务器对浏览器请求作出相应,并把对应的带有html文本的http响应报文发送给浏览器。 - 浏览器解析渲染页面
- 连接结束
7. HTTP状态码
五中可能取值
- 1xx:指示信息--表示请求已接收,继续处理;
- 2xx:成功--表示请求已被成功接收、理解、接受;
- 3xx:重定向--要完成请求必须进行更进一步的操作;
- 4xx:客户端错误--请求有语法错误或请求无法实现;
- 5xx:服务器错误--服务器未能实现合法的请求。
常见状态码
- 200 OK:正常返回信息;
- 400 Bad Request:客户端请求有语法错误,不能被服务器所理解;
- 401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用;
- 403 Forbidden:服务器收到请求,但是拒绝提供服务;
- 404 Not Found:请求资源不存在;
- 500 Internal Server Error:服务器发生不可预期的错误;
- 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常。
8. GET请求和POST请求的区别
从三个层面来解答
Http报文层面:GET将请求信息放在URL后面,请求信息和URL之间以问号隔开,请求信息的格式为键值对,POST放在报文体中,想获得请求信息必须获得报文。
因此安全性较GET要高一些。但要获得报文体中的请求信息也很容易,安全性上其实无太多区别。具体解决传输过程中的安全问题,还要靠https。数据库层面:GET符合幂等性和安全性,POST不符合。
幂等性:对数据进行一次操作或者多次操作的结果是一致的,则认为符合幂等性。
安全性:对数据的操作没有改变数据库中的数据,则认为符合安全性。其他层面:GET可以被缓存、被存储,而POST不行。
9. Cookie和Session的区别
Cookie简介
Cookie技术是客户端的解决方案
- 是由服务器发给客户端的特殊信息,以文本的形式存放在客户端。
客户端每次向服务器发送请求的时候都会带上这些特殊的信息(Http响应头中)。 - 客户端再次请求的时候,会把Cookie回发
- 服务器接收到后,会解析Cookie生成与客户端相对应的内容。
Cookie的设置以及发送过程
Session简介
- 服务器端的机制,在服务器上保存的信息。
- 解析客户端请求并操作session id,按需求保存状态信息。
Session的实现方式
-
使用Cookie来实现
使用URL回写来实现
服务器在发送给浏览器的所有链接中都写到sessionId的参数,这样客户端点击任何一个链接都会把sessionId带回服务器。如果直接输入url来请求资源,那么session是匹配不到的。
Tomcat存在Cookie和url回写两种机制,如果支持Cookie就使用Cookie,如果不支持Cookie就一直使用url回写。
Cookie和Session的区别
- Cookie数据存放在客户的浏览器上,Session数据放在服务器上;
- Session相对于Cookie更安全
- 若考虑减轻服务器负担,应当使用Cookie。
七、HTTP和HTTPS的区别
1. HTTPS简介
超文本传输安全协议
HTTPS是一种以计算机网络安全通信为目的的传输协议,在HTTP下面加入了SSL层,从而具有了保护交换数据隐私以及完整性,还有提供了对于网站服务器身份认证的功能,简单来说,它就是安全版的HTTP协议。
2. SSL(Security Sockets Layer,安全套接层)
- 为网络通信提供安全及数据完整性的一种安全协议;
- SSL位于TCP与各应用层之间,是操作系统对外的API,SSL3.0后更名为TLS;
- 采用身份验证和数据加密保证网络通信的安全和数据的完整性
3. 加密的方式
- 对称加密:加密和解密都使用同一个密钥。
此种加密效率高; - 非对称加密:加密使用的密钥和解密使用的密钥是不同的。
分别称为公钥和私钥,公钥和算法都是公开的,私钥是保密的。此种算法效率较低,但是安全性超强。由于其加密特性,其加密长度有限。如区块链技术很多使用非对称加密。 - 哈希算法:将任意长度的信息转换为固定长度的值,算法不可逆。
常见的是MD5算法。 - 数字签名:证明某个消息或者文件是某人发出/认同的
签名就是在信息的后面加上一段内容,这些内容是经过哈希后的值,可以证明信息没有被修改过。
哈希值都会经过哈希后也就是签名后和信息一起发送,以保证这个哈希值不被修改。
4. HTTPS数据传输流程
HTTPS使用证书加各种加密方式进行加密。
HTTPS在进行数据传输之前,会与网站服务器与Web浏览器进行一次握手,在握手时确定双方的加密密码信息,具体过程如下:
- 浏览器将支持的加密算法信息发送给服务器(A1S对称加密,ECC对称加密);
- 服务器选择一套浏览器支持的加密算法,以证书的形式回发浏览器(证书发布的CA机构,证书发布的有效期,公钥,证书所有者,签名等等);
注:CA机构是具备证书颁发资格的权威机构。 - 浏览器验证证书合法性,并结合证书公钥加密信息发送给服务器;
如果证书受到浏览器信任,则在地址栏会有标志性显示,否则就会显示不受信的标识。当证书受信之后,Web浏览器会随机生成一串密码,并使用证书中的公钥加密,之后就是使用约定好的哈希算法握手消息并生成随机数对消息进行加密,再将之前生成的消息回发给服务器。 - 服务器使用私钥解密信息,验证哈希,加密响应消息回发浏览器;
- 浏览器解密响应消息,并对消息进行验真,之后进行加密交换数据。
5. HTTP和HTTPS的区别
- HTTPS需要到CA申请CA申请证书,HTTP不需要;
- HTTPS密文传输,HTTP明文传输;
- 连接方式不同,HTTPS默认使用443端口,HTTP使用80端口;
- HTTPS=HTTP+加密+认证+完整性保护,较HTTP安全。
6. HTTPS真的安全么?
- 浏览器默认填充http://,请求需要进行跳转,有被劫持的风险;
- 可以使用HSTS(HTTP Strict Transport Security)优化。
八、Socket相关
1. Socket简介
两个进程需要通信,最基本的一个前提是能够唯一的标识一个进程,在本地进程通信中,我们可以使用pid来唯一标识一个进程,但pid只在本地唯一,网络中的两个进程pid冲突的几率还是有的,这时候我们需要另辟蹊径了,我们知道ip层的ip地址可以唯一标识一台主机,而TCP协议和端口号可以唯一标识主机的一个进程,这样,我们可以利用ip地址+协议+端口号来唯一标识网络中的一个进程,能够唯一标识网络中的进程后,他们就可以利用Socket进行通信了。
什么是Socket呢?
Socket是对TCP/IP协议的抽象,是操作系统对外开放的接口。
2. Socket通信流程
服务器首先创建socket,为socket绑定ip地址和端口号,服务器监听端口号的请求,随时准备接收客户端发来的连接,这时候服务器的socket只是listen并没有打开,此时我们假设客户端创建并打开了socket,并根据服务器的ip地址和端口号尝试去连接服务器的socket,服务器的socket接收到客户端的socket请求被动打开开始接收客户端的请求,直到客户端返回连接信息,这时候服务器的socket进入到阻塞状态(accept方法需要一直等待客户端返回连接信息后才返回,同时开始接收下一个客户端的连接请求),客户端在连接成功之后就会向服务器发送连接状态信息,服务端在接收到客户端的连接信息之后就会将accept方法返回,并提示连接成功,之后客户就可以向socket写入信息了,服务器就能收到并且读取相关的信息,最后在发送完数据后,客户端就会关闭socket,紧接着服务端也要关闭socket。以上就是整个socket通信流程。
3. Socket相关面试题
编写一个网络应用程序,有客户端与服务器端,客户端向服务器发送一个字符串,服务器收到该字符串后将其打印到命令行上,然后向客户端返回该字符串的长度,最后,客户端输出服务器端返回的该字符串的长度,分别用TCP和UDP两种方式去实现。
SocketUtil.java
import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author ShiXinXin
* @date 2019-09-11 20:07
*/
public class SocketUtil {
/**
* 输入流转String
*
* @param inputStream 输入流
* @return 流对应的字符串
*/
public static String getStringFromStream(InputStream inputStream) {
byte[] buff = new byte[1024];
int len = 0;
try {
len = inputStream.read(buff);
} catch (IOException e) {
e.printStackTrace();
return "";
}
return new String(buff, 0, len);
}
/**
* 对象转byte数组
*
* @param object 对象
* @return 对应的byte数组
*/
public static byte[] getByteFromObject(Object object) {
return String.valueOf(object).getBytes();
}
/**
* 从数据包中读取数据
*
* @param packet 数据包
* @return 其中的字符串
*/
public static String getStringFromPacket(DatagramPacket packet) {
byte[] data = packet.getData();
return new String(data, 0, packet.getLength());
}
/**
* 创建upd发送数据包
*
* @param object 数据
* @param ip ip地址
* @param port 端口号
* @return 数据包
*/
public static DatagramPacket createSendDatagramPacket(Object object, String ip, int port) {
byte[] buff = String.valueOf(object).getBytes();
InetAddress address = null;
try {
address = InetAddress.getByName(ip);
} catch (UnknownHostException e) {
throw new RuntimeException("参数错误");
}
return new DatagramPacket(buff, buff.length, address, port);
}
/**
* 创建upd发送数据包
*
* @param object 发送对象
* @param packet 数据包
* @return 数据包
*/
public static DatagramPacket createSendDatagramPacket(Object object, DatagramPacket packet) {
byte[] buff = String.valueOf(object).getBytes();
return new DatagramPacket(buff, buff.length, packet.getAddress(), packet.getPort());
}
/**
* 创建接收数据包
*
* @return 数据包
*/
public static DatagramPacket createReceiveDatagramPacket() {
byte[] data = new byte[100];
return new DatagramPacket(data, data.length);
}
}
1. TCP实现
TcpServer.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author ShiXinXin
* @date 2019-09-11 19:33
*/
public class TcpServer {
public static void main(String[] args) throws IOException {
// 初始化一个Socket
ServerSocket serverSocket = new ServerSocket(65000);
while (true) {
// 开始接受数据
Socket socket = serverSocket.accept();
// 接受到数据创建一个线程继续接收
new LengthCalculator(socket).start();
}
}
}
LengthCalculator.java
import com.xinxin.socket.utils.SocketUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* @author ShiXinXin
* @date 2019-09-11 19:35
*/
public class LengthCalculator extends Thread {
private Socket socket;
public LengthCalculator(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 获取socket的输入流与输出流
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
// 从输入流读数据
String content = SocketUtil.getStringFromStream(inputStream);
System.out.println(content);
// 将要发送的数据写入输出流
outputStream.write(SocketUtil.getByteFromObject(content.length()));
// 关闭所有链接
inputStream.close();
outputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
TcpClient.java
import com.xinxin.socket.utils.SocketUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import static com.xinxin.socket.utils.SocketUtil.getByteFromObject;
/**
* @author ShiXinXin
* @date 2019-09-11 19:40
*/
public class TcpClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 65000);
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
// 将要发送的数据写入输出流
outputStream.write(SocketUtil.getByteFromObject("Hello World"));
// 打印输入流得到的数据
System.out.println(SocketUtil.getStringFromStream(inputStream));
// 关闭所有链接
inputStream.close();
outputStream.close();
socket.close();
}
}
2. UDP实现
UdpServer.java
import com.xinxin.socket.utils.SocketUtil;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* @author ShiXinXin
* @date 2019-09-11 19:54
*/
public class UdpServer {
public static void main(String[] args) throws IOException {
// 创建udp Socket
DatagramSocket socket = new DatagramSocket(65001);
while (true) {
// 初始化一个接收包
DatagramPacket packet = SocketUtil.createReceiveDatagramPacket();
// 使用packet接收数据
socket.receive(packet);
// 接收到数据则初始化一个线程
new LengthCalculator(socket, packet).start();
}
}
}
LengthCalculator.java
import com.xinxin.socket.utils.SocketUtil;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @author ShiXinXin
* @date 2019-09-11 20:08
*/
public class LengthCalculator extends Thread {
private DatagramSocket socket;
private DatagramPacket packet;
public LengthCalculator(DatagramSocket socket, DatagramPacket packet) {
this.socket = socket;
this.packet = packet;
}
@Override
public void run() {
try {
// 从包中获取数据
String content = SocketUtil.getStringFromPacket(packet);
System.out.println(content);
// 创建一个数据包,并发送数据包
socket.send(SocketUtil.createSendDatagramPacket(content.length(), packet));
} catch (IOException e) {
e.printStackTrace();
}
}
}
UdpClient.java
import com.xinxin.socket.utils.SocketUtil;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @author ShiXinXin
* @date 2019-09-11 20:00
*/
public class UdpClient {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket();
DatagramPacket packet = SocketUtil.createSendDatagramPacket("你好,世界!", "127.0.0.1", 65001);
socket.send(packet);
DatagramPacket receivedPacket = SocketUtil.createReceiveDatagramPacket();
socket.receive(receivedPacket);
String content = SocketUtil.getStringFromPacket(receivedPacket);
System.out.println(content);
}
}
九、网络知识总结
OSI七层架构
TCP/IP协议
1. TCP
(1). 三次握手
- SYN Flood攻击
- TCP报文头
- 为什么要进行三次握手
(2). 四次挥手
- TIME_WAIT
- CLOSE_WAIT
- 为什么要进行四次挥手