技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTNET

TCP、UDP、Socket、HTTP 网络编程面试题

所属馆藏
NET Protocol
文件格式
Markdown
原始路径
NET Protocol/07-TCP、UDP、Socket、HTTP网络编程面试题
本文目录50 个章节
  1. 什么是网络编程
  2. 网络编程中两个主要的问题
  3. 网络协议是什么
  4. 为什么要对网络协议分层
  5. 计算机网络体系结构
  6. 1.1 什么是 TCP/IP、TCP 和 UDP
  7. 1.2 TCP 与 UDP 的区别
  8. 1.3 TCP 和 UDP 的应用场景
  9. 1.4 形容一下 TCP 和 UDP
  10. 1.5 运行在 TCP 或 UDP 上的应用层协议
  11. 什么是 ARP 协议(Address Resolution Protocol)
  12. 什么是 NAT(Network Address Translation,网络地址转换)
  13. 从输入网址到获得页面的过程
  14. 1.6.1 什么是 TCP 的三次握手
  15. 1.6.2 三次握手的具体细节
  16. 1.6.3 用现实理解三次握手
  17. 1.6.4 建立连接可以两次握手吗?为什么?
  18. 1.6.5 可以采用四次握手吗?为什么?
  19. 1.6.6 第三次握手中,如果客户端的 ACK 未送达服务器,会怎样?
  20. 1.6.7 如果已经建立连接,但客户端出现故障怎么办?
  21. 1.6.8 初始序列号是什么?
  22. 1.7.1 什么是 TCP 的四次挥手
  23. 1.7.2 四次挥手的具体细节
  24. 1.7.3 用现实理解 TCP 的四次挥手
  25. 1.7.4 为什么不能把服务器的 ACK 和 FIN 合并成三次挥手?CLOSE_WAIT 有什么意义?
  26. 1.7.5 如果第二次挥手时服务器的 ACK 没有送达客户端,会怎样?
  27. 1.7.6 客户端 TIME-WAIT 状态的意义
  28. 2.1 什么是 Socket
  29. 2.2 Socket 属于网络的哪个层面
  30. 2.3 Socket 通讯的过程
  31. 2.4 TCP 协议 Socket 代码示例
  32. 2.5 UDP 协议 Socket 代码示例
  33. 2.6 Socket 的常用 Java 类
  34. 什么是 HTTP 协议
  35. Socket 和 HTTP 的区别及应用场景
  36. 什么是 HTTP 请求体
  37. HTTP 响应报文有哪些
  38. HTTP 和 HTTPS 的区别
  39. HTTPS 工作原理
  40. 一次完整的 HTTP 请求经历哪些步骤
  41. 常用 HTTP 状态码分类
  42. HTTP 协议有哪些请求方法
  43. GET 方法与 POST 方法的区别
  44. HTTP 版本的对比
  45. 什么是对称加密与非对称加密
  46. Cookie 和 Session 对 HTTP 有什么用
  47. 什么是 Cookie
  48. 什么是 Session
  49. Cookie 与 Session 的区别
  50. 参考资料

TCP、UDP、Socket、HTTP 网络编程面试题

Category(分类): NET Protocol Status: 优

本文尽量保留原文的面试题结构、比喻和代码示例,并结合 RFC 9293、RFC 768、RFC 8085、RFC 9110、RFC 9112、RFC 9113、RFC 9114、RFC 8446 以及现代浏览器实现进行修订。文中“TCP/IP 四层模型”和“OSI 七层模型”是帮助理解的抽象模型,实际协议和实现并不总能严格放进某一层。

什么是网络编程

  • 网络编程的本质是不同计算设备之间的数据交换。数据传递本身并不只是“把一个设备中的数据发送给另一个设备”,还涉及地址发现、路由、连接管理、数据边界、可靠性、拥塞控制、安全和应用协议解析。常见网络程序采用请求/响应方式:一个程序发送请求,另一个程序返回响应。发起连接或请求的一方通常称为客户端(Client),等待连接或提供服务的一方通常称为服务器(Server)。
  • 客户端可以按需启动,服务器一般持续运行并监听端口,但这不是绝对规则。P2P、WebSocket、消息队列和服务间通信中,一台设备或一个进程也可能同时具有客户端和服务器的角色。
  • 例如打电话时,首先拨号的一方类似客户端,等待接听的一方类似服务器。连接建立后,双方都可以发送数据,TCP 连接本身是双向的;客户端和服务器只是当前交互中的角色,不代表某一方永远只能发送或接收。

网络编程中两个主要的问题

  1. 如何准确地定位网络上的一个或多个主机、接口和服务;
  2. 找到目标后,如何以合适的可靠性、吞吐量、时延和安全性传输数据。
  • IP 层主要负责寻址和分组转发,但 IP 地址并不一定唯一对应一台物理主机:一台主机可以有多个接口和地址,多个主机也可能通过 NAT 共享公网地址。
  • 传输层提供端到端的传输抽象。TCP 提供可靠、有序的字节流;UDP 提供尽量简单的无连接数据报服务,不保证送达、顺序或不重复。QUIC 运行在 UDP 之上,但在用户态补充了可靠流、拥塞控制、TLS 保护和连接迁移等能力。
  • 网络编程通常通过 Socket API 使用 TCP、UDP、Unix domain socket 或其他协议。应用程序仍然需要自己定义消息格式、消息边界、超时、重试、鉴权和错误处理。
  • 目前常见的网络模型包括客户端/服务器(C/S)、浏览器/服务器(B/S)、发布/订阅、P2P 和微服务模型。服务器一般作为长期运行的进程监听端口,但也可以采用事件驱动、线程池、协程、无服务器函数等实现,而不一定“每来一个客户端就启动一个进程”。

网络协议是什么

在计算机网络中要有条理地交换数据,就必须遵守事先约定的规则,例如地址格式、消息结构、编码方式、是否需要确认、错误如何处理和连接如何关闭。这些规则被称为网络协议。

协议不仅规定“发送什么”,也规定接收方如何解释数据。双方即使都能发送字节,如果没有共同的协议和 framing(消息边界)约定,也无法可靠地还原业务消息。

为什么要对网络协议分层

  • 降低问题复杂度:将寻址、路由、可靠传输、加密和应用语义拆分为相对独立的问题;
  • 便于替换技术:某一层的技术发生变化时,只要保持对上层的接口和语义,其他层可以少受影响,例如 HTTP/3 将 HTTP 语义映射到 QUIC;
  • 便于实现和维护:每层有相对清晰的职责和测试边界;
  • 促进标准化:不同厂商可以围绕统一的层间接口和协议规范实现互操作。

分层是模型,不表示真实报文一定严格只经过某一层,也不表示所有协议都能无争议地归入某一层。TLS、QUIC、Socket API 等都可能跨越教科书中的层次边界。

计算机网络体系结构

TCP/IP 与 OSI 模型对照

OSI 参考模型

  • OSI(Open Systems Interconnection,开放系统互联)参考模型由 ISO 制定,用于描述网络通信的七层框架:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
  • OSI 是参考模型,不是“所有公司都必须逐层实现”的工程协议栈。现实中的 TCP/IP 协议族并没有严格实现独立的会话层和表示层,TLS、RPC、序列化等功能可能分布在不同位置。

OSI 七层模型示意

TCP/IP 参考模型

常见的 TCP/IP 四层模型为:

  1. 应用层:为应用提供协议和数据格式,例如 HTTP、DNS、SMTP、SSH、FTP。HTTPS 可以理解为 HTTP 与 TLS 的组合;
  2. 传输层:提供进程到进程的传输抽象,TCP、UDP、SCTP、QUIC(从应用使用角度常被视为传输协议)都与这一层有关。端口号用于区分主机上的服务或进程;
  3. 网际层/网络层:IP 负责跨网络寻址和分组转发;ICMP 用于差错报告和诊断;
  4. 网络接口层/链路层:处理局域网链路上的帧、介质访问和链路地址。物理传输有时另列为物理层,因此也常见 TCP/IP 五层模型。

原文将“数据链路层”描述成同时包含硬件、软件和物理线路,这在入门模型中可以理解,但严格划分时应区分链路层和物理层。

1 TCP / UDP

1.1 什么是 TCP/IP、TCP 和 UDP

TCP/IP 不是一个单独的协议,而是以 IP 为核心的一组协议族。TCP(Transmission Control Protocol)是其中的传输层协议;UDP(User Datagram Protocol)也是传输层协议。

  • TCP 是面向连接、可靠、有序的字节流协议。它通过序列号、确认、重传、校验和、接收窗口和拥塞控制等机制,把应用写入的字节流尽量按序交付给对端应用。TCP 不保留应用层消息边界,连接异常中断时,尚未被确认的数据仍可能丢失。
  • UDP 是无连接的数据报协议。每次发送一个 UDP 数据报,接收端按数据报边界接收;UDP 不保证送达、不保证顺序、不保证不重复,也不提供像 TCP 那样的拥塞控制和连接状态。UDP 具有校验和机制,不能简单说“内容正确性完全不可能保证”,但应用不能把校验和当作可靠传输机制。
  • UDP 本身简单,不等于应用一定不可靠。DNS、音视频协议和 QUIC 都可以在 UDP 之上增加重试、序号、校验、拥塞控制或可靠流;反过来,使用 TCP 也不代表业务一定成功,因为连接可能在应用确认前中断。

1.2 TCP 与 UDP 的区别

  • TCP 是面向连接的;UDP 不需要建立 TCP 式连接;
  • TCP 提供可靠、有序的字节流;UDP 提供尽力而为的数据报;
  • TCP 的数据没有消息边界,接收方需要通过长度字段、分隔符、固定长度或关闭连接等方式自行组帧;UDP 一个数据报对应一次接收操作的消息边界,但超过路径或接收能力仍可能丢弃或分片;
  • TCP 通常支持一对一的连接。UDP 可以一对一、一对多、多对一或多对多,是否允许广播/组播还取决于网络和 Socket 配置;
  • TCP 首部最小 20 字节,UDP 首部 8 字节,但实际传输开销还包括 IP、链路层、TLS、QUIC 或应用层开销,不能只比较这两个数字;
  • TCP 维护连接状态、发送/接收窗口和拥塞控制状态;UDP 协议本身维护的状态很少,应用或内核仍可能维护发送队列、NAT 映射等状态;
  • TCP 面向连接并不等于“永远长连接”,连接可以超时、被关闭或被 RST;UDP 无连接也不等于不能持续发送多个数据报。

TCP 通信可以类比打电话:双方先建立连接,再持续交流。UDP 通信可以类比广播:发送方发出一条消息,不要求接收方先建立会话,也不自动知道对方是否收到。

1.3 TCP 和 UDP 的应用场景

  • 需要可靠、有序传输且不希望应用自己实现重传和拥塞控制时,常使用 TCP,例如传统 HTTP/1.1、HTTP/2、SSH、FTP 控制连接、SMTP、数据库连接;
  • 对时延、实时性或组播更敏感,能够容忍少量丢包,或希望自行设计传输机制时,可以使用 UDP,例如实时音视频、在线游戏、DNS 查询、NTP;
  • “实时就一定用 UDP、其他都用 TCP”不是绝对规则。WebRTC 使用 UDP 为主但有 TCP/TLS 回退;QUIC/HTTP/3 使用 UDP 却提供可靠流;实时应用也可能根据数据类型同时使用可靠和不可靠传输。

1.4 形容一下 TCP 和 UDP

TCP 通信可看作打电话

李三:喂,是王五吗?
王五:哎,您谁啊?
李三:我是李三,我想给你说点事儿,你现在方便吗?
王五:哦,我现在方便,你说吧。
(连接建立,接下来传输应用数据)

这个比喻用于说明连接建立和双向通信,但 TCP 并不理解“句子”或“通话内容”,它只传输有序字节流。

UDP 通信可看作学校广播

播音室:喂喂喂!全体操场集合!

广播发出后,发送方不会从 UDP 协议本身得到“所有人都听到了”的保证;应用若需要确认,必须自行设计反馈。

1.5 运行在 TCP 或 UDP 上的应用层协议

常见使用 TCP 的协议

  • HTTP/1.1、HTTP/2:现代实现通常运行在 TCP 之上;HTTP/3 改为运行在 QUIC 之上;
  • HTTPS:HTTP over TLS,不是与 HTTP 完全无关的新应用语义;TLS 可以运行在 TCP 或 QUIC 等传输之上;
  • FTP:控制连接通常使用 TCP,数据连接也使用 TCP;
  • POP3、SMTP、IMAP:邮件收取和传输协议,通常通过 TLS 或 STARTTLS 保护;
  • Telnet:基于 TCP 的远程终端协议,但明文传输,现代环境通常使用 SSH 代替;
  • SSH:基于 TCP 的加密远程登录和隧道协议;
  • 传统数据库协议:多数使用 TCP,但具体取决于数据库和驱动实现。

常见使用 UDP 的协议

  • DHCP:IPv4 中客户端通常使用 UDP 68,服务器使用 UDP 67;
  • NTP:通常使用 UDP 123;
  • DNS 查询:通常优先使用 UDP,响应过大、区域传送或特定场景会使用 TCP;DNS 也可以通过 TLS(DoT)或 HTTPS(DoH)传输;
  • QUIC:运行在 UDP 之上,为 HTTP/3 等应用提供可靠流、拥塞控制、TLS 1.3 集成和连接迁移;
  • 实时媒体协议:许多 RTP/RTCP 部署使用 UDP,但可靠性和拥塞控制由上层或配套协议负责。

既可以使用 TCP 也可以使用 UDP 的协议

  • DNS:并非只能使用 UDP;
  • ECHO:RFC 862 定义的诊断协议,可以使用 TCP 或 UDP,但现代公网通常禁用;
  • SNMP:传统上主要使用 UDP,也有 TCP、TLS、DTLS 等传输映射;
  • 部分自定义应用协议:可以根据需求选择不同传输,不应仅凭协议名称推断。

ARP 不属于运行在 TCP 或 UDP 之上的应用层协议,它直接服务于 IPv4 局域网中的下一跳链路寻址,应单独理解。

什么是 ARP 协议(Address Resolution Protocol)

  • ARP 用于在 IPv4 局域网中把一个 IPv4 地址解析成对应的链路层地址(例如以太网 MAC 地址)。它不是把 IP 地址映射成抽象的“物理地址”,也不跨路由器查询远端主机。
  • 每个主机通常维护 ARP 缓存。当源主机要发送 IPv4 数据包时,会先判断目的 IPv4 地址是否属于本地子网:
    • 如果目的主机在同一局域网,主机查询或广播 ARP 请求,获取目的主机的链路地址;
    • 如果目的主机在远端网络,主机解析的是默认网关或下一跳路由器的链路地址,然后把 IP 数据包交给路由器转发。
  • ARP 请求通常以链路层广播发送;目标主机发现请求中的目标 IP 是自己后,返回 ARP 响应。缓存项会过期,不能假设映射永久有效。
  • IPv6 不使用 ARP,而使用 ICMPv6 Neighbor Discovery(邻居发现协议)。
  • ARP 没有内置强身份认证,局域网中存在 ARP 欺骗风险,企业网络可以结合交换机防护、静态绑定、动态 ARP 检测和加密协议降低风险。

什么是 NAT(Network Address Translation,网络地址转换)

NAT 由网络设备修改经过的数据包中的地址,很多家庭路由器还会同时转换端口,因此常见的是 NAPT/PAT。它主要用于缓解 IPv4 地址不足、实现内网地址复用以及控制入站连接。

  • 静态 NAT:内外地址之间固定映射;
  • 动态 NAT:从地址池中分配外部地址;
  • PAT/NAPT:多个内网地址通过不同端口共享一个或少量公网 IPv4 地址;
  • 端口映射/端口转发:允许外部连接映射到内网服务。

NAT 会破坏端到端可达性,使入站连接、P2P、VoIP 和某些协议需要 NAT 穿透、STUN、TURN、UPnP 或端口映射。NAT 不是完整的防火墙,也不是加密安全机制;IPv6 通常不需要用 NAT 解决地址不足,但仍可能部署防火墙或其他地址转换方案。

从输入网址到获得页面的过程

传统教材常把过程简化成“DNS → TCP 三次握手 → HTTP 请求 → 关闭 TCP”。现代浏览器还会使用缓存、Service Worker、代理、连接池、TLS、HTTP/2 多路复用和 HTTP/3/QUIC,因此实际过程可能不同。

  1. 解析 URL:确定 scheme、主机、端口、路径和查询参数;浏览器还可能受到 HSTS、Service Worker、扩展、代理和缓存影响;
  2. 查找地址:浏览器和操作系统可能先查自身缓存、Hosts 文件和本地 DNS stub,再向配置的递归 DNS 解析器查询。递归解析器可能从缓存、权威 DNS、根服务器、顶级域服务器获得 A/AAAA 等记录;
  3. 选择连接:根据协议协商结果选择 HTTP/1.1、HTTP/2 或 HTTP/3。HTTP/1.1/2 常见路径是 TCP(HTTPS 还需要 TLS),HTTP/3 是 QUIC(QUIC 集成 TLS 1.3);已有可复用连接时不一定重新握手;
  4. 发送 HTTP 请求:请求可能经过正向代理、CDN、负载均衡和反向代理。HTTP/2/3 可以在同一连接上复用多个 Stream;
  5. 服务端处理和返回响应:服务端根据方法、目标 URI、首部、认证信息和请求内容处理请求,返回状态码、响应首部和可选内容。发生重定向时,浏览器可能继续请求新的 URI;
  6. 解析和渲染:浏览器解析 HTML、CSS、JavaScript 和图片等资源,并根据缓存策略、优先级和安全策略请求子资源;
  7. 连接继续复用或关闭:HTTP/1.1 持久连接、HTTP/2 连接和 HTTP/3 连接都可能继续服务后续请求,不能把“服务器关闭 TCP 连接”当作每个 HTTP 请求的必经步骤。

1.6 TCP 的三次握手

1.6.1 什么是 TCP 的三次握手

TCP 建立连接的过程通常称为三次握手。它不是简单的“确认服务器开机”,而是让双方交换初始序列号、确认对方的控制报文已经到达,并建立双方的连接状态。

TCP 三次握手示意

1.6.2 三次握手的具体细节

设客户端初始序列号为 x,服务端初始序列号为 y

  1. 第一次握手:客户端发送 SYN=1, SEQ=x,表示请求建立连接并声明自己的初始序列号,客户端进入 SYN-SENT
  2. 第二次握手:服务端收到客户端 SYN 后,发送 SYN=1, ACK=1, SEQ=y, ACK=x+1。这同时确认了客户端的 SYN,并声明服务端自己的初始序列号,服务端进入 SYN-RECEIVED
  3. 第三次握手:客户端收到并验证服务端的 SYN+ACK 后,发送 ACK=1, ACK=y+1。客户端进入 ESTABLISHED;服务端收到这个 ACK 后也进入 ESTABLISHED,随后双方可以发送应用数据。

简化表示:

客户端                         服务端
  SYN, SEQ=x        ────────>
                    <────────  SYN+ACK, SEQ=y, ACK=x+1
  ACK= y+1          ────────>

SYN 会占用一个序列号,因此确认号要加 1。TCP 序列号和确认号是 32 位序号空间中的值,会发生回绕,实际判断还结合窗口和时间戳等机制。

三次握手分别确认了什么

  • 第一次握手到达服务端,服务端确认自己可以接收客户端发来的 SYN,但此时服务端还不知道客户端是否能收到服务端的报文;
  • 第二次握手到达客户端,客户端可以确认服务端发出了 SYN+ACK,并能接收服务端报文;
  • 第三次握手到达服务端后,服务端才确认客户端收到了自己的 SYN+ACK,并且客户端到服务端的反向路径可用。

因此三次握手能够在正常情况下确认双方的收发路径和初始序列号。它不能保证应用程序一定存活,也不能保证后续业务处理成功。

1.6.3 用现实理解三次握手

客户端:喂,是服务端吗?(SYN)
服务端:是我,我听到了,你能听到我吗?(SYN + ACK)
客户端:听到了,我也确认收到你的消息。(ACK)

1.6.4 建立连接可以两次握手吗?为什么?

正常 TCP 连接不能用两次握手替代三次握手。原因包括:

  • 旧网络中可能存在延迟到达的重复 SYN。若服务端收到后仅凭一次响应就建立连接,而客户端其实没有发起当前连接,服务端可能分配资源并长期等待,形成半开连接;
  • 两次握手不能让服务端确认客户端收到自己的 SYN+ACK;
  • 三次握手让双方能够完成初始序列号的双向确认,并避免把过期的连接请求误认为新连接。

两次消息在某些抽象协议中可能足够,但 TCP 需要面对旧报文、双向可达性和序列空间等问题,所以正常建立过程至少需要三次控制报文。实际抓包中第三次 ACK 可能和第一段应用数据合并,但逻辑上的 ACK 仍然存在。

1.6.5 可以采用四次握手吗?为什么?

可以发送额外的确认或控制报文,但没有必要把正常 TCP 建连设计成四次。三次握手已经完成必要的状态同步,额外报文只会增加时延和开销。某些 TCP 扩展、代理或应用协议可能在握手后继续交换自己的协商消息,那属于应用或扩展层,不是 TCP 必需的第四次握手。

1.6.6 第三次握手中,如果客户端的 ACK 未送达服务器,会怎样?

  • 服务端没有收到第三次 ACK 时,通常仍停留在 SYN-RECEIVED,并按照实现配置的重传计时器和退避策略重发 SYN+ACK。重传间隔、次数和最终超时时间不是固定的“每 3 秒、五次”,会因操作系统、网络和配置而变化;
  • 如果客户端再次发送一个包含可接受 ACK 的报文,服务端可能据此完成状态转换;
  • 如果服务端最终放弃半开连接,客户端之后发送的报文可能收到 RST 或其他错误;
  • 客户端通常已经进入 ESTABLISHED,它可能重传 ACK,或在发送数据时携带 ACK。是否能恢复取决于服务端当前状态和网络情况。

1.6.7 如果已经建立连接,但客户端出现故障怎么办?

TCP 可以通过重传超时发现未确认数据失败,也可以使用可选的 TCP keep-alive 探测空闲连接。但 keep-alive 不是所有实现都默认开启,RFC 规定的是能力和建议,并不是所有系统都使用“空闲 2 小时、每 75 秒探测、连续 10 次失败”这一固定组合。

现代服务通常还会使用应用层心跳、请求超时、连接池健康检查和负载均衡探活。TCP keep-alive 只能说明传输层对端是否仍有响应,不能证明应用服务健康或业务请求已经处理成功。

1.6.8 初始序列号是什么?

TCP 连接的一方会选择一个 32 位初始序列号(Initial Sequence Number,ISN)。它不是为了给每条应用消息编号,而是为连接中的字节流建立序号空间,并降低旧连接报文干扰新连接的风险。

例如发送方从 1000 开始,若发送 1000 字节,则这些字节占用相应的序号区间。对端返回 ACK=2001 时,通常表示序号小于 2001 的数据已经按序收到,下一步期望序号为 2001。ACK 反映的是 TCP 接收和确认状态,不等于应用程序已经读取或业务已经持久化。

SYN 和 FIN 各自占用一个序列号;纯 ACK 不占用序列号。TCP 是字节流,不能把一次 send()、一个应用对象或一个 TCP Segment 当成天然的业务消息边界。

1.7 TCP 的四次挥手

1.7.1 什么是 TCP 的四次挥手

TCP 是全双工连接,两个方向可以独立关闭。通常一方先发送 FIN 关闭自己的发送方向,另一方先 ACK,再在自己没有更多数据时发送 FIN,因此常见的是四个控制报文。实际情况可能是三段、四段或更多段:ACK 和 FIN 可以合并,也可能出现同时关闭和重传。

TCP 四次挥手示意

1.7.2 四次挥手的具体细节

以下以客户端主动关闭为例:

  1. 第一次挥手:客户端发送 FIN(通常也带 ACK),表示客户端不再发送数据,进入 FIN-WAIT-1
  2. 第二次挥手:服务端收到 FIN 后发送 ACK,确认号为客户端 FIN 序号加 1,进入 CLOSE-WAIT。此时服务端仍可以继续向客户端发送尚未完成的数据;
  3. 第三次挥手:服务端应用完成发送后,TCP 发送 FIN,表示服务端发送方向也关闭,服务端进入 LAST-ACK
  4. 第四次挥手:客户端收到服务端 FIN 后发送 ACK,进入 TIME-WAIT;服务端收到 ACK 后通常进入 CLOSED。客户端等待约 2×MSL 后释放连接状态。
客户端                                      服务端
  FIN, SEQ=u       ───────────────────────>
                   <───────────────────────  ACK=u+1
                   <───────────────────────  FIN, SEQ=v
  ACK=v+1          ───────────────────────>
       TIME-WAIT                         CLOSED

FIN 表示发送 FIN 的一方在该方向没有更多数据,不等于双方立即停止接收。第二次挥手的 ACK 只是确认收到了 FIN,不代表服务端已经同意立即关闭或服务端数据已经发送完毕。

1.7.3 用现实理解 TCP 的四次挥手

客户端:我这边说完了,不再发送了。(第一次挥手)
服务端:收到,我知道你不再发送了。(第二次挥手)
服务端:我这边剩余内容也发送完了,我也不再发送了。(第三次挥手)
客户端:收到你关闭发送方向的消息。(第四次挥手)

“四次”是典型的独立半关闭过程,不是任何断开都严格产生四个 TCP Segment。RST 异常中止连接时,通常不会走完整的 FIN 挥手。

1.7.4 为什么不能把服务器的 ACK 和 FIN 合并成三次挥手?CLOSE_WAIT 有什么意义?

如果服务端收到客户端 FIN 时已经没有待发送数据,ACK 和 FIN 可以在同一个报文中发送,实际抓包可能只有三段。不能要求所有场景都合并,是因为服务端收到客户端 FIN 时可能还有数据未发送完:

  • 服务端先 ACK,进入 CLOSE-WAIT
  • 服务端继续发送剩余数据;
  • 应用关闭 Socket 后,TCP 再发送 FIN,进入 LAST-ACK

CLOSE-WAIT 说明对端已经关闭了它的发送方向,而本地应用还没有关闭自己的发送方向。服务端长期处于 CLOSE_WAIT,通常意味着应用没有及时关闭连接或存在资源泄漏。

1.7.5 如果第二次挥手时服务器的 ACK 没有送达客户端,会怎样?

客户端没有收到对自己 FIN 的确认,会根据 TCP 重传计时器重传 FIN,而不是按照固定时间简单重发。服务端收到重复 FIN 后通常再次发送 ACK。只要服务端仍保留连接状态,关闭流程可以继续;若服务端已经超时清理,客户端可能收到 RST。

1.7.6 客户端 TIME-WAIT 状态的意义

主动关闭的一方进入 TIME-WAIT,主要有两个目的:

  1. 确保最后一个 ACK 有机会重传:如果服务端没有收到最后 ACK,会重传 FIN;客户端在 TIME-WAIT 中可以再次确认;
  2. 防止旧连接的延迟报文干扰新连接:等待旧报文在网络中消失,避免相同四元组快速复用时产生歧义。

RFC 9293 要求主动关闭方等待 2×MSL,但现代系统可能根据时间戳和实现策略优化连接复用。TIME-WAIT 多并不自动表示网络异常,短连接过多、主动关闭角色、端口范围和连接复用策略都可能造成大量 TIME-WAIT。

2 Socket

2.1 什么是 Socket

Socket 是操作系统向应用提供的网络通信接口和抽象对象。它允许应用通过统一 API 使用 TCP、UDP、Unix domain socket、SCTP、原始套接字等不同协议,具体能力取决于操作系统和 Socket 类型。

  • TCP 连接的一个端点可以用本地地址、端口、远端地址、远端端口和协议共同描述;“一个 Socket 由一个 IP 地址和端口唯一确定”是入门时期的简化说法,不足以描述 TCP 连接。服务器监听 Socket 和已建立连接的 Socket 也不是同一个对象;
  • Java 中 java.net.Socket 主要表示 TCP 客户端连接,ServerSocket 用于监听 TCP 连接,DatagramSocket 用于 UDP 数据报;
  • Socket API 偏底层,HTTP 客户端、数据库驱动、WebSocket 库和 RPC 框架通常在其上实现协议和连接管理;
  • TCP Socket 的连接可以长时间保持,但“Socket 连接就是长连接”不准确。是否保持连接由 TCP 状态、应用协议、超时、连接池和对端行为共同决定;HTTP/1.1、HTTP/2 也可以复用持久连接。

Socket 抽象示意

2.2 Socket 属于网络的哪个层面

Socket 不是 OSI 或 TCP/IP 模型中的一个独立网络协议层,而是应用程序与操作系统网络协议栈之间的编程接口。它把 bind、listen、accept、connect、send、recv、shutdown 等操作抽象出来,让应用不必直接操作 IP、TCP 报文和网卡。

Socket API 与协议栈关系

把 Socket 称为“应用层与 TCP/IP 协议族之间的中间软件抽象层”可以作为入门理解,但它并不只支持 TCP/IP,也不负责自动定义业务消息格式。

2.3 Socket 通讯的过程

基于 TCP

服务器端通常执行:

  1. 创建监听 Socket;
  2. bind 绑定本地地址和端口;
  3. listen 进入监听状态;
  4. accept 等待客户端连接;
  5. 对每个已建立连接使用独立的连接 Socket 收发字节流;
  6. 完成协议交互后 shutdownclose

客户端通常执行:

  1. 创建 Socket;
  2. connect 连接服务端地址和端口;
  3. 按应用协议发送和读取数据;
  4. 关闭或复用连接。

一个阻塞式 BIO 服务器可以为每个连接创建线程,也可以一次只处理一个连接。生产环境通常使用线程池、NIO、异步 IO 或事件循环。

基于 UDP

UDP 服务端通常创建并绑定 DatagramSocket,通过 receive 接收一个个数据报;客户端创建 Socket 后通过 send 把数据报发送到目标地址。UDP 不需要 TCP 式的三次握手,但应用可以使用 connect 记录默认对端并过滤来源,这不等于建立了 TCP 可靠连接。

UDP 发送成功通常只代表数据交给了本机协议栈,不代表目标主机已经接收或应用已经处理。需要可靠性时,应在应用层设计序号、确认、超时、重传、去重、拥塞控制和安全校验。

2.4 TCP 协议 Socket 代码示例

先运行服务端,再运行客户端。示例使用换行作为应用层消息分隔符,演示了 TCP 字节流必须自行定义消息边界。它是单线程 BIO 示例,不适合直接用于高并发生产环境。

服务端

package com.test.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class BIOServer {
    public static void main(String[] args) throws IOException {
        try (ServerSocket server = new ServerSocket(8000)) {
            System.out.println("服务端启动成功,监听端口为 8000...");

            while (true) {
                try (Socket socket = server.accept();
                     BufferedReader in = new BufferedReader(
                         new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
                     BufferedWriter out = new BufferedWriter(
                         new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8))) {

                    System.out.println("客户连接成功:" + socket.getRemoteSocketAddress());
                    String line;
                    while ((line = in.readLine()) != null) {
                        System.out.println("收到:" + line);
                        out.write("hello!\n");
                        out.flush();
                    }
                }
            }
        }
    }
}

客户端

package com.test.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

public class Client01 {
    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket("127.0.0.1", 8000);
             BufferedReader in = new BufferedReader(
                 new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
             BufferedWriter out = new BufferedWriter(
                 new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
             Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8)) {

            System.out.println("TCP 连接成功,请输入内容:");
            while (scanner.hasNextLine()) {
                out.write(scanner.nextLine());
                out.write('\n');
                out.flush();
                System.out.println("服务端响应:" + in.readLine());
            }
        }
    }
}

原示例存在一个重要问题:服务端使用 read() 循环等待客户端关闭输出流,客户端却一直保持连接并继续发送,因此服务端可能一直阻塞,根本执行不到返回 hello! 的代码。修订后使用换行定义消息边界,并在每次请求后返回一行响应。

如果实际协议传输 JSON、文件或二进制内容,应使用长度前缀、固定头部、明确分隔符或其他 framing 方案;不能依赖一次 write 对应一次 read

TCP Socket 示例运行结果一

TCP Socket 示例运行结果二

2.5 UDP 协议 Socket 代码示例

UDP 示例只演示一次数据报发送和接收。发送端打印“发送成功”只表示本机 API 接受了数据,不代表服务端一定收到。实际应用应处理丢包、乱序、重复、超时和消息大小限制。

服务端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.charset.StandardCharsets;

public class Server1 {
    public static void main(String[] args) {
        byte[] buffer = new byte[1024];

        try (DatagramSocket socket = new DatagramSocket(8888)) {
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
            System.out.println("等待 UDP 数据报...");
            socket.receive(packet);

            String message = new String(
                packet.getData(), packet.getOffset(), packet.getLength(), StandardCharsets.UTF_8);
            System.out.println("收到 " + packet.getAddress() + ":" + packet.getPort() + ":" + message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;

public class Client1 {
    public static void main(String[] args) {
        byte[] data = "UDP 协议的 Socket 请求,可能丢失".getBytes(StandardCharsets.UTF_8);

        try (DatagramSocket socket = new DatagramSocket()) {
            InetAddress address = InetAddress.getByName("127.0.0.1");
            DatagramPacket packet = new DatagramPacket(data, data.length, address, 8888);
            socket.send(packet);
            System.out.println("UDP 数据报已交给本机协议栈");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

原文中的 ODP 是笔误,应为 UDPnew StringgetBytes 使用默认字符集也可能在不同机器上产生乱码,因此示例明确使用 UTF-8。

UDP Socket 示例运行结果

2.6 Socket 的常用 Java 类

类名协议/用途作用
SocketTCP客户端 TCP 连接,也可表示服务端 accept 得到的已连接 Socket;负责字节流读写和连接状态管理
ServerSocketTCP绑定并监听服务端口,accept 返回一个独立的 TCP 连接 Socket
DatagramSocketUDP发送和接收 UDP 数据报
DatagramPacketUDP表示待发送或已接收的数据报及其地址、端口和有效长度
InetAddressIP 地址表示 IPv4/IPv6 地址并提供 DNS 解析相关方法,不包含端口
InetSocketAddressIP/主机名 + 端口封装 Socket 连接或绑定所需的地址和端口,不依赖某一种应用协议

3 HTTP

什么是 HTTP 协议

HTTP(Hypertext Transfer Protocol,超文本传输协议)是应用层协议,定义了客户端和服务器之间传输请求、响应、表示和控制信息的语义。它可以承载 HTML、JSON、图片、音频、视频和任意二进制内容。

HTTP 的可靠性、顺序性和安全性不是 HTTP 语义单独提供的:HTTP/1.1 和 HTTP/2 常运行在 TCP 上,HTTP/3 运行在 QUIC 上,HTTPS 则在 HTTP 与传输之间使用 TLS 保护。HTTP 不是“可靠传输文字、图片、音频、视频的协议”这么简单。

HTTP 与协议栈关系

Socket 和 HTTP 的区别及应用场景

  • Socket 是操作系统提供的通信 API/抽象对象;HTTP 是运行在传输协议之上的应用层协议。二者不是同一层次的概念;
  • TCP Socket 可以承载 HTTP,也可以承载自定义二进制协议、数据库协议或 WebSocket 握手后的数据;
  • HTTP/1.1 支持持久连接和连接复用;HTTP/2 在一条 TCP 连接上多路复用多个 Stream;HTTP/3 在 QUIC 连接上多路复用,因此“HTTP 是短连接、Socket 是长连接”是过时的简化说法;
  • HTTP 适合 Web、API、文件传输、服务间调用;自定义 Socket 协议适合需要特殊消息格式、低延迟或长时间双向交互的场景,但应用必须自行处理 framing、安全、鉴权、心跳和重连;
  • 网络游戏、直播、在线协作等实时场景可以使用 UDP、QUIC、WebSocket、WebRTC 或 TCP,具体选择取决于可靠性、延迟、浏览器能力和部署环境。

什么是 HTTP 请求体

HTTP 请求消息可以包含请求目标、首部和可选内容。HTTP/1.1 文本形式通常是:

POST /users HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 15

{"name":"Tom"}

“请求体由请求行、请求头、请求数据组成”不准确:请求行和请求首部属于请求消息的起始行/首部部分,body 是可选内容,三者不能都称为请求体。

  • POST、PUT、PATCH 常使用请求体,但并非强制;
  • GET 请求在 HTTP 语义中没有通用的请求体语义,客户端通常不应发送 GET content,除非目标服务明确规定;不能笼统说所有 GET 都绝对没有请求体;
  • HTTP/2、HTTP/3 不使用文本请求行和空行,而是用伪首部字段、HEADERS 帧和 DATA 帧表达相同语义;
  • body 的格式由 Content-Type 等首部描述,长度和边界由具体 HTTP 版本的 framing 规则确定。

POST 请求报文示意

GET 请求报文示意

HTTP 响应报文有哪些

HTTP 响应是在请求之后返回的消息,但请求不必包含请求体。HTTP/1.1 响应通常包含:

  1. 状态行,例如 HTTP/1.1 200 OK
  2. 响应首部字段,例如 Content-TypeContent-LengthCache-Control
  3. 空行;
  4. 可选响应内容。

HTTP/2/3 则使用 :status 等伪首部和二进制帧,不再使用文本状态行和空行。204、304、HEAD 响应等场景不能按普通响应返回内容。

HTTP 响应报文示意

HTTP 和 HTTPS 的区别

HTTPS 可以理解为 HTTP over TLS:

  • HTTP 默认端口通常是 80,HTTPS 默认端口通常是 443,但端口可以由部署配置改变;
  • http:// 不使用 TLS 时,内容可能被窃听或篡改;
  • HTTPS 使用 TLS 提供机密性、完整性和服务器身份认证;
  • 证书不一定需要付费,Let’s Encrypt 等 CA 可以签发免费证书;
  • 现代证书的公钥不一定是 RSA,也可以是 ECDSA 等算法;
  • HTTP/1.1 和 HTTP/2 常通过 TCP + TLS 运行,HTTP/3 通过 QUIC 使用 TLS 1.3;
  • HTTPS 不能修复服务器漏洞、弱密码、错误授权或恶意业务逻辑,也不能自动证明网站内容可信。

HTTPS 工作原理

原文将现代 HTTPS 描述成“客户端生成随机数,再用服务器 RSA 公钥加密,服务端用私钥解密”,这是早期 TLS RSA 密钥传输的说法,不适用于现代主流 TLS 1.2 ECDHE 和 TLS 1.3。现代流程大致是:

  1. 客户端发送 ClientHello,包含支持的 TLS 版本、密码套件、随机数、SNI、ALPN 和 ECDHE key share 等扩展;
  2. 服务端选择参数并发送 ServerHello,随后发送自己的证书链和 CertificateVerify 签名;
  3. 客户端验证证书链是否锚定到信任 CA,检查有效期、用途以及证书 SAN 是否匹配目标主机名;
  4. 双方通过 ECDHE 等临时密钥交换计算共享秘密,不是把一个“随机数”直接用 RSA 加密传输;
  5. TLS 使用 HKDF 等密钥派生机制生成握手密钥和应用数据密钥;
  6. 服务端的证书私钥用于签名认证,客户端使用证书公钥验证签名。签名不是简单的“用私钥加密摘要”;
  7. 双方通过 Finished 校验握手 transcript 后,用协商出的对称 AEAD 密钥加密应用数据。

TLS 1.3 典型完整握手通常为 1-RTT,并删除了 RSA 静态密钥传输和许多旧式弱算法。RSA 仍可作为证书签名算法使用,但不再作为 TLS 1.3 的 RSA 密钥传输方式。MD5 和 SHA-1 不应作为现代 TLS 认证或签名方案使用。

一次完整的 HTTP 请求经历哪些步骤

原文的“建立 TCP、发送请求行/请求头、服务器回送状态行、发送数据、关闭 TCP”可以作为 HTTP/1.1 首次访问的历史简化流程,但现代 HTTP 还要考虑连接复用和 HTTP/2/3。较完整的过程是:

  1. 解析 URL、代理配置、缓存和安全策略;
  2. 查询 DNS,得到一个或多个 A/AAAA 地址;
  3. 选择或复用 TCP/TLS 连接,或者建立 QUIC 连接;
  4. 发送 HTTP 请求。HTTP/1.1 使用文本起始行和首部,HTTP/2/3 使用 HEADERS/DATA 等帧;
  5. 服务端、CDN、代理和网关处理请求,返回状态码、首部和内容;
  6. 浏览器依据缓存、CORS、Cookie、CSP 和 MIME 类型处理响应,并请求页面子资源;
  7. 连接可能留在连接池中继续复用,也可能因超时、服务器策略、错误或用户操作而关闭。

HTTP/1.1 的典型报文仍可以写成:

GET /sample/hello.html HTTP/1.1
Host: example.com
Accept: text/html

首部结束的空行是 HTTP/1.1 文本消息的 framing 形式;HTTP/2 和 HTTP/3 不发送这个文本空行。

常用 HTTP 状态码分类

HTTP 状态码表示请求的处理结果,但状态码不等于业务结果:

  • 1xx:信息响应,例如 100 Continue101 Switching Protocols103 Early Hints
  • 2xx:成功,例如 200 OK201 Created202 Accepted204 No Content206 Partial Content
  • 3xx:重定向或缓存,例如 301302303304307308
  • 4xx:当前服务器不能满足请求,例如 400401403404405409413429
  • 5xx:服务器、代理或网关处理失败,例如 500501502503504

原文表格中的几处说法需要更新:

  • 303 See Other 要求客户端使用 GET(HEAD 情况除外)访问另一个 URI;
  • 307 Temporary Redirect302 的历史行为不同,要求保留原请求方法和请求内容,并不是“强制使用 POST”;
  • 304 用于条件 GET/HEAD 的缓存验证结果,不是普通重定向;
  • 401 Unauthorized 通常需要认证并配合 WWW-Authenticate403 是已理解但拒绝服务;
  • 503 表示暂时不可用,可能配合 Retry-After,不能只解释为“服务器正忙”。

HTTP 协议有哪些请求方法

方法主要语义常见性质
GET获取目标资源的当前表示safe、幂等,通常可缓存
HEAD获取类似 GET 的首部,不返回内容safe、幂等
POST对目标资源进行资源特定处理默认非 safe、非幂等
PUT创建或替换目标资源的表示幂等,通常不 safe
PATCH对目标资源做部分修改是否幂等取决于具体设计
DELETE请求删除目标资源幂等语义,但不代表资源一定存在
OPTIONS查询目标资源或服务器支持的通信选项safe、幂等
CONNECT建立到目标的隧道,常用于代理由代理场景定义
TRACE回显请求用于诊断生产环境常禁用

PUT 不是“只能传输文件”,它的语义是用请求内容创建或替换目标资源的表示;PATCH 也不是简单地“把文档内容取代”,而是部分修改。方法是否允许请求体、是否缓存、是否幂等,都应参考具体方法规范和资源接口定义。

GET 方法与 POST 方法的区别

  • GET 的主要语义是获取资源;POST 的语义是让目标资源根据请求内容执行资源特定处理;
  • GET 通常把查询参数放在 URI 中,POST 通常把数据放在请求体中,但这不是安全边界,也不是绝对的语法限制;
  • URL 没有一个由 HTTP 统一规定的“最大长度”。浏览器、代理、服务器和网关各有实现限制,过长 URI 可能得到 414 URI Too Long;POST 也会受到请求体大小限制;
  • GET 是 safe 和幂等方法,POST 默认不是。幂等表示重复请求的预期服务器效果相同,不表示响应内容、日志或计费行为一定完全相同;
  • GET 响应通常更容易被缓存,POST 响应在显式满足缓存规则时也可能缓存;
  • GET 参数可能出现在历史记录、日志、书签、Referer 等位置,但 POST 放在请求体并不等于安全。两者都应使用 HTTPS,敏感数据不应通过 URL 传递;
  • GET 不应依赖请求体的通用语义,POST 可以有 body,但真正是否接受、如何解析由接口定义决定。

“GET 数据量小、POST 可以传大文件”是工程经验,不是 HTTP 方法本身的严格限制。实际限制来自 URI、首部、请求体、代理和服务器配置。

HTTP 版本的对比

HTTP/1.0

  • 早期 HTTP/1.0 默认每个请求完成后关闭连接,持久连接常通过非标准或扩展的 Connection: keep-alive 实现;
  • HTTP 语义本身是无状态的,服务器是否保存会话状态由 Cookie、Session、Token 等应用机制决定;
  • 每个请求建立连接会增加 TCP/TLS 握手和慢启动开销。

HTTP/1.1

  • 持久连接成为默认行为,只要双方没有明确关闭,就可以在同一 TCP 连接上发送多个请求/响应;
  • 支持 Host、分块传输、Range 请求、条件请求和更完善的缓存控制;
  • 管线化允许客户端先发送多个请求,但响应仍需按顺序返回,容易出现应用层队头阻塞,浏览器实际支持有限,后来通常使用多个连接缓解;
  • HTTP/1.1 的文本报文并不意味着一次 send() 就是一个请求,TCP 仍然是字节流,接收方必须按 HTTP framing 解析。

HTTP/2(RFC 9113)

  • 使用二进制帧;
  • 使用 HPACK 压缩首部;
  • 在同一 TCP 连接上通过多个 Stream 多路复用;
  • 支持 Stream 级和连接级流量控制;
  • 旧的 Stream 优先级机制已经被 RFC 9113 弃用,现代实现可以参考 RFC 9218 的 HTTP Priority;
  • Server Push 仍存在于协议规范历史中,但主流浏览器已经禁用或移除实际支持,现代项目通常使用缓存、preload 和 103 Early Hints 代替;
  • 多路复用解决了部分 HTTP 层队头阻塞,但所有 Stream 共享 TCP,丢包仍可能阻塞整条 HTTP/2 连接。

HTTP/3(RFC 9114)

  • 基于 QUIC,而不是 TCP;
  • 使用 QUIC 的独立 Stream,减少 TCP 层跨请求队头阻塞;
  • 使用 QPACK 压缩首部;
  • QUIC 集成 TLS 1.3,并支持连接迁移、快速建立和更灵活的流控制;
  • 使用 UDP 不代表天然不可靠,QUIC 在 UDP 之上提供可靠、有序的 Stream;
  • 单个 Stream 内仍然有顺序要求,丢包仍可能影响该 Stream。

什么是对称加密与非对称加密

  • 对称加密使用同一个密钥加密和解密,速度通常较快,适合大量应用数据。主要困难是通信双方如何安全地取得共享密钥;
  • 非对称密码使用公钥和私钥。公钥可以公开,私钥需要保护。它可以用于加密/解密,也可以用于数字签名/验证,但“签名”与“公钥加密”是两个不同用途;
  • 非对称算法通常比对称算法计算成本高,因此 TLS 常先通过证书和 ECDHE 完成身份认证及密钥协商,再使用对称 AEAD 算法保护应用数据;
  • 公钥本身不能自动证明属于某个域名。HTTPS 依靠 CA 签发的 X.509 证书、主机名校验、有效期和密钥用途建立信任链。

HTTP 的通用语义是无状态的,它不会自动记住上一次请求是谁。但 Web 应用需要登录态、购物车和用户偏好,因此可以使用 Cookie、Session、Token 等机制保存和传递状态。

Cookie 是用户代理保存的一小段键值数据,不应只理解为服务器保存在浏览器上的普通文件。服务器通过 Set-Cookie 设置属性,浏览器根据 Domain、Path、Secure、HttpOnly、SameSite 和过期时间决定是否保存及发送:

Set-Cookie: session_id=abc; Path=/; Secure; HttpOnly; SameSite=Lax

后续请求可能携带:

Cookie: session_id=abc
  • Cookie 的实际大小、数量和总首部限制因浏览器和服务器而异,常见实现约把单个 Cookie 限制在 4KB 左右,但不是所有环境都严格相同;
  • Secure 通常要求仅通过 HTTPS 发送;
  • HttpOnly 阻止 JavaScript 通过 document.cookie 读取,但不能阻止 XSS 利用当前登录态发起请求;
  • SameSite 用于控制跨站请求携带 Cookie,现代浏览器对未设置该属性的 Cookie 通常按 Lax 兼容策略处理;
  • Cookie 不是天然加密存储,敏感数据应放在安全设计的会话标识中,并结合服务端失效、轮换和权限检查。

什么是 Session

Session 不是 HTTP 标准中的一个状态码或传输协议,而是应用层会话管理方式。常见设计是:

  1. 服务端创建一份会话状态,生成随机不可预测的 Session ID;
  2. 通过 Cookie、URL、请求首部或请求体把 Session ID 交给客户端;
  3. 客户端后续请求带回 ID;
  4. 服务端根据 ID 从内存、数据库、Redis 或其他存储中读取会话状态。

Session 也可以采用客户端保存的签名/加密令牌,不一定总是“服务端分配一块储存空间”。Session ID 被盗用时同样会造成会话劫持,服务端仍应使用 HTTPS、HttpOnly、Secure、SameSite、轮换和注销机制。

  1. Cookie 数据由用户代理保存,Session 状态通常由服务端或服务端可验证的令牌保存;
  2. Cookie 会随匹配请求自动发送,可能增加带宽开销;Session ID 通常也通过 Cookie 传递,因此二者可以配合使用;
  3. Cookie 的大小、数量受浏览器和首部限制;Session 的存储容量取决于服务端设计,但服务端也要考虑内存、数据库和集群一致性;
  4. “Cookie 安全性差、Session 一定安全”不是绝对结论。Cookie 内容可能只是随机 ID,Session ID 泄露同样危险;真正的安全性取决于 HTTPS、随机性、访问控制、过期、CSRF 防护和服务端实现;
  5. 原文末尾重复出现的对称加密、Cookie 和 Session 段落属于重复内容,已合并保留一份。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS