# Nodejs网络及部署
# 1、网络模型
# 1、osi七层模型
- 第7层应用层:为操作系统或网络应用程序提供访问网络服务的接口。
- 协议包括:https、http、ftp、telnet、ssh、smtp、pops、dns等
- html
- 第6层表示层:把数据转换为接受者能够兼容并且传输的内容,比如数据加密、压缩、格式转换等。
- 第5层会话层:负责数据传输中设置和维持网络设置直接的通讯连接。管理主机之间的会话进程,还可以利用在数据中插入校验点来实现数据同步。
- 第4层传输层:把传输表头加至数据形成数据包,完成端到端的数据传输,传输表头包含协议等信息。
- 协议包括:tcp、udp协议
- 第3层网络层:负责对子网间的数据包进行寻址和路由选择,还可以实现阻塞控制和网际互联等功能。
- 协议包括:ip、ipx等
- 路由器
- 第2层数据链路层:在物理介质上提供可靠的传输,包括物理地址寻址、数据封装成帧、流量控制、数据校验、重发等
- 交互机
- 第1层物理层:在局域网传输数据帧,负责电脑通讯设备和网络媒体之间的互通,包括针脚、电压、线缆规范、集线器、网卡、主机适配。
# 2、TCP/IP参考模型
- 应用层:应用层、表示层、会话层
- 传输层:传输层
- 网络层:网络层
- 链路层:数据链路层、物理层
# 3、面试题
协议是什么?
定义每一层的作用和职责,类似于规范。
TCP/IP协议具体指什么?
互联网通讯所需要的协议家族,以TCP、IP为核心,包含HTTP、SMTP、TELNET等各种协议
数据包是什么?
网络层及以上分层中包的单位。每个分层都会对发送的数据添加一个首部,首部包含了该层协议相关的信息,真正发送的内容称为数据,即每个数据包为首部+数据。对于下层来说,上层发送的全部内容,都会被当作本层的数据。
- 传输层TCP包:TCP包首部 + 数据
- 网络层IP包:IP包首部 + (TCP包首部 + 数据)
- 数据链路层以太网包:以太网包首部 + (IP包首部 + (TCP包首部 + 数据))
每次在接收数据后除了添加首部还会做什么?
用户1
- 传输层:TCP模块为保证数据的可靠传输, 需要添加TCP首部
- 网络层:IP包生成后,参考路由控制表决定接受此 IP 包的路由或主机。
- 数据链路层:生成的以太网数据包将通过物理层传输给接收端
用户2
- 数据链路层:主机收到以太网包后,首先从以太网包首部找到 MAC 地址判断是否为发送给自己的包,若不是则丢弃数据。 如果是发送给自己的包,则从以太网包首部中的类型确定数据类型,再传给相应的模块,比如IP.
- 网络层:从包首部中判断此 IP 地址是否与自己的 IP 地址匹配,如果匹配则根据首部的协议类型将数据发送给对应的模块,比如TCP
- 传输层:在 TCP 模块中,首先会计算一下校验和,判断数据是否被破坏。然后检查是否在按照顺序接收数据。最后检查端口号,确定具体的应用程序。数据被完整地接收以后,会传给由端口号识别的应用程序。
总结一下几个地址:
- 数据链路层的是MAC地址, 用来识别同一链路中的不同计算机
- 网络层的是IP地址, 用来识别TCP/IP 网络中互连的主机和路由器
- 传输层的是端口号(程序地址), 用来识别同一台计算机中进行通信的不同应用程序
通过MAC地址、IP地址、端口号是否可识别一次通讯?
答案是否定的,我们需要通过以下这几个数据综合来识别一次通信:
- IP首部:源IP地址
- IP首部:目标IP地址
- 协议号, TCP或者UDP
- TCP首部:源端口号
- TCP首部:目标端口号
# 2、TCP和UDP协议
# 1、区别和场景
传输层协议:
UDP是⽆连接的,TCP必须三次握⼿建⽴连接
UDP是⾯向报⽂,没有拥塞控制,所以速度快,适合多媒体通信要求,⽐如及时聊天,⽀持⼀对⼀,⼀对多,多对⼀,多对多。就像⽜客⽹的视频⾯试就是⽤的UDP
TCP只能是⼀对⼀的可靠性传输
DETAILS
面试题:网络直播底层是什么协议
分析:其实现在常⻅的rtmp(实时消息传输协议)和hls(HTTP Live Streaming)直播, 都是基于TCP的, 希望能提供稳定的直播环境.
# 2、TCP协议
# 1、可靠性保证
- 超时重发,发出报文端要是没有收到及时的确认,会重发
- 数据包的校验,校验首部数据和
- 对失序的数据重新排序
- 进行流量控制,防止缓冲区溢出
- 快重传和会恢复
- TCP会将数据截断为合理的长度
# 2、拥堵控制
拥塞控制就是防止过多的数据注入网络中,这样可以使网络中的路由器或链路不致过载。
发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量和慢开始门限ssthresh状态变量:初始设置ssthresh=16,cwnd=1.
当cwnd<ssthresh时,使⽤慢开始算法,也就是乘法算法,拥塞窗口 cwnd 随着传输次数按指数规律增长。
当cwnd=ssthresh时,慢开始与拥塞避免算法任意。
当cwnd>ssthresh时,改⽤拥塞避免算法,也就是加法算法,拥塞窗口按线性规律增长。
当出现拥塞的时候就把新的门限值ssthresh设为此时窗口大小cwnd的一半,窗口大小设置为1,再重新执行上面的步骤。eg:cwnd为24时拥堵,设置ssthresh为12,cwnd为1,再开始慢开始算法。
当收到连续三个重传的时候这就需要快重传和快恢复了,当收到连续三个重传,这个时候发送方就要重传自己的信息,然后门限ssthresh减半,但是这个时候并不是网络阻塞,窗口cwnd只会减半,执行拥塞避免算法。
# 3、三次握手、四次挥手
TCP协议的一次数据传输, 从建立连接到断开连接都有哪些流程?
- 三次握手
- 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
- 第二次握手:服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
- 第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。完成了三次握手,客户端和服务器端就可以开始传送数据。
- 四次挥手
- 第一次挥手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
- 第二次挥手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求;
- 第三次挥手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
- 第四次挥手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
DETAILS
面试题:为什么进行三次握手和四次挥手?
解析:
- 三次握手:为了解决“网络中存在延迟的重复分组”的问题(为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误),另一种描述,为了在不可靠信道上可靠地传输信息。
- 四次挥手:为实现TCP这种全双工连接的可靠释放;为使旧的数据包在网络因过期而消失。
# 3、IP地址
网络层协议
IP 地址(IPv4 地址)由32位正整数来表示,在计算机内部以二进制方式被处理。
- 将32位的 IP 地址以每8位为一组,分成4组,每组以 “.” 隔开,再将每组数转换成十进制数(0-255)。
- IP地址包含网络标识和主机标识,eg:172.112.110.11,172.112.110就是网络标识,,同一网段内网络标识必须相同。11就是主机标识,,同一网段内主机标识不能重复。
IPv6:解决 IPv4 地址耗尽的问题而被标准化的网际协议。IPv4 的地址长度为 4 个 8 位字节,即 32 比特,而 IPv6 的地址长度则是原来的 4 倍,即 128 比特,一般写成 8 个 16 位字节。通常写成8组,每组为四个十六进制数的形式。比如:AD80:0000:0000:0000:ABAA:0000:00C2:0002
# 4、DNS
应用层协议:DNS协议是用来将域名转换为IP地址(也可以将IP地址转换为相应的域名地址)
- DNS使用TCP和UDP端口53。
- 对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。
客户端发送查询报文"query zh.wikipedia.org"至DNS服务器:
DNS服务器首先检查自身缓存,如果存在记录则直接返回结果。如果记录老化或不存在,则执行如下步骤:
DNS服务器向根域名服务器发送查询报文"query zh.wikipedia.org",根域名服务器返回顶级域 .org 的顶级域名服务器地址。
DNS服务器向 .org 域的顶级域名服务器发送查询报文"query zh.wikipedia.org",得到二级域 .wikipedia.org 的权威域名服务器地址。
DNS服务器向 .wikipedia.org 域的权威域名服务器发送查询报文"query zh.wikipedia.org",得到主机 zh 的A记录,存入自身缓存并返回给客户端。
# 5、HTTP
HTTP协议(HyperText Transfer Protocol,超⽂本传输协议)是⽤于从WWW服务器传输超⽂本到本地浏览器的传输协议。HTTP是客户端浏览器或其他程序与Web服务器之间的应⽤层通信协议。在Internet上的Web服务器上存放的都是超⽂本信息,客户机需要通过HTTP协议传输所要访问的超⽂本信息。
# 1、URI和URL
- URI:uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。
- URL:Uniform Resource Locator,统⼀资源定位符,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源,常用URL标识每个⽹⻚Internet地址。
# 2、完整的http通讯
建⽴ TCP 连接,⼀般 TCP 连接的端⼝号是80
客户端向服务器发送请求命令,如GET/info.html HTTP/1.1
客户端发送请求头信息,如user-agent,host等关于自身的信息,最后发送空⽩⾏来通知服务器,它已经结束了该头信息的发送;若为post请求,会继续发送请求体。
服务器应答,响应的第⼀部分是协议的版本号和响应状态码,例如: HTTP/1.1 200 OK
服务器返回响应头信息,关于⾃身的信息及被请求的⽂档;
服务器向客户端发送数据:发送头信息后,它会发送⼀个空⽩⾏来表示头信息的发送到此为结束,接着它就以 Content-Type 响应头信息所描述的格式发送⽤户所请求的实际数据;
服务器关闭 TCP 连接:⼀般情况下,⼀旦服务器向客户端返回了请求数据,它就要关闭 TCP 连接,然后如果客户端或者服务器在其头信息加⼊了这⾏代码 Connection:keep-alive ,TCP 连接在发送后将仍然保持打开状态,于是,客户端可以继续通过相同的连接发送请求。保持连接节省了为每个请求建⽴新连接所需的时间,还节约了⽹络带宽。
# 3、HTTP协议特点
通过请求和响应的交换达成通信:协议规定, 请求从客户端发出, 服务端响应请求并返回.
无状态:协议对于发送过的请求或响应都不做持久化处理
使用Cookie做状态管理:服务端返回的头信息上有可能会携带Set-Cookie, 那么当客户端接收到响应后, 就会在本地种上cookie. 在下一次给服务端发送请求的时候, 就会携带上这些cookie。
通过URL定位资源
方法标识意图:比如GET POST PUT DELETE等
持久连接:HTTP 协议的初始版本中,每进行一个 HTTP 通信都要断开一次 TCP 连接,增加了很多没必要的建立连接的开销。为了解决上述 TCP 连接的问题,HTTP/1.1 支持持久连接。其特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。旨在建立一次 TCP 连接后进行多次请求和响应的交互。在 HTTP/1.1 中,所有的连接默认都是持久连接,也就是说默认情况下建立 TCP 连接不会断开,只有在请求报头中声明 Connection: close 才会在请求完成后关闭连接。
管道机制:1.1版本引入pipelining机制, 即在同一个TCP连接里面,客户端可以同时发送多个请求。
举例来说,客户端需要请求两个资源。以前的做法是,在同一个TCP连接里面,先发送A请求,然后等待服务器做出回应,收到后再发出B请求。管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B请求。但是现代浏览器一般没开启这个配置, 这个机制可能会造成队头阻塞. 因为响应是有顺序的, 如果一个TCP连接中的第一个HTTP请求响应非常慢, 那么就会阻塞后续HTTP请求的响应,所以现实中默认情况下, 一个TCP连接同一时间只发一个HTTP请求.
DETAILS
面试题1:chrome最大支持6个同域名请求呢?
chrome最大支持同时开启6个TCP连接。
# 4、HTTP1.0/1.1/2.0
# 1、并发强求区别
- HTTP1.0:每次TCP连接只能发送一个请求,当服务器响应后就会关闭这次连接,下一个请求需要再次建立TCP连接.
- HTTP1.1:
- 默认采用持续连接(TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive);
- 增加了管道机制,在同一个TCP连接里,允许多个请求同时发送,增加了并发性,进一步改善了HTTP协议的效率,但是同一个TCP连接里,所有的数据通信是按次序进行的。回应慢,会有许多请求排队,造成"队头堵塞"。
- HTTP2.0
- 加了双工模式,即不仅客户端能够同时发送多个请求,服务端也能同时处理多个请求,解决了队头堵塞的问题。
- 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。
- 增加服务器推送的功能,不经请求服务端主动向客户端发送数据。
# 2、其他问题
HTTP1.1⻓连接和HTTP2.0多路复⽤的区别?
- HTTP1.1:同⼀时间⼀个TCP连接只能处理⼀个请求, 采⽤⼀问⼀答的形式, 上⼀个请求响应后才能处理下⼀个请求. 由于浏览器最⼤TCP连接数的限制, 所以有了最⼤并发请求数的限制。
- HTTP2.0:同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接⽽带来的延时和内存消耗。单个连接上可以并⾏交错的请求和响应,之间互不⼲扰。
为什么HTTP/1.1不能实现多路复⽤?
HTTP2是基于⼆进制“帧”的协议,HTTP1.1是基于“⽂本分割”解析的协议。
- HTTP1.1的报⽂结构中, 服务器需要不断的读⼊字节,直到遇到换⾏符或者说⼀个空⽩⾏。处理顺序是串⾏的,⼀个请求和⼀个响应需要通过⼀问⼀答的形式才能对应起来。
- HTTP2.0中,有两个⾮常重要的概念,分别是帧(frame)和流(stream)。帧代表着最⼩的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。多路复⽤,就是在⼀个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极⼤的提⾼传输性能。
# 5、HTTP状态码
1xx: 信息性状态码,webscoket
2xx: 成功状态码,200、204(No Content) 、206(范围请求,⽐如⾳视频资源)
3xx: 重定向, 301(永久) 、302(临时,可能会改变方法) 、304(协商缓存)、 307(临时,不会从post变为get) 、308(永久)
4xx: 客户端错误, 400(bad request) 、401(Unauthorized,无session,未登录认证) 、403(Forbidden) 、404(not found) 、405(Method not allowed)
5xx: 服务端错误,500(Internal server error) 、502(Bad Gateway) 、503(Service Unavailable)
# 6、HTTP请求头
# 1、Connection
请求头和返回头
close:HTTP/1.1 版本的默认连接都是持久连接。当服务器端想明确断开连接时,则指定Connection⾸部字段的值为 close
Keep-Alive:HTTP/1.1 之前的HTTP版本的默认连接都是⾮持久连接。为此,如果想在旧版本的HTTP协议上维持持续连接,则需要指定Connection⾸部字段的值为Keep-Alive。
# 2、Allow
返回头:⽤于通知客户端能够⽀持 Request-URI 指定资源的所有 HTTP ⽅法。
当服务器接收到不⽀持的 HTTP ⽅法时,会以状态码 405 Method Not Allowed 作为响应返回。与此同时,还会把所有能⽀持的 HTTP ⽅法写⼊⾸部字段 Allow 后返回。Allow: GET, HEAD。
# 3、Host
请求头,Host: aam.sdc.cs.icbc:11688
告知服务器,请求的资源所处的互联⽹主机和端⼝号。
Host ⾸部字段是 HTTP/1.1 规范内唯⼀⼀个必须被包含在请求内的⾸部字段。
若服务器未设定主机名,那直接发送⼀个空值即可 Host: 。
# 4、Accept、Accept-Language、Accept-Encoding
请求头
Accept: text/html, application/xhtml+xml, application/xml;
Accept ⾸部字段可通知服务器,⽤户代理能够处理的媒体类型及媒体类型的相对优先级。可使⽤ type/subtype 这种形式,⼀次指定多种媒体类型。
Accept-Encoding: gzip, deflate
Accept-Encoding ⾸部字段⽤来告知服务器⽤户代理⽀持的内容编码及内容编码的优先顺序,并可⼀次性指定多种内容编码,也可使⽤星号(*)作为通配符,指定任意的编码格式。
gzip表明实体采⽤GNU zip编码
compress表明实体采⽤Unix的⽂件压缩程序
deflate表明实体采⽤zlib的格式压缩的
identity表明没有对实体进⾏编码,当没有 Content-Encoding ⾸部字段时,默认采⽤此编码⽅式
Accept-Language: zh-cn,zh;q=0.5
表示浏览器所支持的语言类型,浏览器支持的语言分别是简体中文和中文,优先支持简体中文。
# 5、Content-Encoding、Content-Type、Content-language
响应头
Content-Encoding: gzip
告知客户端服务器对实体的主体部分选⽤的内容编码⽅式。内容编码是指在不丢失实体信息的前提下所进⾏的压缩。
Content-Type: text/html; charset=UTF-8
说明了实体主体内对象的媒体类型。和⾸部字段 Accept ⼀样,字段值⽤ type/subtype 形式赋值。
Content-language:zh-cn,zh;q=0.5
说明了实体主体内对象的语言类型
# 6、User-Agent
请求头
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36
⾸部字段 User-Agent 会将创建请求的浏览器和⽤户代理名称等信息传达给服务器。由⽹络爬⾍发起请求时,有可能会在字段内添加爬⾍作者的电⼦邮件地址。此外,如果请求经过代理,那么中间也很可能被添加上代理服务器的名称。
# 7、Set-Cookie、Cookie
Set-Cookie返回头、Cookie请求头
Set-Cookie: userId=11111; expires=Mon, 10 Jul 20121 15:50:06 GMT; path=/;
NAME=VALUE: cookie名称和值
expires=DATE: Cookie 的有效期(若不明确指定则默认为浏览器关闭前为⽌)
path=PATH: ⽤于限制指定 Cookie 的发送范围的⽂件⽬录。
domain=域名: cookie有效的域名 (若不指定则默认为创建 Cookie的服务器的域名)
Secure: 仅在 HTTPS 安全通信时才会发送 Cookie
HttpOnly: 使 Cookie 不能被 JavaScript 脚本访问
# 8、强缓存:Expires、Cache-Control
Expires:返回头,这是http1.0时的规范,Expires: Mon, 10 Jul 2021 15:50:06 GMT
⾸部字段 Expires 会将资源失效的⽇期告知客户端。
缓存服务器在接收到含有⾸部字段 Expires 的响应后,会以缓存来应答请求,在 Expires 字段值指定的时间之前,响应的副本会⼀直被保存。当超过指定的时间后,缓存服务器在请求发送过来时,会转向源服务器请求资源。
Cache-Control:返回头,设置缓存机制
Cache-Control: public,可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器
Cache-Control: private,只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存
Cache-Control: no-cache,可以在客户端存储资源,每次都必须去服务端做过期校验,来决定从服务端获取新的资源
(200)还是使⽤客户端缓存(304)。也就是所谓的协商缓存。
Cache-Control: no-store,永远都不要在客户端存储资源,永远都去原始服务器去获取资源。
Cache-Control: max-age=604800(单位:秒),当客户端发送的请求中包含 max-age 指令时,如果判定缓存资源的缓存时间数值⽐指定的时间更⼩,那么客户端就接收缓存的资源。另外,当指定 max-age 的值为0,那么缓存服务器通常需要将请求转发给源服务器。HTTP/1.1 版本的缓存服务器遇到同时存在 Expires ⾸部字段的情况时,会优先处理max-age指令,并忽略Expires ⾸部字段
Cache-Control: s-maxage=604800(单位:秒),s-maxage 指令的功能和 max-age 指令的相同,它们的不同点是 s-maxage 指令只适⽤于供多位⽤户使⽤的公共缓存服务器(⼀般指代理)。当使⽤ s-maxage 指令后,则直接忽略对 Expires ⾸部字段及 max-age 指令的处理
# 9、协商缓存:ETag、If-None-Match、Last-Modified、If-Modified-Since
If-Modified-Since: Mon, 10 Jul 2021 15:50:06 GMT,请求头。
- ⽤于确认代理或客户端拥有的本地资源的有效性。
- 在指定 If-Modified-Since 字段值的⽇期时间之后,如果请求的资源都没有过更新,则返回状态码 304 Not Modified 的响应
If-None-Match: “lubai”,请求头。⽤于指定 If-None-Match 字段值的实体标记(ETag)值与请求资源的 ETag 不⼀致时,它就告
知服务器处理该请求。
ETag: “aaaa-1234”,返回头,⾸部字段 ETag 能告知客户端实体标识。它是⼀种可将资源以字符串形式做唯⼀性标识的⽅式。服务器会为每份资源分配对应的 ETag 值。另外,当资源更新时,ETag 值也需要更新。⽣成 ETag 值时,并没有统⼀的算法规则,⽽仅仅是由服务器来分配。
Last-Modified : Fri , 12 May 2006 18:53:33 GMT,返回头。在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是客户端请求的资源,同时有一个Last-Modified的属性标记此文件在服务器端最后被修改的时间。
//1、强缓存响应头设置:浏览器直接读取内存或硬盘,不发起请求
//两个条件:不是当前页面的第一个请求(非浏览器url地址栏的请求)、在有效期内
res.setHeader('Cache-Control','max-age=10'); //单位s,相对时间,http1.1,比expires优先
res.setHeader('Expires',new Date(Date.now() + 10*1000).toUTCString()); //绝对时间,Date.now()返回ms
//2、协商缓存
//服务器返回Last-Modified(改变时间)、Etag(文件内容hash值)
//浏览器下次请求时返回:If-None-Match、If-Modified-Since
//3、实践,返回强缓存、协商缓存头
const img = fs.readFileSync('./ip_kinds.jpg')
const etag = crypto.createHash('md5').update(img).digest('base64')
const reqEtag = req.headers['if-none-match']
// 有etag就根据etag判断,返回304还是200
if(reqEtag && reqEtag == etag) {
console.log('etag 协商缓存⽣效')
res.statusCode = 304
res.end()
return;
}
const imgStat = fs.statSync('./ip_kinds.jpg')
const ctime = imgStat.ctime.toUTCString();
const reqLastModified = req.headers['if-modified-since']
// 没有etag根据last-modified 判断304还是200
if(reqLastModified === ctime) {
console.log('last modified 协商缓存⽣效')
res.statusCode = 304
res.end()
return;
}
// 200 -> 读⽂件,设置强缓存和协商缓存
// 强缓存——响应头
res.setHeader('Cache-Control','max-age=10');
res.setHeader('Expires',new Date(Date.now() + 10*1000).toUTCString());
// 协商缓存——响应头
res.setHeader('Last-Modified', ctime);
res.setHeader('Etag',etag );
res.end(img)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 7、HTTPS
# 1、https握手
http + ssl/tsl = https,其中ssl/tsl协议为http和tcp之间的协议,在osi7层模型的会话层和表示层、保证了传输过程的安全性
特点:
- 内容加密:采用混合加密技术,中间者无法直接查看明文
- 验证身份:通过证书认证客户端访问的是自己的服务器
- 保护数据完整性:防止传输的内容背中间人冒充或者篡改
握手过程:
- 客户端发起https请求,端口443,发送随机数1和客户端支持的加密算法
- 服务端收到信息后回传响应信息,包括随机数2和匹配的加密算法
- 服务端配置数字证书,实质为公私钥,其中数字证书为认证机构处理过的公钥,包含:证书颁发机构、过期时间、服务端公钥、第三方证书认证机构CA的签名、服务端的域名信息等内容。
- 服务端传输数字证书给客户端
- 客户端解析证书并验证,有问题提示警告,无问题生成随机值3,将随机数1、随机数2、随机值3组合成密钥,并用公钥加密
- 客户端向服务端传输加密后的对称密钥
- 服务端接收后,用私钥解密出对称密钥,拿对称密钥对传输内容进行对称加密
- 服务端传输加密后的报文
- 客户端用随机值进行解密,获取报文
# 2、证书校验
数字证书内容:包括了加密后服务器的公钥、权威机构的信息、服务器域名,还有经过CA私钥签名之后的证书内容(先通过Hash函数计算得到证书数字摘要,然后用权威机构私钥加密数字摘要得到数字签名),签名计算方法以及证书对应的域名
https握手过程中,客户端如何验证证书的合法性:
- 首先浏览器读取证书中的证书所有者、有效期等信息进行校验:校验证书的网站域名是否与证书颁发域名一致,校验证书是否在有效期内;
- 浏览器开始查找操作系统中已内置的受信任的证书颁发机构CA,与服务器发来的证书中的颁发者CA进行对比,用于校验证书是否为合法机构颁发;
- 如果找不到,浏览器会报错,说明浏览器发来的证书是不可信任的;
- 如果找到,那么浏览器会从操作系统中取出颁发者CA的公钥(浏览器一般内置常用认证机构的公钥),然后对服务器发来的证书里的数字签名进行解密;
- 浏览器使用相同的hash算法计算出服务器发来的证书的hash值,将这个计算值与证书中签名做对比;
- 对比结果一致,证明服务器发来的证书合法,没有被冒充
# 3、中间人攻击
https中间人攻击:
- 服务器向客户端发送公钥;
- 攻击者截获公钥,保留在自己手上;
- 攻击者伪造假公钥,发给客户端;
- 客户端收到伪造的假公钥,生成加密hash值发给服务器;
- 攻击者获得加密hash值,用自己私钥解密获取hash值,同时生成假的hash加密值,发给服务器;
- 服务器用私钥解密获得假的hash值,用假的hash值加密传输信息。
防范:服务器在发送浏览器的公钥中加入ca证书,浏览器可以验证ca证书的有效性(现有https很难被劫持,除非信任了劫持者的ca证书)
# 6、Nodejs网络模块
socket套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用,来实现进程在网络中通信,比如create,listen,accept,connect,read和write等等。
http为应用层模块,主要按照特定协议编解码数据;
net为传输层模块,主要负责传输编码后的应用层数据;
https是个综合模块(涵盖了http/tls/crypto等),主要用于确保数据安全性
# 1、TCP模块
- tcp客户端
const net = require('net');
const HOST = '127.0.0.1';
const PORT = 7777;
// 创建一个TCP服务器实例,调用listen函数开始监听指定端口
// net.createServer()有一个参数, 是监听连接建立的回调
net.createServer((socket) => {
const remoteName = `${socket.remoteAddress}:${socket.remotePort}`;
// 建立成功了一个连接, 这个回调函数里返回一个socket对象.
console.log(`${remoteName} 连接到本服务器`);
// 接收消息
socket.on('data', (data) => {
console.log(`${remoteName} - ${data}`)
// 给客户端发消息
socket.write(`你刚才说啥?是${data}吗?`);
});
// 关闭
socket.on('close', (data) => {
console.log(`${remoteName} 连接关闭`)
});
}).listen(PORT, HOST);
console.log(`Server listening on ${HOST}:${PORT}`);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- tcp服务端
const net = require('net');
const HOST = '127.0.0.1';
const PORT = 7777;
const client = new net.Socket();
const ServerName = `${HOST}:${PORT}`;
let count = 0;
client.connect(PORT, HOST, () => {
console.log(`成功连接到 ${ServerName}`);
// 向服务端发送数据
const timer = setInterval(() => {
if (count > 10) {
client.write('我没事了, 告辞');
clearInterval(timer);
return; }
client.write('⻢冬梅' + count++);
}, 1000)
});
// 接收消息
client.on('data', (data) => {
console.log(`${ServerName} - ${data}`);
// 关闭连接
// client.destroy();
});
// 关闭事件
client.on('close', () => {
console.log('Connection closed');
});
client.on('error', (error) => {
console.log(error);
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 2、UDP模块
- server
const dgram = require('dgram');
const server = dgram.createSocket('udp4');
server.on('message', (msg, remote) => {
console.log(`${remote.address}:${remote.port} - ${msg}`)
server.send(`收到!`, remote.port, remote.address);
})
server.on('listening', () => {
const address = server.address()
console.log(`Server listening on ${address.address}:${address.port}`);
})
server.bind(44444);
2
3
4
5
6
7
8
9
10
11
- client
const dgram = require('dgram')
const message = Buffer.alloc(5, 'lubai')
const client = dgram.createSocket('udp4')
client.send(message, 0, message.length, 44444, 'localhost', (err, bytes) => {
console.log(`发送成功${bytes}字节`);
// client.close()
} )
client.on('message', (buffer) => {
console.log(buffer.toString())
})
2
3
4
5
6
7
8
9
10
# 3、HTTP模块
//server
const http = require('http')
http.createServer(function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
})
res.end('Hello World')
}).listen(80, '127.0.0.1')
console.log('Server running at http://127.0.0.1:80/')
//client
const http = require('http')
const options = {
hostname: '127.0.0.1',
port: 80,
path: '/',
method: 'GET'
}
const req = http.request(options, (res) => {
console.log(`Status=${res.statusCode}, Headers=$ {JSON.stringify(res.headers)}`);
res.setEncoding('utf8')
res.on('data', (data) => {
console.log(data)
})
})
req.end()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//浏览器访问:http://127.0.0.1:80/
//curl访问:curl -v http://127.0.0.1:80
// 三次握⼿
* Rebuilt URL to: http://127.0.0.1:80/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
// 客户端向服务端发送请求报⽂
> GET / HTTP/1.1
> Host: 127.0.0.1:80
> User-Agent: curl/7.54.0
> Accept: */*
>
// 服务端响应客户端内容
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Wed, 04 Aug 2021 15:55:55 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
<
* Connection #0 to host 127.0.0.1 left intact
Hello World%
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 4、rpc调用
Remote Procedure Call(远程过程调⽤)
与http调⽤的区别
- 不⼀定使⽤ DNS 作为寻址服务,可使用内部协议存储
- http传输时:http://test.com/aa -> ip
- rpc传输时:可使用内部协议存储,v1://dd.ccc -> ip
- 基于TCP或UDP协议
- 使⽤⼆进制协议(更⼩的体积,更快的解码速率)
- http传输时:明文一般为json格式
- 使用二进制,eg:定义1-4位为name、5-9位为age,使用buffer库进行解析,也可使用protocol-buffers库进行处理,像使⽤JSON⼀样简单
- 不⼀定使⽤ DNS 作为寻址服务,可使用内部协议存储
tcp通信⽅式
- 单⼯:只能由一方向另一方发送数据另一方不可以,客户端向服务端发送数据或服务端向客户端发送数据
- 半双⼯:独木桥,双向都可发送数据,但每次只能传输一种数据,客户端向服务端发送数据后,服务端向客户端发送数据
- 全双⼯:双方可同时发送数据
- 错乱问题、粘包
- 制定了⼀条协议规则,即 包序号(1-2字节)+ 包⻓度(3-6字节) + 包主体内容。
DETAILS
protocol-buffers (opens new window)
nodejs rpc框架 阿⾥ sofa (opens new window)
# 7、网络部署及应用
使用阿里云服务器,基于linux系统:
- 下载安装包
wget https://nodejs.org/dist/v10.9.0/node-v10.9.0-linux-x64.tar.xz
- 解压
tar xf node-v10.9.0-linux-x64.tar.xz
- 设置软链接
ln -s /root/node-v10.9.0-linux-x64/bin/node /usr/local/bin/node
ln -s /root/node-v10.9.0-linux-x64/bin/npm /usr/local/bin/npm
- 查看Node版本和npm版本
node -v
npm -v
- 设置npm源
npm config set registry https://registry.npm.taobao.org
- 服务器安装pm2
PM2 是一个带有负载均衡功能的 Node 应用进程管理器
npm install -g pm2
ln -s /root/node-v10.9.0-linux-x64/bin/pm2 /usr/local/bin/
- 配置ssh
- 本地生成秘钥对:
ssh-keygen -t rsa
demo_id_rsa - 将公钥放到服务器上:
scp ~/.ssh/demo_id_rsa.pub root@39.107.238.161:/root/.ssh/authorized_keys
- 修改ssh配置
vi ~/.ssh/config
Host lubai
HostName 39.107.238.161
User root
Port 22
IdentityFile ~/.ssh/demo_id_rsa
2
3
4
5
- 服务器上修改ssh配置
vim /etc/ssh/sshd_config
PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys
- 最后就可以ssh登录了!
ssh xx
- 将本地代码同步到服务器
rsync是linux可以实现增量备份的工具
rsync -avzp -e "ssh" ./Internet/ lubai:/root/app
- 服务器上启动http
pm2 start /root/app/http-server.js
- 本地修改发布命令
10.1 新建deploy.sh文件
#!/bin/bash
HOST=xx
rsync -avzp -e "ssh" ./Internet/ $HOST:/root/app
ssh $HOST "pm2 restart /root/app/http-server.js"
echo 'deploy success'
2
3
4
5
6
7
8
10.2 初始化npm命令
npm init
,新增scripts "deploy": "./deploy.sh"
10.3 发布
npm run deploy
- 修改http-server的监听host
const http = require('http')
const host = '0.0.0.0';
const port = 80;
http.createServer(function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
})
res.end('Hello World')
}).listen(port, host)
console.log(`Server running at http://${host}:${port}/`)
2
3
4
5
6
7
8
9
10
11
ECS安全组添加80端口
查看服务器上是否已正常监听80端口
netstat -tpln
通过ip+端口访问:39.107.238.161:80