网络协议之TCP和HTTP

2022年 5月 12日 83点热度 0人点赞 0条评论

网络协议

国际标准化组织 (International Standard Organization, ISO) 公布了开放系统互连参考模型 (OSI/RM). OSI/RM 是一种分层的体系结构, 参考模型共有 7 层.

TCP/IP (Transmission Control Protocol/Internet Protocol) 作为 Internet 的核心协议. 它是个协议族, 包含多种协议.

分层的基本想法是每一层都在它的下层提供的服务基础上提供更高级的增值服务, 而最高层提供能运行分布式应用程序的服务.

file

发送请求的过程是从最顶层 (应用层) 出发, 每一层负责封装属于自己的信息到请求中, 最后将一整个请求发送给对方.

接收请求的过程是从最底层 (网络接口层) 开始, 每一层的协议负责解析属于自己的东西, 比如网际层 (IP) 处理 IP 信息, 传输层 (TCP) 处理点对点的端口, 应用层 (HTTP) 处理 Request 或 Response 的 Line\Header\Body.

TCP (Transmission Control Protocol, 传输控制协议)

TCP 是一种面向连接 (连接导向) 的, 可靠的基于字节流的传输层通信协议. TCP 将用户数据打包成报文段, 它发送后启动一个定时器, 另一端收到的数据进行确认, 对失序的数据重新排序, 丢弃重复数据.

TCP 的特点有:

  • TCP 是面向连接的运输层协议
  • 每一条 TCP 连接只能有两个端点, 每一条 TCP 连接只能是点对点的
  • TCP 提供可靠交付的服务
  • TCP 提供全双工通信. 数据在两个方向上独立的进行传输. 因此, 连接的每一端必须保持每个方向上的传输数据序号.
  • 面向字节流. 面向字节流的含义: 虽然应用程序和 TCP 交互是一次一个数据块, 但 TCP 把应用程序交下来的数据仅仅是一连串的无结构的字节流.

TCP 头格式

file

  • Source Port(源端口号): 数据发起者的端口号, 16bit.
  • Destination Port(目的端口号): 数据接收者的端口号, 16bit.
  • Sequence Number(顺序号码, seq): 用于在数据通信中解决网络包乱序 (reordering) 问题, 以保证应用层接收到的数据不会因为网络上的传输问题而乱序 (TCP 会用这个顺序号码来拼接数据), 32bit.
  • Acknowledgment Number(确认号码,ack): 是数据接收方期望收到发送方在下一个报文段的顺序号码 (seq), 因此确认号码应当是上次已成功收到顺序号码 (seq) 加 1, 32bit.
  • Offset (TCP 报文头长度): 用于存储报文头中有多少个 32bit (上图的一行), 存储长度为 4bit, 最大可表示 (2^3+2^2+2^1+1)*32bit=60bytes 的报文头. 最小取值 5, 5*32bit=20bytes.
  • Reserved (保留): 6bit, 均为 0
  • TCP Flags (TCP 标志位) 每个长度均为 1bit
    • CWR: 压缩, 0x80.
    • ECE: 拥塞, 0x40.
    • URG: 紧急, 0x20. 当 URG=1 时, 表示报文段中有紧急数据, 应尽快传送.
    • ACK: 确认, 0x10. 当 ACK = 1 时, 代表这是一个确认的 TCP 包, 取值 0 则不是确认包.
    • PSH: 推送, 0x08. 当发送端 PSH=1 时, 接收端尽快的交付给应用进程.
    • RST: 复位, 0x04. 当 RST=1 时, 表明 TCP 连接中出现严重差错, 必须释放连接, 再重新建立连接.
    • SYN: 同步, 0x02. 在建立连接是用来同步序号. SYN=1, ACK=0 表示一个连接请求报文段. SYN=1, ACK=1 表示同意建立连接.
    • FIN: 终止, 0x01. 当 FIN=1 时, 表明此报文段的发送端的数据已经发送完毕, 并要求释放传输连接.
  • 窗口: 用来控制对方发送的数据量, 通知发放已确定的发送窗口上限.
  • 检验和: 该字段检验的范围包括头部和数据这两部分. 由发端计算和存储, 并由收端进行验证.
  • 紧急指针: 紧急指针在 URG=1 时才有效, 它指出本报文段中的紧急数据的字节数.
  • TCP 选项: 长度可变, 最长可达 40 字节

备注: ISN (Inital Sequence Number): 初始化 Sequence Number, 发生在建立连接时.

TCP 协议中的三次握手和四次挥手

file

特别注意

  • seq: 是发送方当前报文的顺序号码.
  • ack: 是发送方期望对方在下次返回报文中给回的 seq.

建立连接需要三次握手

第一次握手: 客户端向服务端发送连接请求包, 标志位 SYN (同步序号) 置为 1, 顺序号码为 X=0.

第二次握手: 服务端收到客户端发过来报文, 由 SYN=1 知道客户端要求建立联机, 则为这次连接分配资源. 并向客户端发送一个 SYN 和 ACK 都置为 1 的 TCP 报文, 设置初始顺序号码 Y=0, 将确认序号 (ack) 设置为上一次客户端发送过来的顺序号 (Seq) 加 1, 即 X+1 = 0+1=1.

第三次握手: 客户端收到服务端发来的包后检查确认号码 (ack) 是否正确, 即第一次发送的 Seq 加 1(X+1=1). 以及标志位 ACK 是否为 1. 若正确, 服务端再次发送确认包,ACK 标志位为 1, SYN 标志位为 0. 确认号码 (ack)=Y+1=0+1=1, 发送顺序号码 (Seq) 为 X+1=1.Server 收到后确认号码值与 ACK=1 则连接建立成功, 可以传送数据了.

断开连接需要四次挥手

提醒: 中断连接端可以是 Client 端, 也可以是 Server 端. 只要将下面两角色互换即可.

第一次挥手: 客户端给服务端发送 FIN 报文, 用来关闭客户端到服务端的数据传送. 将标志位 FIN 和 ACK 置为 1, 顺序号码为 X=1, 确认号码为 Z=1. 意思是说 "我 Client 端没有数据要发给你了, 但是如果你还有数据没有发送完成, 则不必急着关闭 Socket, 可以继续发送数据, 所以你先发送 ACK 过来."

第二次挥手: 服务端收到 FIN 后, 发回一个 ACK (标志位 ACK=1), 确认号码为收到的顺序号码加 1, 即 X=X+1=2. 顺序号码为收到的确认号码=Z. 意思是说 "你的 FIN 请求我收到了, 但是我还没准备好, 请继续你等我的消息", 这个时候客户端就进入 FIN_WAIT 状态, 继续等待服务端的 FIN 报文.

第三次挥手: 当服务端确定数据已发送完成, 则向客户端发送 FIN 报文, 关闭与客户端的连接. 标志位 FIN 和 ACK 置为 1, 顺序号码为 Y=1, 确认号码为 X=2. 意思是告诉 Client 端 "好了, 我这边数据发完了, 准备好关闭连接了."

第四次挥手: 客户端收到服务器发送的 FIN 之后, 发回 ACK 确认 (标志位 ACK=1), 确认号码为收到的顺序号码加 1, 即 Y+1=2. 顺序号码为收到的确认号码 X=2. 意思是 "我 Client 端知道可以关闭连接了, 但是我还是不相信网络, 怕 Server 端不知道要关闭, 所以发送 ACK 后进入 TIME_WAIT 状态, 如果 Server 端没有收到 ACK 则可以重传. Client 端等待了 2MSL 后依然没有收到回复, 则证明 Server 端已正常关闭, 那好, 我 Client 端也可以关闭连接了." 在 TIME_WAIT 状态中, 如果 TCP client 端最后一次发送的 ACK 丢失了, 它将重新发送. TIME_WAIT 状态中所需要的时间是依赖于实现方法的. 典型的值为 30 秒, 1 分钟和 2 分钟. 等待之后连接正式关闭, 并且所有的资源 (包括端口号) 都被释放.

为什么关闭的时候却是四次挥 (握) 手?

因为当 Server 端收到 Client 端的 SYN 连接请求报文后, 可以直接发送 SYN+ACK 报文. 其中 ACK 报文是用来应答的, SYN 报文是用来同步的. 但是关闭连接时, 当 Server 端收到 FIN 报文时, 很可能并不会立即关闭 SOCKET, 所以只能先回复一个 ACK 报文, 告诉 Client 端, "你发的 FIN 报文我收到了". 只有等到我 Server 端所有的报文都发送完了, 我才能发送 FIN 报文, 因此不能一起发送. 故需要四步握手.

关于 TIME_WAIT 状态的说明

客户端最后一次发送 ACK 包后进入 TIME_WAIT 状态, 而不是直接进入 CLOSED 状态关闭连接, 这是为什么呢?

TCP 是面向连接的传输方式, 必须保证数据能够正确到达目标机器, 不能丢失或出错, 而网络是不稳定的, 随时可能会毁坏数据, 所以机器 A 每次向机器 B 发送数据包后, 都要求机器 B "确认", 回传 ACK 包, 告诉机器 A 我收到了, 这样机器 A 才能知道数据传送成功了. 如果机器 B 没有回传 ACK 包, 机器 A 会重新发送, 直到机器 B 回传 ACK 包.

客户端最后一次向服务器回传 ACK 包时, 有可能会因为网络问题导致服务器收不到, 服务器会再次发送 FIN 包, 如果这时客户端完全关闭了连接, 那么服务器无论如何也收不到 ACK 包了, 所以客户端需要等待片刻, 确认对方收到 ACK 包后才能进入 CLOSED 状态. 那么, 要等待多久呢?

数据包在网络中是有生存时间的, 超过这个时间还未到达目标主机就会被丢弃, 并通知源主机. 这称为报文最大生存时间 (MSL, Maximum Segment Lifetime). TIME_WAIT 要等待 2MSL 才会进入 CLOSED 状态. ACK 包到达服务器需要 MSL 时间, 服务器重传 FIN 包也需要 MSL 时间, 2MSL 是数据包往返的最大时间, 如果 2MSL 后还未收到服务器重传的 FIN 包, 就说明服务器已经收到了 ACK 包.

为什么连接的时候是三次握手, 关闭的时候却是四次握手?

建立连接时因为当 Server 端收到 Client 端的 SYN 连接请求报文后, 可以直接发送 SYN+ACK 报文. 其中 ACK 报文是用来应答的,SYN 报文是用来同步的. 所以建立连接只需要三次握手.

由于 TCP 协议是一种面向连接的, 可靠的, 基于字节流的运输层通信协议,TCP 是全双工模式. 这就意味着, 关闭连接时, 当 Client 端发出 FIN 报文段时, 只是表示 Client 端告诉 Server 端数据已经发送完毕了. 当 Server 端收到 FIN 报文并返回 ACK 报文段, 表示它已经知道 Client 端没有数据发送了, 但是 Server 端还是可以发送数据到 Client 端的, 所以 Server 很可能并不会立即关闭 SOCKET, 直到 Server 端把数据也发送完毕. 当 Server 端也发送了 FIN 报文段时, 这个时候就表示 Server 端也没有数据要发送了, 就会告诉 Client 端, 我也没有数据要发送了, 之后彼此就会愉快的中断这次 TCP 连接.

为什么要等待 2MSL?

MSL: 报文段最大生存时间, 它是任何报文段被丢弃前在网络内的最长时间. 有以下两个原因:

第一点: 保证 TCP 协议的全双工连接能够可靠关闭: 由于 IP 协议的不可靠性或者是其它网络原因, 导致了 Server 端没有收到 Client 端的 ACK 报文, 那么 Server 端就会在超时之后重新发送 FIN, 如果此时 Client 端的连接已经关闭处于 CLOESD 状态, 那么重发的 FIN 就找不到对应的连接了, 从而导致连接错乱, 所以, Client 端发送完最后的 ACK 不能直接进入 CLOSED 状态, 而要保持 TIME_WAIT, 当再次收到 FIN 的收, 能够保证对方收到 ACK, 最后正确关闭连接.

第二点: 保证这次连接的重复数据段从网络中消失如果 Client 端发送最后的 ACK 直接进入 CLOSED 状态, 然后又再向 Server 端发起一个新连接, 这时不能保证新连接的与刚关闭的连接的端口号是不同的, 也就是新连接和老连接的端口号可能一样了, 那么就可能出现问题: 如果前一次的连接某些数据滞留在网络中, 这些延迟数据在建立新连接后到达 Client 端, 由于新老连接的端口号和 IP 都一样,TCP 协议就认为延迟数据是属于新连接的, 新连接就会接收到脏数据, 这样就会导致数据包混乱. 所以 TCP 连接需要在 TIME_WAIT 状态等待 2 倍 MSL, 才能保证本次连接的所有数据在网络中消失.

TCP/IP 中 MSL 详解

MSL 是 Maximum Segment Lifetime 的英文缩写, 可译为"最长报文段寿命", 它是任何报文在网络上存在的最长的最长时间, 超过这个时间报文将被丢弃. 我们都知道 IP 头部中有个 TTL 字段,TTL 是 time to live 的缩写, 可译为"生存时间", 这个生存时间是由源主机设置设置初始值但不是但不是存在的具体时间, 而是一个 IP 数据报可以经过的最大路由数, 每经过一个路由器, 它的值就减 1, 当此值为 0 则数据报被丢弃, 同时发送 ICMP 报文通知源主机.RFC793 中规定 MSL 为 2 分钟, 但这完全是从工程上来考虑, 对于现在的网络,MSL=2 分钟可能太长了一些. 因此 TCP 允许不同的实现可根据具体情况使用更小的 MSL 值.TTL 与 MSL 是有关系的但不是简单的相等关系,MSL 要大于 TTL.

在 TCP 连接释放的过程中, 从 TIME_WAIT 状态到 CLOSED 状态有一个超时设置, 这个超时设置是 2MSL(RFC793 定义 MSL 为 2 分钟), 那么为什么在 TIME_WAIT 后必须等待 2MSL 时间呢? 主要原因有两点 (在我的上一篇博客中有讲, 我们再来说下吧):

为了保证客户端 (我们记为 A 端) 发送的最后一个 ACK 报文段能够到达服务器端. 这个 ACK 报文段有可能丢失, 因而使处在 LASK—ACK 端的服务器端 (我们记为 B 端) 收不到对已发送的 FIN+ACK 报文段.B 会超时重传这个 FIN+ACK 报文段, 而 A 就能在 2MSL 时间内收到这个重传的 FIN+ACK 报文段. 接着 A 重传一次确认, 重新启动 2MSL 计时器. 最后,A 和 B 都正常进入到 CLOSED 状态. 如果 A 在 TIME_WAIT 状态不等待一段时间, 而是在发送完 ACK 确认后立即释放连接, 那么就无法收到 B 重传的 FIN+ACK 报文段, 因而也不会再发送一次确认报文段, 这样,B 就无法正常进入 CLOSED 状态.

我们都知道, 假如 A 发送的第一个请求连接报文段丢失而未收到确认,A 就会重传一次连接请求, 后来 B 收到了确认, 建立了连接. 数据传输完毕后, 就释放了连接.A 共发送了两个连接请求报文段, 其中第一个丢失, 第二个到达了 B. 假如现在 A 发送的第一个连接请求报文段没有丢失, 而是在某些网络节点长时间都留了, 以至于延误到连接释放后的某个时间才到达 B, 这本来是已失效的报文段, 但 B 并不知道, 就会又建立一次连接. 而等待的这 2MSL 就是为了解决这个问题的,A 在发送完最后一个确认报后, 在经过时间 2MSL, 就可以使本链接持续时间内所产生的所有报文段都从网络中消失, 这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段.

我们回到 MSL, 在 2MSL 时间内, 该地址上的连接 (客户端地址, 端口和服务器的端口地址) 不能被使用, 比如我们在建立一个连接后关闭连接然后迅速重启连接, 那么就会出现端口不可用的情况.

TCP 报文抓取工具: Wireshark

捕获过滤器中填入表达式:host www.cnblogs.com and port 80 (80 等效于 http)

有多个 TCP 流时在显示过滤器中填入表达式:tcp.stream eq 0 筛选出第一个 TCP 流 (包含完整的一次 TCP 连接: 三次握手和四次挥手)

file

file

每条记录都有如下协议层

  1. Frame: 物理层的数据帧概况
  2. Ethernet II: 数据链路层以太网帧头部信息
  3. Internet Protocol Version 4: 互联网层 IP 包头部信息
  4. Transmission Control Protocol: 传输层的数据段头部信息, 此处是 TCP
  5. Hypertext Transfer Protocol: 应用层的信息, 此处是 HTTP 协议

file

TCP

TCP 的特性

  • TCP 提供一种面向连接的, 可靠的字节流服务
  • 在一个 TCP 连接中, 仅有两方进行彼此通信. 广播和多播不能用于 TCP
  • TCP 使用校验和, 确认和重传机制来保证可靠传输
  • TCP 给数据分节进行排序, 并使用累积确认保证数据的顺序不变和非重复
  • TCP 使用滑动窗口机制来实现流量控制, 通过动态改变窗口的大小进行拥塞控制

注意:TCP 并不能保证数据一定会被对方接收到, 因为这是不可能的.TCP 能够做到的是, 如果有可能, 就把数据递送到接收方, 否则就 (通过放弃重传并且中断连接这一手段) 通知用户. 因此准确说 TCP 也不是 100% 可靠的协议, 它所能提供的是数据的可靠递送或故障的可靠通知.

三次握手与四次挥手

所谓三次握手 (Three-way Handshake), 是指建立一个 TCP 连接时, 需要客户端和服务器总共发送 3 个包.

三次握手的目的是连接服务器指定端口, 建立 TCP 连接, 并同步连接双方的序列号和确认号, 交换 TCP 窗口大小信息. 在 socket 编程中, 客户端执行 connect() 时. 将触发三次握手.

  • 第一次握手 (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 域里, 同时将确认序号 (Acknowledgement Number) 设置为客户的 ISN 加 1, 即 X+1. 发送完毕后, 服务器端进入 SYN_RCVD 状态.

  • 第三次握手 (ACK=1,ACKnum=y+1)

客户端再次发送确认包 (ACK),SYN 标志位为 0,ACK 标志位为 1, 并且把服务器发来 ACK 的序号字段+1, 放在确定字段中发送给对方, 并且在数据段放写 ISN 的+1

发送完毕后, 客户端进入 ESTABLISHED 状态, 当服务器端接收到这个包时, 也进入 ESTABLISHED 状态,TCP 握手结束.

三次握手的过程的示意图如下:[无图言屌]

TCP 的连接的拆除需要发送四个包, 因此称为四次挥手 (Four-way handshake), 也叫做改进的三次握手. 客户端或服务器均可主动发起挥手动作, 在 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=y)

服务器端准备好关闭连接时, 向客户端发送结束连接请求,FIN 置为 1.

发送完毕后, 服务器端进入 LAST_ACK 状态, 等待来自客户端的最后一个 ACK.

  • 第四次挥手 (ACK=1,ACKnum=y+1)

客户端接收到来自服务器端的关闭请求, 发送一个确认包, 并进入 TIME_WAIT 状态, 等待可能出现的要求重传的 ACK 包.

服务器端接收到这个确认包之后, 关闭连接, 进入 CLOSED 状态.

客户端等待了某个固定时间 (两个最大段生命周期,2MSL,2 Maximum Segment Lifetime) 之后, 没有收到服务器端的 ACK , 认为服务器端已经正常关闭连接, 于是自己也关闭连接, 进入 CLOSED 状态.

四次挥手的示意图如下:[无图言屌]

TCP 重传

为了完成数据包的重传,TCP 套接字每次发送数据包时都会启动定时器, 如果在一定时间内没有收到目标机器传回的 ACK 包, 那么定时器超时, 数据包会重传.

上图演示的是数据包丢失的情况, 也会有 ACK 包丢失的情况, 一样会重传.

重传超时时间 (RTO, Retransmission Time Out)

这个值太大了会导致不必要的等待, 太小会导致不必要的重传, 理论上最好是网络 RTT 时间, 但又受制于网络距离与瞬态时延变化, 所以实际上使用自适应的动态算法 (例如 Jacobson 算法和 Karn 算法等) 来确定超时时间.

往返时间 (RTT,Round-Trip Time) 表示从发送端发送数据开始, 到发送端收到来自接收端的 ACK 确认包 (接收端收到数据后便立即确认), 总共经历的时延.

重传次数

TCP 数据包重传次数根据系统设置的不同而有所区别. 有些系统, 一个数据包只会被重传 3 次, 如果重传 3 次后还未收到该数据包的 ACK 确认, 就不再尝试重传. 但有些要求很高的业务系统, 会不断地重传丢失的数据包, 以尽最大可能保证业务数据的正常交互.

SYN 攻击

什么是 SYN 攻击 (SYN Flood)?

在三次握手过程中, 服务器发送 SYN-ACK 之后, 收到客户端的 ACK 之前的 TCP 连接称为半连接 (half-open connect). 此时服务器处于 SYN_RCVD 状态. 当收到 ACK 后, 服务器才能转入 ESTABLISHED 状态.

SYN 攻击指的是, 攻击客户端在短时间内伪造大量不存在的 IP 地址, 向服务器不断地发送 SYN 包, 服务器回复确认包, 并等待客户的确认. 由于源地址是不存在的, 服务器需要不断的重发直至超时, 这些伪造的 SYN 包将长时间占用未连接队列, 正常的 SYN 请求被丢弃, 导致目标系统运行缓慢, 严重者会引起网络堵塞甚至系统瘫痪.

SYN 攻击是一种典型的 DoS/DDoS 攻击.

如何检测 SYN 攻击?

检测 SYN 攻击非常的方便, 当你在服务器上看到大量的半连接状态时, 特别是源 IP 地址是随机的, 基本上可以断定这是一次 SYN 攻击. 在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击.

如何防御 SYN 攻击?

SYN 攻击不能完全被阻止, 除非将 TCP 协议重新设计. 我们所做的是尽可能的减轻 SYN 攻击的危害, 常见的防御 SYN 攻击的方法有如下几种:

  • 缩短超时 (SYN Timeout) 时间
  • 增加最大半连接数
  • 过滤网关防护
  • SYN cookies 技术

TCP KeepAlive

TCP 的连接, 实际上是一种纯软件层面的概念, 在物理层面并没有"连接"这种概念. TCP 通信双方建立交互的连接, 但是并不是一直存在数据交互, 有些连接会在数据交互完毕后, 主动释放连接, 而有些不会. 在长时间无数据交互的时间段内, 交互双方都有可能出现掉电, 死机, 异常重启等各种意外, 当这些意外发生之后, 这些 TCP 连接并未来得及正常释放, 在软件层面上, 连接的另一方并不知道对端的情况, 它会一直维护这个连接, 长时间的积累会导致非常多的半打开连接, 造成端系统资源的消耗和浪费, 为了解决这个问题, 在传输层可以利用 TCP 的 KeepAlive 机制实现来实现. 主流的操作系统基本都在内核里支持了这个特性.

TCP KeepAlive 的基本原理是, 隔一段时间给连接对端发送一个探测包, 如果收到对方回应的 ACK, 则认为连接还是存活的, 在超过一定重试次数之后还是没有收到对方的回应, 则丢弃该 TCP 连接.

TCP-Keepalive-HOWTO 有对 TCP KeepAlive 特性的详细介绍, 有兴趣的同学可以参考. 这里主要说一下, TCP KeepAlive 的局限. 首先 TCP KeepAlive 监测的方式是发送一个 probe 包, 会给网络带来额外的流量, 另外 TCP KeepAlive 只能在内核层级监测连接的存活与否, 而连接的存活不一定代表服务的可用. 例如当一个服务器 CPU 进程服务器占用达到 100%, 已经卡死不能响应请求了, 此时 TCP KeepAlive 依然会认为连接是存活的. 因此 TCP KeepAlive 对于应用层程序的价值是相对较小的. 需要做连接保活的应用层程序, 例如 QQ, 往往会在应用层实现自己的心跳功能.

KeepAlive 并不是 TCP 协议规范的一部分, 但在几乎所有的 TCP/IP 协议栈 (不管是 Linux 还是 Windows) 中, 都实现了 KeepAlive 功能.

如何设置它?

在设置之前我们先来看看 KeepAlive 都支持哪些设置项

KeepAlive 默认情况下是关闭的, 可以被上层应用开启和关闭

  • tcp_keepalive_time: KeepAlive 的空闲时长, 或者说每次正常发送心跳的周期, 默认值为 7200s(2 小时)
  • tcp_keepalive_intvl: KeepAlive 探测包的发送间隔, 默认值为 75s
  • tcp_keepalive_probes: 在 tcp_keepalive_time 之后, 没有接收到对方确认, 继续发送保活探测包次数, 默认值为 9(次)

我们讲讲在 Linux 操作系统和使用 Java,C 语言以及在 Nginx 如何设置.

在 Linux 内核设置

KeepAlive 默认不是开启的, 如果想使用 KeepAlive, 需要在你的应用中设置 SO_KEEPALIVE 才可以生效.

查看当前的配置:

cat /proc/sys/net/ipv4/tcp_keepalive_time
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
cat /proc/sys/net/ipv4/tcp_keepalive_probes

在 Linux 中我们可以通过修改 /etc/sysctl.conf 的全局配置:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9

添加上面的配置后输入 sysctl -p 使其生效, 你可以使用 sysctl -a | grep keepalive 命令来查看当前的默认配置

如果应用中已经设置 SO_KEEPALIVE, 程序不用重启, 内核直接生效

使用的场景

一般我们使用 KeepAlive 时会修改空闲时长, 避免资源浪费, 系统内核会为每一个 TCP 连接建立一个保护记录, 相对于应用层面效率更高.

常见的几种使用场景:

  • 检测挂掉的连接 (导致连接挂掉的原因很多, 如服务停止, 网络波动, 宕机, 应用重启等)
  • 防止因为网络不活动而断连 (使用 NAT 代理或者防火墙的时候, 经常会出现这种问题)
  • TCP 层面的心跳检测

KeepAlive 通过定时发送探测包来探测连接的对端是否存活, 但通常也会许多在业务层面处理的, 他们之间的特点:

  • TCP 自带的 KeepAlive 使用简单, 发送的数据包相比应用层心跳检测包更小, 仅提供检测连接功能
  • 应用层心跳包不依赖于传输层协议, 无论传输层协议是 TCP 还是 UDP 都可以用
  • 应用层心跳包可以定制, 可以应对更复杂的情况或传输一些额外信息
  • KeepAlive 仅代表连接保持着, 而心跳包往往还代表客户端可正常工作

和 Http 中 Keep-Alive 的关系

  • HTTP 协议的 Keep-Alive 意图在于连接复用, 同一个连接上串行方式传递请求-响应数据
  • TCP 的 KeepAlive 机制意图在于保活, 心跳, 检测连接错误

https://segmentfault.com/a/1190000021057175

HTTP(HyperText Transfer Protocol, 超文本传输协议)

HTTP 是一个应用层协议, 虽然在 2015 年已推出 HTTP/2 版本, 并被主要的 web 浏览器和 web 服务器支持. 但目前使用最广泛的还是 HTTP/1.1 版本. 有关历史请查阅这里.

它的主要特点可概括如下:

  • 支持客户/服务器模式.
  • 简单快速: 客户向服务器请求服务时, 只需传送请求方法和路径. 由于 HTTP 协议简单, 使得 HTTP 服务器的程序规模小, 因而通信速度很快.
  • 灵活:HTTP 允许传输任意类型的数据对象. 正在传输的类型由 Content-Type 加以标记.
  • 无连接: 无连接的含义是限制每次连接只处理一个请求. 服务器处理完客户的请求, 并收到客户的应答后, 即断开连接. 采用这种方式可以节省传输时间.
  • 无状态:HTTP 协议是无状态协议. 无状态是指协议对于事务处理没有记忆能力. 缺少状态意味着如果后续处理需要前面的信息, 则它必须重传, 这样可能导致每次连接传送的数据量增大. 另一方面, 在服务器不需要先前信息时它的应答就较快. 为了解决这个问题, Web 程序引入了 Cookie 机制来维护状态.
  • 另外,HTTP 请求报文和响应报文都是由开始行 (对于请求消息, 开始行就是请求行, 对于响应消息, 开始行就是状态行), 消息报头 (可选), 空行 (只有 CRLF 的行), 消息正文 (可选) 组成. 将在下面详细讲解.

请求报文结构

报文中的数据都使用 ASCII 编码, 各个字段的长度是不确定的 (除了作为结尾的 CRLF 外, 不允许出现单独的 CR 或 LF 字符).

file

请求报文样例

POST /search HTTP/1.1  
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, 
application/msword, application/x-silverlight, application/x-shockwave-flash, */*  
Referer: http://www.google.cn/  
Accept-Language: zh-cn  
Accept-Encoding: gzip, deflate  
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld)  
Host: www.google.cn 
Connection: Keep-Alive  
Cookie: PREF=ID=80a06da87be9ae3c:U=f7167333e2c3b714:NW=1:TM=1261551909:LM=1261551917:S=ybYcq2wpfefs4V9g; 
NID=31=ojj8d-IygaEtSxLgaJmqSjVhCspkviJrB6omjamNrSm8lZhKy_yMfO2M4QMRKcH1g0iQv9u-2hfBW7bUFwVh7pGaRUb0RnHcJU37y-
FxlRugatx63JLv7CWMD6UB_O_r

hl=zh-CN&source=hp&q=domety

请求报文参数详解

请求方法

所有请求方法名称全为大写, 目前有 9 种:

file

备注

关于 HTTP 请求 GET 和 POST 的区别

  • 提交形式:
    • GET 提交的数据会放在 URL 之后, 以 ? 分割 URL 和传输数据, 参数之间以&相连, 如 EditPosts.aspx?name=test1&id=123456.
    • POST 方法是把提交的数据放在 HTTP 包的 Body 中.
  • 传输数据的大小:
    • HTTP 协议本身没有对传输的数据大小进行限制,HTTP 协议规范也没有对 URL 长度进行限制. 而在实际开发中存在的限制主要有:
    • GET: 特定浏览器和服务器对 URL 长度有限制, 例如 IE 对 URL 长度的限制是 2083 字节 (2K+35). 对于其他浏览器, 如 Netscape, FireFox 等, 理论上没有长度限制, 其限制取决于操作系统的支持. 因此对于 GET 提交时, 传输数据就会受到 URL 长度的限制.
    • POST: 由于不是通过 URL 传值, 理论上数据不受限. 但实际各个 WEB 服务器会规定对 post 提交数据大小进行限制, Apache, IIS6 都有各自的配置.
  • 安全性:
    • POST 的安全性要比 GET 的安全性高, 具有真正的 Security 的含义. 而且通过 GET 提交数据, 用户名和密码将明文出现在 URL 上, 因为登录页面有可能被浏览器缓存, 其他用户浏览历史纪录就可以拿到账号和密码了.

请求报头域

报头域指头部中的 Key, 且不分大小写.

file

响应报文结构

如所见, 响应报文结构与请求报文结构唯一真正的区别在于第一行中用状态信息代替了请求信息. 状态行 (status line) 通过提供一个状态码来说明所请求的资源情况.

file

响应报文样例

HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Encoding: UTF-8
Content-Length: 138
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
ETag: "3f80f-1b6-3e1cb03b"
Accept-Ranges: bytes
Connection: close

<html>
<head>
  <title>An Example Page</title>
</head>
<body>
  Hello World, this is a very simple HTML document.
</body>
</html>

响应报文参数详解

响应状态码

状态代码由三位数字组成, 第一个数字定义了响应的类别, 且有五种可能取值.

  • 1xx: 指示信息--表示请求已接收, 继续处理.
  • 2xx: 成功--表示请求已被成功接收, 理解, 接受.
  • 3xx: 重定向--要完成请求必须进行更进一步的操作.
  • 4xx: 客户端错误--请求有语法错误或请求无法实现.
  • 5xx: 服务器端错误--服务器未能实现合法的请求.

常用状态码

  • 200 OK: 成功返回状态, 对应,GET,PUT,PATCH,DELETE.
  • 201 created - 成功创建.
  • 302 Found: 重定向, 新的 URL 会在 response 中的 Location 中返回, 浏览器将会使用新的 URL 发出新的 Request. 例如在 IE 中输入 http://www.google.com. HTTP 服务器会返回 304, IE 取到 Response 中 Location header 的新 URL, 又重新发送了一 个 Request.
  • 304 Not Modified: 代表上次的文档已经被缓存了, 还可以继续使用.
  • 400 bad request - 请求格式错误.
  • 401 unauthorized - 未授权.
  • 403 forbidden - 鉴权成功, 但是该用户没有权限.
  • 404 not found - 请求的资源不存在.
  • 405 method not allowed - 该 http 方法不被允许.
  • 410 gone - 这个 url 对应的资源现在不可用.
  • 415 unsupported media type - 请求类型错误.
  • 422 unprocessable entity - 校验错误时用.
  • 429 too many request - 请求过多.
  • 500 Internal Server Error: 服务器发生了不可预期的错误.
  • 503 Server Unavailable: 服务器当前不能处理客户端的请求, 一段时间后可能恢复正常.

其它状态码请查阅:https://en.wikipedia.org/wiki/List_of_HTTP_status_codes

响应报头域

报头域指头部中的 Key, 且不分大小写.

file

HTTP 报文抓取工具

Wireshark, Fiddler, HttpWatch(需结合 IE), Telnet

在显示过滤器中填入表达式:http and ip.addr == 42.121.252.58 and tcp.port == 80 过滤出 http 的响应和请求流程

file

Session 和 Cookie

说到 HTTP, 就不得不提 Session 和 Cookie. 但严格来说,Session 和 Cookie 并不是 http 协议的一部分. 由于 HTTP 协议设计原则是无状态的, 但是近年来出现了种种需求, 其中 cookie 的作用就是为了解决 HTTP 协议无状态的缺陷所作出的努力. 后来出现的 session 机制则是又一种在客户端与服务器之间保持状态的解决方案. 具体来说 cookie 机制采用的是在客户端保持状态的方案, 而 session 机制采用的是在服务器端保持状态的方案. 同时我们也看到, 由于采用服务器端保持状态的方案在客户端也需要保存一个标识, 所以 session 机制可能需要借助于 cookie 机制来达到保存标识的目的, 但实际上它还有其他选择.

Session

Session 是可以存储针对于某一个用户的浏览器以及通过其当前窗口打开的任何窗口具有针对性的用户信息存储机制.
通常大家认为, 只要关闭浏览器,session 就消失, 其实这是错误的理解. 对 session 来说也是一样的, 除非程序通知服务器删除一个 session, 否则服务器会一直保留. 由于关闭浏览器不会导致 session 被删除, 迫使服务器为 seesion 设置了一个失效时间, 当距离客户端上一次使用 session 的时间超过这个失效时间时, 服务器就可以认为客户端已经停止了活动, 才会把 session 删除以节省存储空间.

(1) 第一次访问某个 web 站点资源时, 客户端提交没有带 SessionID 的请求 (请求报文头没有 Cookie 头域信息).
而 web 服务器会检查是否有 SessionID 过来, 没有则创建 SessionID, 并根据 web 程序自身定义在请求哪个资源时添加属于当前会话的信息 (也可为空), 这个信息列表以 SessionID 作为标识. 然后将 SessionID 返回给客户端 (通过响应报文头的 Set-Cookie 头域).
(2 ) 客户端再次访问同个 web 站点时, 提交带有 SessionID 的请求 (通过 Cookie 头域存储 SessionID). 由服务端判断 session 是否失效, 如果未失效, 可查询属于当前会话的信息列表. 如果失效, 则创建新的 session(产生新的 SessionID), 而原先的 session(包含 session 带的信息列表) 则丢失, 无法访问.

Cookie

保存 SessionID 的方式可以采用 Cookie, 这样在交互过程中浏览器可以自动的按照规则把这个 SessionID 发回给服务器.Cookie 的命名方式类似于 SessionID. 有时 Cookie 被人为的禁止, 所以出现了其他机制以便在 Cookie 被禁止时仍然能够把 SessionID 传递回服务器. 这种技术叫做 URL 重写, 就是把 SessionID 直接附加在 URL 路径的后面, 附加方式也有两种, 一种是作为 URL 路径的附加信息, 表现形式为 http://www.wantsoft.com/index.asp;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764 .
另一种是作为查询字符串附加在 URL 后面, 表现形式为 http://www.wantsoft.com/index?js ... 99zWpBng!-145788764 .

file

file

相关资料

  • 《系统架构设计师教程》
  • 《C# 网络应用编程》(第 2 版)
  • Hypertext Transfer Protocol -- HTTP/1.1
  • OSI model
  • TCP/IP Reference
  • wireshark 抓包图解 TCP 三次握手/四次挥手详解
  • TCP 的那些事儿 (上)
  • TCP 协议中的三次握手和四次挥手 (图解)
  • Hypertext Transfer Protocol
  • HTTP 协议详解
  • HTTP 请求报文和 HTTP 响应报文
  • HTTP 协议详解 (经典)
  • HTTP 协议之 Session 和 Cookie

本文来自:https://blog.duhbb.com

本文链接地址:网络协议之TCP和HTTP,英雄不问来路,转载请注明出处,谢谢。

有话想说:那就赶紧去给我留言吧。

rainbow

这个人很懒,什么都没留下

文章评论