编程 PHP 8.6 给 stream 加上 TLS 会话复用与 0-RTT:高频调用第三方 HTTPS 时能省掉一次完整握手

2026-10-27 21:06:47

PHP 8.6 stream 的 TLS 会话复用与 0-RTT:三组 SSL 上下文选项的用法

来源:PHP: New Features - Manual,migration86.new-features.php 的 OpenSSL 章节。

PHP 8.6 给 stream 补了三块 TLS 能力:会话复用、外部 PSK、TLS 1.3 早数据(0-RTT)。它们都通过 stream_context_create() 的 ssl 上下文选项配置,可以按用途分成三组。

三组上下文选项

会话复用

会话复用(session resumption)允许客户端跨请求保存和恢复会话,也允许服务端自己实现会话存储、控制会话缓存行为。新增选项:

  • session_data
  • session_new_cb
  • session_cache
  • session_cache_size
  • session_timeout
  • session_id_context
  • session_get_cb
  • session_remove_cb
  • num_tickets

客户端主要用 session_data 注入上次拿到的会话;session_new_cb 用来接收新建的会话。服务端方向是 session_get_cb、session_new_cb、session_remove_cb 三个回调,加上 session_cache、session_cache_size、session_timeout、session_id_context、num_tickets 控制缓存与票据行为。

外部 PSK

外部 PSK 用两个选项:

  • psk_client_cb
  • psk_server_cb

分别用于设置和接收 pre-shared key。适合已经有带外密钥分发渠道的服务之间连接,不依赖证书链。

早数据(0-RTT)

客户端用 early_data 选项发送早数据;服务端用 max_early_data 决定接受量,用 early_data_cb 接收数据。结果不通过返回值,而是写在 stream_get_meta_data() 返回数组的 crypto 条目下,键为 early_data,取值是 accepted、rejected、not_sent 三者之一。

客户端:反复调第三方 HTTPS

支付网关、内部服务这类需要高频调用的接口,每次完整握手开销不小。把会话存下来,下次连接时回填:

$session = $cache->get('tls:pay.example.com'); // 上次保存的会话

$ctx = stream_context_create([
    'ssl' => [
        'session_data' => $session,
        'session_new_cb' => 'tls_save_session', // 回调签名以手册为准
        'early_data' => true,                   // 开启 0-RTT 发送
    ],
]);

$fp = stream_socket_client(
    'tls://pay.example.com:443',
    $errno, $errstr, 5,
    STREAM_CLIENT_CONNECT, $ctx
);

$meta = stream_get_meta_data($fp);
$early = $meta['crypto']['early_data'] ?? null; // accepted / rejected / not_sent

early_data 的结果要看,不能假定早数据一定被服务端接受:rejected 时数据会走正常握手路径重发,not_sent 说明压根没发出去。

服务端:存储要自己实现

服务端拿不到现成的会话池,session_get_cb、session_new_cb、session_remove_cb 都要自己接上 Redis、APCu 之类的存储:

$ctx = stream_context_create([
    'ssl' => [
        'session_get_cb'    => 'tls_load_session',
        'session_new_cb'    => 'tls_store_session',
        'session_remove_cb' => 'tls_drop_session',
        'session_timeout'   => 300,
        'max_early_data'    => 4096,
        'early_data_cb'     => 'tls_handle_early_data',
    ],
]);

$server = stream_socket_server(
    'tls://0.0.0.0:443',
    $errno, $errstr,
    STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
    $ctx
);

sid_context 相关选项 session_id_context 用来把会话绑定到具体服务身份,多站点共用一套缓存时要设,避免会话被跨站取到。

两个必须注意的风险

0-RTT 数据可以被重放。 早数据在握手完成前就被服务端处理,攻击者可以原样重发。因此 early_data 这条路径上只能放幂等操作,查询、读接口可以;支付下单、扣款、写状态一律不能走早数据,要等握手完成后的正常请求。

PSK 与会话缓存都有生命周期。 session_timeout、session_cache_size、num_tickets 决定缓存里的条目能活多久、能存多少;过期条目要能被 session_remove_cb 清掉,否则缓存只增不减。并发场景下,session_new_cb 的写入要考虑多个 worker 同时写同一份存储的竞争,用带原子性的写入或按会话 id 分键。

同版本其它 stream 相关改动

  • curl_getinfo 增加 size_delivered,需 libcurl 8.20.0+。
  • CURLOPT_SEEKFUNCTION 支持请求体重放。
  • filter.max_filter_count 限制 php://filter 链长度。
  • 新的 Stream 错误 API:StreamError / StreamException、stream_last_errors,以及 error_mode、error_store、error_handler 选项。
复制全文 生成海报 PHP TLS 接口对接

推荐文章

程序员茄子在线接单