网络分层模型
网络传输可能发生的问题:
- 数据丢包
- 数据重复
- 数据完整性校验
- 数字信号转模拟信号
- 不同介质间的转换
- 信号衰减
通过网络分层解决网络可靠性问题
将网络分层,遵守严格的分层模式,上下两层之间保持接口不变,这样可以修改、替换其中一层,不会影响其他层。
常用的网络分层:
- OSI(Open System Interconnection Reference Model)开放系统互联参考模型
- TCP/IP 协议族,OSI 的表示、会话两层被合并入应用层
HTTP(HyperText Transfer Protocol)
超文本传输协议。 一种无状态的,以请求/应答方式运行的协议,它使用可扩展的语义和自描述消息格式,与基于网络的超文本信息系统灵活的互动。
HTTP 报文格式
HTTP 协议的请求报文和响应报文的结构基本相同
- 三大部分组成
- 起始行(start line): 描述请求或响应的基本信息
GET /index.html HTTP/1.1
- 头部字段集合(header): 使用 key-value 形式更详细地说明报文
Connection:keep-alive
- 消息正文(entity): 实际传输的数据,不一定是纯文本,可以是图片、视频等二进制数据
<html>...
- 起始行(start line): 描述请求或响应的基本信息
- 请求行报文格式:
METHOD[空格]URI[空格]VERSION[换行]
- 请求方法:GET/HEAD/PUT/POST,表示对资源的操作
- 请求目标:通常是一个 URI,标记了请求方法要操作的资源
- 版本号:表示报文使用的 HTTP 协议版本
- 响应行报文格式:
VERSION[空格]STATUS CODE[空格]REASON[换行]
- 版本号:表示报文使用的 HTTP 协议版本
- 状态码:一个三位数,用数码的形式表示处理结果
- 原因:作为状态码的补充,帮助人理解原因
- HTTP 头字段
头字段是 key-value 的形式,key 和 value 间用’:’分割,最后用 CRLF 表示字段结束。头字段除了标准的字段如 Host、Connection 等,还可以添加任意自定义头,这样可以扩展出无限的可能性。
- 头字段注意事项:
- 字段名不区分大小写,名称中不能有空格,可以使用连字符’-‘,不可以使用下划线’_‘,字段名后面要紧接’:’不能有空格,而’:’后的字段值前可以加多个空格
- 字段的顺序没有意义,排列顺序不影响语义
- 字段原则上不能重复,除非这个字段本身的语义允许,例如 Set-Cookie
- 自定义 Header 通常以
X-/x-
开头,这种设置对旧版本的 Load Balancer 兼容性较好
- 头字段注意事项:
- 常用头字段
HTTP 协议的头字段基本分为四大类
- 请求字段:请求头中的字段,如 Host(标识链接是跟哪个服务建立的),Referer(当前请求来源,可用于防盗图)
- 响应字段:响应头中的字段,如 Server(服务器类型,如 Nginx、Apache)
- 通用字段:在请求头和响应头都可以出现,如 Content-type(当前报文的数据格式,如 Json、Html),Connection
一个 HTTP 请求流程
- 在浏览器输入网址按下回车 浏览器解析出网址的域名部分、在浏览器缓存查询域名
- 通过域名查找 DNS 得到 IP 地址 浏览器缓存、Host 文件、操作系统缓存、DNS 服务器
- 通过 IP 地址访问服务器,发送 HTTP 请求 TCP 三次握手(/TLS 握手)、将数据放入缓冲区、服务端程序处理
- 收到结果 服务器返回 http 响应、浏览器解析响应、渲染页面、TCP 四次挥手
Status Code(状态码)
每次请求返回一个状态码,由三位数字表示
- 1xx 过程中
- 100 continue
- 101 Switching Protocols, 协议切换
- 2xx 成功
- 200 OK
- 201 Created, 已创建
- 206 Partial Content, 返回部分内容
- 3xx 重定向
- 304 Not Modified,如首部未改变,客户端可重用
- 4xx 客户端错误
- 400 用户请求通用错误;
- 403 用户无该路径权限;
- 404 路径不存在;
- 405 Method Not Allowed;
- 406 Not Acceptable, 如 xml/json 差异
- 408 服务器等待客户端请求时超时;
- 499 Nginx 自定义错误, 客户端还未接收完 response 就关闭了连接
- 5xx 服务器错误
- 500 服务器内部通用错误;
- 501 Not Implemented
- 502 bad gateway 有时请求已经打到 LB/Nginx 了, 但是后面的服务出了问题
- 503 服务不可用, 如 CPU 内存等造成的资源不足
- 504 Gateway Timeout
1.0 1.1 2.0
- HTTP1.0 每个请求都是一个短连接,如果一个网页有多个关联资源,则需要多次重新建立连接
- HTTP1.1 访问网页时保持一个长连接,多个资源下载不需要重新建立连接,不过多个资源之间的下载是顺序处理
- HTTP2.0
- 二进制格式(Binary Format) HTTP1.X 是文本通信,文本解析较复杂,为了做到健壮性要考虑很多场景。HTTP2.0 采用二进制传输,实现方便、健壮性好。
- 多路复用(MultiPlexing) 一个连接可以由多个请求共享,通过 request id 区分,多个请求并行进行。
- header 压缩 通讯双方各自 cache 一份 header fields 表,既避免了重复 header 的传输,又减小了需要传输的大小
- 服务端推送(server push) server 端可以主动向客户端 push 消息或文件
HTTPS(Hyper Text Transfer Protocol over SecureSocket Layer)
由于 HTTP 是明文文本传输,存在较大安全隐患。比如被读取密码、被篡改请求、被篡改 Response。 HTTPS 在 HTTP 的基础上通过传输加密和身份认证保证了传输过程的安全性。
SSL/TLS(Secure Sockets Layer/Transport Layer Security)
SSL 由 Netscape 公司 1994 年发明并于 1999 年改名为 TSL,是信息安全领域的权威标准。
- SSL 的关键算法是 RSA,非对称加密,生成一对公、私钥,私钥自己留着、公钥公开出去。公钥加密后的数据可以用私钥解密;私钥加密后的数据可以用公钥解密;
-
RSA 这种非对称加密性能较差,所以一般不直接用于通信,而是用于生成摘要进行身份认证;或者用于加密对称加密的密钥。
- 数字证书的组成 CA(Certificate Authority) 信息、公钥用户信息(组织信息)、公钥、证书序列号、有效期、最后是 CA 认证机构的签名
-
数字证书的作用
- 通过数字证书向浏览器证明身份
- 数字证书里包含了公钥
- 数字证书中的
权威机构签名
实际上就是上一级权威机构将当前证书信息进行 MD5(或其他 Hash 算法)进行摘要后,再用私钥进行一次加密的产物。这样外界只要拿着上级公钥对这个签名进行解密,就能得到证书信息的摘要,同时自己再利用证书信息生成一次摘要,如果两个摘要是一致的,说明是上一级权威机构签名的。 - 数字证书一般以链式形式提供 比如存在一系列认证链 A->B->C,其中 A 是根证书、C 是当前用户证书。当前用户向外提供证书时,就要把一系列证书链都提供出去,这样便于进行链式验证,证明整条链的合法性。
- 根证书比较特殊,无法由目标网站直接提供,一般是带在操作系统里的,由根证书机构提供。通常操作系统没什么问题,但也不排除整个操作系统+DNS 域名系统一起被篡改的情况,这样篡改的成本非常大。
HTTPS 流程
- 首先在进行任何通信前,先要准备 CA
- 服务器生成自己的私钥、公钥,私钥自己保存、公钥交给 CA 机构,同时提交自己的真实公司信息、域名信息以便 CA 机构进行认证
- CA 机构利用服务器提交的信息制作服务器的 CA 证书,用自己的私钥对服务器 CA 证书进行机构签名,将 CA 证书交给服务器所属公司
- 根据 CA 机构的等级,费用不同,证书有效期也不同
- CA 机构是分很多级的,通常最高级会把自己的根证书交给操作系统或浏览器发行商,这样保证根证书可信
- 客户端访问时,首先是三次握手建立 TCP 连接
- 客户端向服务端发送加密套件列表,包括支持的散列算法、对称加密算法、非对称加密算法
- 服务器从加密套件列表中选择一个套件,将数字证书链返回给客户端,证书链里没有根证书,根证书是内置在浏览器或操作系统里的
- 客户端拿到证书链后,通过对链上每个证书进行 Hash、上级 CA 公钥解密签名,验证每个证书的合法性
- 客户端随机生成对称加密密钥,用服务器的公钥进行加密,然后将加密后的数据发给服务端
- 服务端拿到加密后的数据,用私钥解密,之后双方利用对称加密密钥进行通信
DNS(Domain Name System)
域名系统,将 IP 和域名地址相互映射的一个分布式数据库。
DNS 查询过程(一旦找到就返回): 浏览器域名缓存->本地 Host 文件->操作系统本地缓存->向设置/(网络接入商提供的)默认 的 DNS 服务器查询 IP->向上级查询直到根域名服务器
- DNS 基于 UDP 协议工作
TCP(Transmission Control Protocol)
传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。
特点
- 基于连接的 数据传输之前需要建立连接
- 全双工 数据同时双向传输
- 字节流 不限制数据大小,打包成报文段,保证有序接收,重复报文自动丢弃 由于发送数据时可能经过不同的路由,所以底层很容发生乱序或重复
- 流量缓冲 解决双方处理能力的不匹配
- 可靠的传输服务 保证可达,丢包时通过重发机制实现可靠性
- 拥塞控制 在网速较差或网络稳定性较差时,会发生大量丢包,这时算法会主动将包切的更小,并降低发送频率,防止网络出现恶性拥塞
报文结构
- 源端口、目的端口 在 TCP 通信时发起的一方要先知道监听方的端口号
- 序号
Seq 序号,占 32 位,用来标识从 TCP 源端向目的端发送的字节流,发起方发送数据时对此进行标记,每次根据发送数据量进行增长。
- 假设一个报文段的序号字段是 301,且携带了 100 字节的数据,则第一个字节的序号是 301,最后一个字节的序号是 400。如果有下一个报文段,则数据序号应从 401 开始。
- 确认序号(Acknowledge Sequence) Ack 序号,占 32 位,只有 ACK 标志位为 1 时,确认序号字段才有效,Ack=Seq+1。
- 标志位:共 6 个,即 URG、ACK、PSH、RST、SYN、FIN 等,具体含义如下:
- URG:紧急指针(urgent pointer)有效。
- ACK:确认序号有效。
- PSH:接收方应该尽快将这个报文交给应用层。
- RST:重置连接。
- SYN:发起一个新连接。
- FIN:释放一个连接。
- 接收窗口 是指发送这个报文的发送者,缓冲区剩余容量的大小。
- 紧急指针 如果 URG 标识位为 1,则优先处理紧急指针这里的数据
- 数据包通常由很多层组成,越靠前越是底层协议,例如 HTTP 基于 TCP 基于 IP,那么数据包结构是 IP 头 + IP 数据(TCP 头 + TCP 数据(HTTP 头 + HTTP 数据)),从数据上看是先看到各个协议的头
- 数据包的头的顺序与协议栈相一致
TCP 连接管理
TCP 连接四元组:源地址、源端口、目的地址、目的端口
- 可以用
tcpdump
工具抓包分析
三次握手(Three-way Handshake)
- 三次握手的目的:
- 同步通信双方初始序列号(ISN, Initial Sequence Number)
- 协商 TCP 通信参数(MSS MaximumSegmentSize 最大报文长度、窗口信息、校验和算法)
- 首先服务端要监听特定的端口号,发起握手的客户端要提前知道并使用这个端口号
- 客户端发起连接时,一般会随机选择自己的一个端口号
- 握手时,双方初次发出的 Seq 是各自独立生成的随机数
- 握手时,每次通信 ACK 值都是 Seq+1,表示之前的 Seq 被消耗掉了,这种情况在数据传输时不会发生。数据传输时,只会根据容量正常消耗 Seq。
SYN 攻击
SYN 攻击是一个典型的 DDoS 攻击。
- DDOS(Distributed Denial of Service Attack)拒绝服务攻击时,攻击者想非法占用被攻击者的一些资源,比如如:带宽,CPU,内存等等,使得被攻击者无法响应正常用户的请求。
在三次握手过程中,服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 Syn_RECV 状态。当收到 ACK 后,服务器转入 ESTABLISHED 状态。
SYN 攻击就是”攻击客户端”在短时间内伪造大量不存在的 IP 地址,向服务器不断地发送 SYN 包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,正常的 SYN 请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。
检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源 IP 地址是随机的,基本上可以断定这是一次 SYN 攻击。
在 Linux 下可以如下命令检测是否被 Syn 攻击netstat -n -p TCP | grep SYN_RECV
一般较新的 TCP/IP 协议栈都对这一过程进行修正来防范 SYN 攻击。修改 TCP 协议实现,主要方法有 SynAttackProtect 保护机制、SYN Cookies 技术、增加最大半连接和缩短超时时间等。但是不能完全防范 SYN 攻击。
四次挥手
四次挥手的关键是双向通道独立关闭过程。
- MSL最大报文生存时间
- MSL 默认是 2 分钟
- 如果最后客户端没有等待 2MSL(Maximum Segment Lifetime) 就关闭,会造成两种问题:
- 万一最后的 ACK 没有到达服务端,那服务端会不断发 FIN,持续占用资源无法释放
- 前一个连接没有等待就关闭,立刻在相同端口号建立新连接,那么新旧数据就可能同时被接收,造成混乱
-
双方都可以主动发起关闭连接,我们这里把主动关闭的一方称为客户端
- 发送方调用 close 时,如果缓冲区还有数据,TCP 会继续把数据发完,然后再开始关闭流程,所以发送缓冲区的数据不会丢。
- 接收方如果没有读取就 close 了,那么没有人再读取缓冲区数据了,读取缓冲区数据会丢失。
- 主动关闭方发送了 FIN 后,只表示本端不能再发送数据(应用层不能调 send),但是仍然可以接收数据。
- 应用层如何知道对端关闭? 通常,在最简单的阻塞模型中,当你调用 recv 时,如果返回 0,则表示对端关闭。在这个时候通常的做法就是也调用 close,那么 TCP 层就发送 FIN,继续完成四次握手。如果你不调用 close,那么对端就会处于 FIN_WAIT_2 状态,而本端则会处于 CLOSE_WAIT 状态。
-
在很多时候,TCP 连接的断开都会由 TCP 层自动进行,例如你 CTRL+C 终止你的程序,TCP 连接依然会正常关闭
- TIME_WAIT 状态所带来的影响:
TCP 连接主动关闭方在最后会处于 TIME_WAIT 状态 2MSL 时常,用于保证连接彻底断开。
当某个连接的一端处于 TIME_WAIT 状态时,该连接(四元组)将不能再被使用。
事实上,对于我们比较有现实意义的是,这个端口将不能再被使用。
某个端口处于 TIME_WAIT 状态(其实应该是这个连接)时,这意味着这个 TCP 连接并没有断开(完全断开),那么,如果你 bind 这个端口,就会失败。
对于服务器而言,如果服务器突然 crash 掉了,那么它将无法在 2MSL 内重新启动,因为 bind 会失败。
解决这个问题的一个方法就是设置 socket 的 SO_REUSEADDR 选项。这个选项意味着你可以重用一个地址。
- 解决 TIME_WAIT 的方案:
- 如果在服务端出现,可能会导致大量端口处于 TIME_WAIT,从而导致端口不足
- 大量 TIME_WAIT 可能本身就是正常的,可能是需要增加服务端主机了
- 可以和客户端协商断开连接的过程,由客户端主动断开
- 可以协商缩短 MSL 的时间
- 可以利用端口重用模式,避免重用时 TIME_WAIT 造成问题
- 注意:重用模式时,路由层的 Nginx 肯恩会修改 TCP 包的一些参数导致重用错误
- 解决 TIME_WAIT 的方案:
数据可靠性保证
如果每次发送都要求一个 ACK,然后再发下一个包,这样效率极低。
实际上使用的是滑动窗口协议与累计确认(延时 ACK)算法。
- 如图所示,发送数据队列分为下列区域:
- 已发送并收到确认
- 已发送但未收到确认
- 未发送并处于发送窗口中
- 未发送但未处于发送窗口中
- 发送后的数据如果超时仍未收到 ACK,则会重发,并且限制窗口移动速度
常见问题
-
TCP 保证数据完整性吗?发送者如何知道发送成功? 保证完整性。发送方调用函数,最终将数据发到对方的缓冲区后就返回成功。
-
为什么 TCP 保证了传输数据的正确性,还需要在应用层加 CRC 或 MD5 校验? 因为处于不同的层。TCP 在传输层保证正确性,但是应用层收到数据后可能未正常保存就崩溃了,这种情况下在 TCP 层看到是正常传输完毕的,而最终文件却有问题,需要校验。
-
如果服务端 CPU 被占满,无法及时处理客户端数据,客户端数据是否会丢失? 根据 TCP 协议,操作系统只要把网卡数据拷贝到内核态接收缓冲区,就会返回 OK ACK,这样客户端就收到了确认,这种情况内核态接收缓冲区的数据不会丢失,迟早能被处理。如果 CPU 被占满,用户态来不及把数据从内核态缓冲区取走,导致内核态接收缓冲区被占满,网卡的数据无法复制到内核态接收缓冲区并向客户端发送 ACK。这种情况客户端的”滑动窗口”就无法向前移动,内核发送缓冲区会被占满,会不断重试并逐步加长每次重试的间隔,直到可以正常发送。所以也不会丢失数据。
-
大量 CloseWait 状态的连接问题
- 原因: 被动一方在收到 FIN 并发出 FIN_ACK 后会进入 CloseWait 状态。一般是因为服务端发了断开连接后,客户端没有发出 FIN_ACK,而是直接关闭了,导致服务端一直没收到 FIN_ACK。
- 方案:
- 客户端要按协议做,保证发送 FIN_ACK 后,并等待 2MSL 再关闭。
- 服务端默认的 CloseWait 等待时间太长了,改小之后可以避免残留大量未关闭连接占用连接队列
-
大量 TimeWait 状态的连接问题
- 原因:
- 主动关闭 TCP 连接的一方都会有 TimeWait 状态的连接,这是正常的
- TimeWait 保证了”全双工”的工作方式正常终止,如果主动断开方发完最后的 ACK,而这个 ACK 对方没收到,那么对方还可能在发 FIN,干扰新链接
- TimeWait 占用连接 4 元组,保证了重复报文不会影响下一个新建连接
- 在高并发短连接服务器上,当请求处理完后服务器主动断开连接,就会产生大量 TimeWait,如果客户端并发量很大就可能有部分客户端连接不上。 产生问题的原因是连接的使用时间远远短于 TimeWait 等待时间,造成大量连接队列被占。
- 还有一种可能:SLB(Service Load Balancer)对 TCP 包进行修改,影响了 timestamp 参数,导致客户端大量 TimeWait
- 主动关闭 TCP 连接的一方都会有 TimeWait 状态的连接,这是正常的
- 方案:
- 首先 TimeWait 本来是正常的,如果实在处理不了就增加机器解决,不用非得解决
- 编辑内核文件
/etc/sysctl.conf
然后执行/sbin/sysctl -p
让参数生效,这样就能减少 TimeWait 等待net.ipv4.tcp_tw_reuse = 1
表示开启重用。允许将 TIME-WAIT sockets 重新用于新的 TCP 连接,默认为 0,表示关闭;net.ipv4.tcp_tw_recycle = 1
表示开启 TCP 连接中 TIME-WAIT sockets 的快速回收,默认为 0,表示关闭。线上环境 tw_recycle 不建议打开因为 SLB 会把 timestamp 清空net.ipv4.tcp_fin_timeout = 60
表示如果套接字由本端要求关闭,这个参数决定了它保持在 FIN-WAIT-2 状态的时间。在明知在 Finish 时对方不会有数据发来时,可以改小这个时间。reuse recycle
原理: 通过 timestamp 的递增性来区分是否新连接,新连接的 timestamp 更大,那么保证小的 timestamp 的 fin 不会 fin 掉新连接,不用等 2MSL。
- 业务逻辑上,可以由客户端主动断开连接,这样 TimeWait 就会在客户端,一般资源不存在问题
- 原因:
UDP(User Datagram Protocol)
用户数据报协议,提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。
-
TCP 协议和 UDP 协议的区别是什么
- TCP 协议是有连接的,有连接的意思是开始传输实际数据之前 TCP 的客户端和服务器端必须通过三次握手建立连接,会话结束之后也要结束连接。而 UDP 是无连接的
- TCP 协议保证数据按序发送,按序到达,提供超时重传来保证可靠性,但是 UDP 不保证按序到达,甚至不保证到达,只是努力交付,即便是按序发送的序列,也不保证按序送到。
- TCP 协议所需资源多,TCP 首部需 20 个字节(不算可选项),UDP 首部字段只需 8 个字节。
- TCP 有流量控制和拥塞控制,UDP 没有,网络拥堵不会影响发送端的发送速率
- TCP 是一对一的连接,而 UDP 则可以支持一对一,多对多,一对多的通信。
- TCP 面向的是字节流的服务,UDP 面向的是报文的服务。
-
常见的哪些应用是基于 UDP 的?为什么要基于 UDP?
- 广播的信息用 UDP 实现更合适,TCP 只能支持一对一通信
- 如果一个应用场景中大多是简短的信息,适合用 UDP 实现,因为 UDP 是基于报文段的
- 如果一个应用场景重视实时性远高于完整性和安全性,可以用 UDP,比如网络流媒体播放,缺一两帧不影响用户体验,但是要求流量大、速度快
- 如果既想利用 UDP 快速响应的优点,又想可靠传输,那么只能自己靠上层应用自己制定规则了。但是通常自定义协议都是对某方面性能做了折中处理,如果没有任何折中那还不如用 TCP。
- 常见的基于 UDP 的应用:QQ、网络视频
SCTP()
IP(Internet Protocol)
网际互连协议,IP 只为主机提供一种无连接、不可靠的、尽力而为的数据报传输服务。 IP 主要包含三方面内容:IP 编址方案、分组封装格式及分组转发规则。
ARP(Address Resolution Protocol)
地址解析协议,工作于网络层,是根据 IP 地址获取物理地址的一个 TCP/IP 协议。 在每个主机上保存一个 IP 到 MAC 地址的映射表。 想要查询映射关系的主机将包含目标 IP 地址的 ARP 请求广播到局域网的所有主机,具有该 IP 地址的主机收到请求会返回自己的 MAC 地址, 然后主动查询的主机收到后可以将映射关系缓存在自己本地。
常见问题
- ARP 是基于局域网之间主机互信的前提的,所以可能被恶意主机返回虚假 ARP 应答报文,使发送者的信息被发送到错误的主机
- 我们如果手动设置了多台主机的 IP 地址为同一个值,会引起 IP 地址冲突