Network Protocols

network

网络分层模型

网络传输可能发生的问题:

  • 数据丢包
  • 数据重复
  • 数据完整性校验
  • 数字信号转模拟信号
  • 不同介质间的转换
  • 信号衰减

通过网络分层解决网络可靠性问题

将网络分层,遵守严格的分层模式,上下两层之间保持接口不变,这样可以修改、替换其中一层,不会影响其他层。

常用的网络分层:

  • OSI(Open System Interconnection Reference Model)开放系统互联参考模型
  • TCP/IP 协议族,OSI 的表示、会话两层被合并入应用层

HTTP(HyperText Transfer Protocol)

超文本传输协议。 一种无状态的,以请求/应答方式运行的协议,它使用可扩展的语义和自描述消息格式,与基于网络的超文本信息系统灵活的互动。

HTTP 报文格式

HTTP HTTP 协议的请求报文和响应报文的结构基本相同

  • 三大部分组成
    • 起始行(start line): 描述请求或响应的基本信息 GET /index.html HTTP/1.1
    • 头部字段集合(header): 使用 key-value 形式更详细地说明报文 Connection:keep-alive
    • 消息正文(entity): 实际传输的数据,不一定是纯文本,可以是图片、视频等二进制数据 <html>...
  • 请求行报文格式: 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 请求流程

  1. 在浏览器输入网址按下回车 浏览器解析出网址的域名部分、在浏览器缓存查询域名
  2. 通过域名查找 DNS 得到 IP 地址 浏览器缓存、Host 文件、操作系统缓存、DNS 服务器
  3. 通过 IP 地址访问服务器,发送 HTTP 请求 TCP 三次握手(/TLS 握手)、将数据放入缓冲区、服务端程序处理
  4. 收到结果 服务器返回 http 响应、浏览器解析响应、渲染页面、TCP 四次挥手

Status Code(状态码)

每次请求返回一个状态码,由三位数字表示

  • 1xx 过程中
    • 100 continue
  • 2xx 成功
    • 200 OK
  • 3xx 重定向
  • 4xx 客户端错误
    • 400 用户请求通用错误;
    • 403 用户无该路径权限;
    • 404 路径不存在;
  • 5xx 服务器错误
    • 500 服务器内部通用错误;
    • 501 Not Implemented
    • 502 bad gateway 有时请求已经打到 LB/Nginx 了,但是后面的服务出了问题
    • 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)

HTTPS 由于 HTTP 是明文文本传输,存在较大安全隐患。比如被读取密码、被篡改请求、被篡改 Response。 HTTPS 在 HTTP 的基础上通过传输加密和身份认证保证了传输过程的安全性。

SSL/TLS(Secure Sockets Layer/Transport Layer Security)

SSL 由 Netscape 公司 1994 年发明并于 1999 年改名为 TSL,是信息安全领域的权威标准。

  • SSL 的关键算法是 RSA,非对称加密,生成一对公、私钥,私钥自己留着、公钥公开出去。公钥加密后的数据可以用私钥解密;私钥加密后的数据可以用公钥解密;
  • RSA 这种非对称加密性能较差,所以一般不直接用于通信,而是用于生成摘要进行身份认证;或者用于加密对称加密的密钥。

  • 数字证书的组成 CA(Certificate Authority) 信息、公钥用户信息(组织信息)、公钥、证书序列号、有效期、最后是 CA 认证机构的签名
  • 数字证书的作用

    1. 通过数字证书向浏览器证明身份
    2. 数字证书里包含了公钥
  • 数字证书中的权威机构签名实际上就是上一级权威机构将当前证书信息进行 MD5(或其他 Hash 算法)进行摘要后,再用私钥进行一次加密的产物。这样外界只要拿着上级公钥对这个签名进行解密,就能得到证书信息的摘要,同时自己再利用证书信息生成一次摘要,如果两个摘要是一致的,说明是上一级权威机构签名的。
  • 数字证书一般以链式形式提供 比如存在一系列认证链 A->B->C,其中 A 是根证书、C 是当前用户证书。当前用户向外提供证书时,就要把一系列证书链都提供出去,这样便于进行链式验证,证明整条链的合法性。
  • 根证书比较特殊,无法由目标网站直接提供,一般是带在操作系统里的,由根证书机构提供。通常操作系统没什么问题,但也不排除整个操作系统+DNS 域名系统一起被篡改的情况,这样篡改的成本非常大。

HTTPS 流程

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

  1. 源端口、目的端口 在 TCP 通信时发起的一方要先知道监听方的端口号
  2. 序号 Seq 序号,占 32 位,用来标识从 TCP 源端向目的端发送的字节流,发起方发送数据时对此进行标记,每次根据发送数据量进行增长。
    • 假设一个报文段的序号字段是 301,且携带了 100 字节的数据,则第一个字节的序号是 301,最后一个字节的序号是 400。如果有下一个报文段,则数据序号应从 401 开始。
  3. 确认序号(Acknowledge Sequence) Ack 序号,占 32 位,只有 ACK 标志位为 1 时,确认序号字段才有效,Ack=Seq+1。
  4. 标志位:共 6 个,即 URG、ACK、PSH、RST、SYN、FIN 等,具体含义如下:
  • URG:紧急指针(urgent pointer)有效。
  • ACK:确认序号有效。
  • PSH:接收方应该尽快将这个报文交给应用层。
  • RST:重置连接。
  • SYN:发起一个新连接。
  • FIN:释放一个连接。
  1. 接收窗口 是指发送这个报文的发送者,缓冲区剩余容量的大小。
  2. 紧急指针 如果 URG 标识位为 1,则优先处理紧急指针这里的数据
  • 数据包通常由很多层组成,越靠前越是底层协议,例如 HTTP 基于 TCP 基于 IP,那么数据包结构是 IP 头 + IP 数据(TCP 头 + TCP 数据(HTTP 头 + HTTP 数据)),从数据上看是先看到各个协议的头
  • 数据包的头的顺序与协议栈相一致

TCP 连接管理

TCP 连接四元组:源地址、源端口、目的地址、目的端口

  • 可以用tcpdump工具抓包分析

三次握手(Three-way Handshake)

  • 三次握手的目的:
    1. 同步通信双方初始序列号(ISN, Initial Sequence Number)
    2. 协商 TCP 通信参数(MSS MaximumSegmentSize 最大报文长度、窗口信息、校验和算法)

TCP

  • 首先服务端要监听特定的端口号,发起握手的客户端要提前知道并使用这个端口号
  • 客户端发起连接时,一般会随机选择自己的一个端口号
  • 握手时,双方初次发出的 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 攻击。

四次挥手

四次挥手的关键是双向通道独立关闭过程。

TCP

  • MSL最大报文生存时间
    • MSL 默认是 2 分钟
    • 如果最后客户端没有等待 2MSL(Maximum Segment Lifetime) 就关闭,会造成两种问题:
      1. 万一最后的 ACK 没有到达服务端,那服务端会不断发 FIN,持续占用资源无法释放
      2. 前一个连接没有等待就关闭,立刻在相同端口号建立新连接,那么新旧数据就可能同时被接收,造成混乱
  • 双方都可以主动发起关闭连接,我们这里把主动关闭的一方称为客户端

  • 发送方调用 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 包的一些参数导致重用错误

数据可靠性保证

TCP 如果每次发送都要求一个 ACK,然后再发下一个包,这样效率极低。

实际上使用的是滑动窗口协议与累计确认(延时 ACK)算法。

TCP

  • 如图所示,发送数据队列分为下列区域:
    • 已发送并收到确认
    • 已发送但未收到确认
    • 未发送并处于发送窗口中
    • 未发送但未处于发送窗口中

TCP

  • 发送后的数据如果超时仍未收到 ACK,则会重发,并且限制窗口移动速度

常见问题

  • TCP 保证数据完整性吗?发送者如何知道发送成功? 保证完整性。发送方调用函数,最终将数据发到对方的缓冲区后就返回成功。

  • 为什么 TCP 保证了传输数据的正确性,还需要在应用层加 CRC 或 MD5 校验? 因为处于不同的层。TCP 在传输层保证正确性,但是应用层收到数据后可能未正常保存就崩溃了,这种情况下在 TCP 层看到是正常传输完毕的,而最终文件却有问题,需要校验。

  • 如果服务端 CPU 被占满,无法及时处理客户端数据,客户端数据是否会丢失? 根据 TCP 协议,操作系统只要把网卡数据拷贝到内核态接收缓冲区,就会返回 OK ACK,这样客户端就收到了确认,这种情况内核态接收缓冲区的数据不会丢失,迟早能被处理。如果 CPU 被占满,用户态来不及把数据从内核态缓冲区取走,导致内核态接收缓冲区被占满,网卡的数据无法复制到内核态接收缓冲区并向客户端发送 ACK。这种情况客户端的”滑动窗口”就无法向前移动,内核发送缓冲区会被占满,会不断重试并逐步加长每次重试的间隔,直到可以正常发送。所以也不会丢失数据。

  • 大量 CloseWait 状态的连接问题

    • 原因: 被动一方在收到 FIN 并发出 FIN_ACK 后会进入 CloseWait 状态。一般是因为服务端发了断开连接后,客户端没有发出 FIN_ACK,而是直接关闭了,导致服务端一直没收到 FIN_ACK。
    • 方案:
      1. 客户端要按协议做,保证发送 FIN_ACK 后,并等待 2MSL 再关闭。
      2. 服务端默认的 CloseWait 等待时间太长了,改小之后可以避免残留大量未关闭连接占用连接队列
  • 大量 TimeWait 状态的连接问题

    • 原因:
      • 主动关闭 TCP 连接的一方都会有 TimeWait 状态的连接,这是正常的
        1. TimeWait 保证了”全双工”的工作方式正常终止,如果主动断开方发完最后的 ACK,而这个 ACK 对方没收到,那么对方还可能在发 FIN,干扰新链接
        2. TimeWait 占用连接 4 元组,保证了重复报文不会影响下一个新建连接
      • 高并发短连接服务器上,当请求处理完后服务器主动断开连接,就会产生大量 TimeWait,如果客户端并发量很大就可能有部分客户端连接不上。 产生问题的原因是连接的使用时间远远短于 TimeWait 等待时间,造成大量连接队列被占。
      • 还有一种可能:SLB(Service Load Balancer)对 TCP 包进行修改,影响了 timestamp 参数,导致客户端大量 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 地址冲突