PAYATHON 2026

发送服务器请求时,fsockopen 和 curl 哪个更好?

支付老李

结论

在 Automated Recurring Billing 应用中发送 HTTP/HTTPS 请求,通常应优先选择 cURL。

cURL 已经处理了 HTTPS、证书校验、超时、重定向、代理、请求头和状态码等常见问题,代码也更容易维护。fsockopen() 更接近底层 TCP Socket。用它发送 HTTP 请求时,需要自行拼装请求、解析响应并处理各种网络细节。除非涉及特殊协议或需要直接控制底层连接,否则没有必要使用它。

如果支付服务商提供官方 PHP SDK,优先使用官方 SDK。SDK 通常以 cURL 等 HTTP 客户端为基础,并进一步封装签名、认证、重试和错误处理。

为什么更推荐 cURL

这两个工具解决的问题不在同一层级:

  • fsockopen() 用于建立 Socket 连接。通过它发送 HTTP 请求,需要手动处理请求行、Headers、Body、TLS、分块响应和连接关闭等细节。
  • cURL 是成熟的网络传输客户端,直接支持 HTTP/HTTPS,并提供完整的请求配置和诊断功能。

对于周期扣费,cURL 有这些优势:

  • 原生支持 HTTPS,可以校验证书和主机名。
  • 可以设置连接超时和总超时,防止请求一直阻塞。
  • 能直接读取 HTTP 状态码。
  • 支持 JSON、表单、Basic Auth、Bearer Token、客户端证书等常见认证方式。
  • 可以区分网络错误与业务接口错误。
  • 便于接入日志、重试和监控。

fsockopen() 依赖较少,提供的底层控制也更多,还能连接非 HTTP 的 TCP 服务。不过,普通支付 API 通常用不到这些能力,自己处理底层细节反而更容易出错。

使用 cURL 发送请求

下面以向支付服务接口提交 JSON 请求为例。URL、字段和认证方式需要替换为实际服务商提供的内容。

<?php

$url = 'https://api.example.com/v1/subscriptions';

$payload = [
    'customer_id' => 'customer_123',
    'plan_id' => 'monthly_plan',
    'request_id' => bin2hex(random_bytes(16)),
];

$ch = curl_init($url);

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAYMENT_API_TOKEN'),
        'Content-Type: application/json',
        'Accept: application/json',
        'Idempotency-Key: ' . $payload['request_id'],
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_SSL_VERIFYHOST => 2,
]);

$responseBody = curl_exec($ch);

if ($responseBody === false) {
    $errorCode = curl_errno($ch);
    $errorMessage = curl_error($ch);
    curl_close($ch);

    throw new RuntimeException(
        "Network request failed ({$errorCode}): {$errorMessage}"
    );
}

$statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

try {
    $response = json_decode($responseBody, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    throw new RuntimeException(
        "Server returned invalid JSON, HTTP status: {$statusCode}",
        0,
        $e
    );
}

if ($statusCode < 200 || $statusCode >= 300) {
    $message = $response['message'] ?? 'Unknown API error';

    throw new RuntimeException(
        "API request failed, HTTP status: {$statusCode}, message: {$message}"
    );
}

// 继续处理成功响应

这段代码依次检查:

  1. curl_exec() 是否遇到连接、DNS、TLS 或超时错误。
  2. HTTP 状态码是否在 2xx 范围内。
  3. 响应内容是否为有效 JSON。
  4. 接口返回的业务结果是否符合预期。

不能只检查 curl_exec() 是否返回 false。服务器返回 400、401 或 500 时,cURL 仍可能已经正常完成网络请求。

fsockopen 为什么更麻烦

使用 fsockopen() 调用 HTTPS 接口时,大致需要手动构造以下请求:

<?php

$host = 'api.example.com';
$path = '/v1/subscriptions';

$body = json_encode([
    'customer_id' => 'customer_123',
    'plan_id' => 'monthly_plan',
], JSON_THROW_ON_ERROR);

$socket = fsockopen(
    'ssl://' . $host,
    443,
    $errorCode,
    $errorMessage,
    10
);

if ($socket === false) {
    throw new RuntimeException(
        "Connection failed ({$errorCode}): {$errorMessage}"
    );
}

$request =
    "POST {$path} HTTP/1.1\r\n" .
    "Host: {$host}\r\n" .
    "Content-Type: application/json\r\n" .
    "Accept: application/json\r\n" .
    "Content-Length: " . strlen($body) . "\r\n" .
    "Connection: close\r\n" .
    "\r\n" .
    $body;

fwrite($socket, $request);

$response = '';
while (!feof($socket)) {
    $response .= fgets($socket, 8192);
}

fclose($socket);

这段代码还不足以用于生产环境。实际使用时,还要妥善处理:

  • TLS 证书和主机名校验;
  • HTTP 状态行及响应头解析;
  • Transfer-Encoding: chunked;
  • 压缩响应;
  • 重定向;
  • 读取与写入超时;
  • 部分写入;
  • 持久连接和不同的 HTTP 版本。

调用普通 HTTP API 时,不建议自行实现这些逻辑。

周期扣费请求的注意事项

支付请求不能只看是否发送成功,还要避免重复扣款和状态不一致。

  • 使用支付平台支持的幂等键,例如 Idempotency-Key。重试同一个业务操作时,应复用原来的键。
  • 只有明确判断请求适合重试时才进行重试。连接超时不代表服务端一定没有处理请求,应通过幂等机制或查询接口确认状态。
  • 设置合理的连接超时和总超时。
  • 不要关闭 CURLOPT_SSL_VERIFYPEER,也不要将 CURLOPT_SSL_VERIFYHOST 设置为 0。
  • 将 API 密钥存放在环境变量或密钥管理服务中,不要写进源码或日志。
  • 不要记录银行卡号、CVV、完整 Token 等敏感数据。
  • 使用支付服务商提供的订阅和 Token 化能力,避免自行保存银行卡信息。
  • Webhook 必须验证签名,并能处理重复投递。数据库更新也要具备幂等性。
  • HTTP 请求成功不代表扣款成功。最终结果应以接口返回的业务状态和经过验证的 Webhook 为准。

普通 PHP 项目应选择 cURL、基于 cURL 的成熟 HTTP 客户端或官方 SDK。只有目标服务不是 HTTP,或者确实需要直接控制底层 Socket 时,才考虑 fsockopen()。

备注:内容仅供参考。