全面排查与解决:Aliyun SMS API (dysmsapi.aliyuncs.com) 在 PHP 环境中不可达问题

在使用阿里云短信服务(Aliyun SMS API)进行短信发送时,时常会遇到“短信发送不出去”、“DNS 解析失败”或“连接超时”等错误。本文将带你从环境介绍、现象分析,到快速排查、解决思路,再到综合检测脚本的实现,帮助你一步步定位并彻底解决 PHP 环境中对 dysmsapi.aliyuncs.com 访问失败的问题。全文基于实际项目场景,内容通俗易懂,适合专科及以上读者,保持技术细节的完整和有效。


一、环境与场景概述

在多数企业级环境中,为保障内部网络安全,生产服务器通常部署在私有网络或企业专网中,并受到防火墙、虚拟机或安全设备(如深信服)等多重保护。本文涉及的环境要素如下:

  • 虚拟化与安全设备
    服务器运行于深信服(Sangfor)提供的虚拟机环境,并受防火墙策略管控,可能对外网访问进行限制或代理转发。

  • 开发语言与框架
    后端使用 PHP,短信发送通过社区维护的 overtrue/easy-sms 第三方库进行封装。

  • 核心访问目标
    域名:dysmsapi.aliyuncs.com
    服务端口:TCP 80、TCP 443

在该环境下,项目在调用 EasySms 时,抛出如下错误,导致短信无法发送:


All the gateways have failed. You can get error details by \$exception->getExceptions()

进一步查看日志可见错误信息:


Error 28: Resolving timed out after 5000 milliseconds
port 80 connection failed (0: php\_network\_getaddresses: getaddrinfo failed: Name or service not known)
port 443 connection failed (0: php\_network\_getaddresses: getaddrinfo failed: Name or service not known)

上述现象表明,无论是 HTTP 请求层面还是 TCP 端口层面,均无法与 dysmsapi.aliyuncs.com 建立正常连接。


二、现象复盘:为什么会发送失败?

在未拿到运行权限的虚拟机内,通过 EasySms 调用接口的逻辑通常是:

  1. 底层先解析域名 -> 获取 IP 地址
  2. 发起 HTTPS 请求(默认 cURL)
  3. 如果 HTTP 请求失败,再尝试其他网关或走备用端口
  4. 捕获异常并汇总到 $exception->getExceptions()

当 DNS 无法解析或网络被隔断时,第 1 步直接挂掉,cURL 报 “Error 28: Resolving timed out”,并提示 getaddrinfo failed。TCP 端口检测同样会在 80/443 均无法连接时,返回连接失败信息。此时 EasySms 会认为所有通道都已失效,从而无法完成短信发送。


三、快速排查思路

为便于开发或运维快速定位问题,我们可以编写一个简易的检测页面,分步骤输出各环节结果,直观判断是 DNS 问题、HTTP 层面问题,还是底层 TCP 端口被阻断。

1. 部署检测脚本

将以下内容保存为 check_sms.php,放置在你的 PHP Web 根目录下(如 /var/www/html/check_sms.php),通过浏览器访问即可。

<?php
// check_sms.php

$host  = 'dysmsapi.aliyuncs.com';
$ports = [80, 443];

// 使用 cURL 发起 HEAD 请求
function checkWithCurl($url, $timeout = 5) {
    if (!function_exists('curl_init')) {
        return ['ok' => false, 'msg' => 'cURL 未启用'];
    }
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_NOBODY         => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => $timeout,
        CURLOPT_CONNECTTIMEOUT => $timeout,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
    ]);
    curl_exec($ch);
    $errno  = curl_errno($ch);
    $errmsg = curl_error($ch);
    $code   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($errno !== 0) {
        return ['ok' => false, 'msg' => "错误 {$errno}: {$errmsg}"];
    }
    return ['ok' => ($code >= 200 && $code < 400), 'msg' => "HTTP {$code}"];
}

// 利用 fsockopen 测试 TCP 端口连通性
function checkWithSocket($host, $port, $timeout = 5) {
    $fp = @fsockopen($host, $port, $errno, $errstr, $timeout);
    if (!$fp) {
        return ['ok' => false, 'msg' => "端口 {$port} 连接失败 ({$errno}: {$errstr})"];
    }
    fclose($fp);
    return ['ok' => true, 'msg' => "端口 {$port} 可连通"];
}

$results = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 1. HTTPS cURL 检测
    $curlRes = checkWithCurl("https://{$host}/");
    $results[] = ['method' => 'cURL (HTTPS)', 'ok' => $curlRes['ok'], 'msg' => $curlRes['msg']];

    // 2. TCP 端口检测
    foreach ($ports as $p) {
        $sockRes    = checkWithSocket($host, $p);
        $results[] = ['method' => "TCP {$p}", 'ok' => $sockRes['ok'], 'msg' => $sockRes['msg']];
    }
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>检测 dysmsapi.aliyuncs.com 可访问性</title>
    <style>
        body  { font-family: sans-serif; padding: 20px; }
        table { border-collapse: collapse; width: 100%; max-width: 600px; margin-top: 10px; }
        th, td { padding: 8px; border: 1px solid #ccc; text-align: left; }
        th     { background: #f4f4f4; }
        .ok    { color: green; font-weight: bold; }
        .fail  { color: red;   font-weight: bold; }
        button { padding: 8px 16px; font-size: 14px; }
    </style>
</head>
<body>
    <h1>检测 dysmsapi.aliyuncs.com 可访问性</h1>
    <form method="post">
        <button type="submit">开始检测</button>
    </form>

    <?php if (!empty($results)): ?>
    <table>
        <thead>
            <tr><th>检测方式</th><th>结果</th><th>详细信息</th></tr>
        </thead>
        <tbody>
        <?php foreach ($results as $r): ?>
            <tr>
                <td><?= htmlspecialchars($r['method']) ?></td>
                <td class="<?= $r['ok'] ? 'ok' : 'fail' ?>">
                    <?= $r['ok'] ? '可达 ✅' : '不可达 ❌' ?>
                </td>
                <td><?= htmlspecialchars($r['msg']) ?></td>
            </tr>
        <?php endforeach; ?>
        </tbody>
    </table>
    <?php endif; ?>
</body>
</html>

使用方式

  1. 将上述脚本保存并部署到 Web 根目录;
  2. 通过浏览器访问 http://<你的域名>/check_sms.php
  3. 点击 开始检测,即可看到每一步的执行结果。

通过可视化的表格方式,我们能够迅速判断出:

  • DNS 解析是否成功(若 cURL 直接报 “解析超时”,则可能是 DNS 问题)
  • HTTPS 层面是否能够正常建立连接(HTTP 状态码是否在 200–399 范围内)
  • TCP 底层端口(80/443)是否被防火墙或网络策略阻断

四、深入分析:常见根因及应对策略

基于上述检测结果,常见导致访问失败的根因主要有以下几种:

  1. DNS 配置问题

    • /etc/resolv.conf 中未配置或配置了不可用的 DNS 服务器(比如内部 DNS 被防火墙拦截)。
    • PHP-FPM 运行用户无权访问系统 DNS 服务。
    • 应对:手动修改 /etc/resolv.conf,添加公共 DNS(如 Google DNS 8.8.8.88.8.4.4 或阿里云 DNS 223.5.5.5),并重启网络服务。
  2. 域名拼写或本地 hosts 干扰

    • 代码中域名写错会导致解析失败。
    • /etc/hosts 中有错误映射,指向了不可达 IP。
    • 应对:核对域名拼写;注释或移除冲突的 hosts 记录。
  3. 网络出口受限

    • 服务器只能通过指定的代理或专网访问外网,直接发包被阻断。
    • 应对:配置 PHP 使用企业 HTTP/SOCKS 代理,或调整安全组/防火墙策略,开放出站 TCP 80/443 端口。
  4. UDP 53 被拦截

    • DNS 查询依赖 UDP 53,若被防火墙拦截,会导致 DNS 超时。
    • 应对:开放出站 UDP 53 端口,或使用 TCP 方式解析(部分系统支持 DNS over TCP)。
  5. 过短的超时时间

    • 在高延迟网络环境下,5 秒可能不足以完成 DNS 解析或 TCP 握手。
    • 应对:根据网络质量适当延长 cURL 的 CURLOPT_CONNECTTIMEOUTCURLOPT_TIMEOUT 值(例如 15–30 秒)。

五、快速验证 DNS 可用性

在命令行或 PHP 脚本中单独测试 DNS 解析,能更精准地判断是否为 DNS 层面故障。

1. 命令行方式

# 使用 host 命令
host dysmsapi.aliyuncs.com

# 使用 dig 查询
dig +short dysmsapi.aliyuncs.com

# 使用 ping 验证
ping -c 2 dysmsapi.aliyuncs.com

若上述命令都无法返回 IP,则说明系统 DNS 无法解析该域名,需要检查 /etc/resolv.conf 并确保 nameserver 可达。

2. PHP 解析脚本

将下面脚本保存为 test_dns.php,通过浏览器或 CLI 运行,即可直观查看解析结果:

<?php
$host = 'dysmsapi.aliyuncs.com';
$ip   = gethostbyname($host);

if ($ip === $host) {
    echo "DNS 解析失败:{$host}\n";
} else {
    echo "解析到 IP:{$ip}\n";
}

若输出 “DNS 解析失败”,可初步确定为 DNS 问题。


六、综合版一键检测脚本

在排查过程中,往往需要同时验证 DNS、HTTP 与 TCP 端口。下面提供一个更完善的脚本 check_dysms.php,一次性输出三大检测模块结果。

<?php
// check_dysms.php

$host  = 'dysmsapi.aliyuncs.com';
$ports = [80, 443];

// 1. DNS 解析检测
function testDns($host) {
    $ip = gethostbyname($host);
    if ($ip === $host) {
        return ['ok' => false, 'msg' => "解析失败"];
    }
    return ['ok' => true,  'msg' => "解析到 {$ip}"];
}

// 2. cURL HTTP 检测
function testCurl($url, $timeout = 15) {
    if (!function_exists('curl_init')) {
        return ['ok' => false, 'msg' => 'cURL 未安装'];
    }
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_NOBODY         => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => $timeout,
        CURLOPT_TIMEOUT        => $timeout,
        CURLOPT_SSL_VERIFYPEER => false,
    ]);
    curl_exec($ch);
    $errno = curl_errno($ch);
    $code  = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($errno !== 0) {
        return ['ok' => false, 'msg' => "错误 {$errno}"];
    }
    return ['ok' => ($code >= 200 && $code < 400), 'msg' => "HTTP {$code}"];
}

// 3. TCP 端口检测
function testSocket($host, $port, $timeout = 5) {
    $fp = @fsockopen($host, $port, $errno, $errstr, $timeout);
    if (!$fp) {
        return ['ok' => false, 'msg' => "端口 {$port} 失败 ({$errstr})"];
    }
    fclose($fp);
    return ['ok' => true,  'msg' => "端口 {$port} 可连"];
}

// 执行检测
$dns  = testDns($host);
$curl = $dns['ok'] ? testCurl("https://{$host}/") : ['ok' => false, 'msg' => '跳过 HTTP 检测'];
$sock = [];
foreach ($ports as $p) {
    $sock[] = testSocket($host, $p);
}

// 渲染结果页面
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Aliyun SMS API 可用性检测</title>
    <style>
        body { font-family: sans-serif; padding: 20px; }
        h2   { margin-top: 20px; }
        p    { font-size: 16px; }
        ul   { list-style: none; padding-left: 0; }
        li   { margin: 5px 0; }
        .ok  { color: green; }
        .fail{ color: red; }
    </style>
</head>
<body>
    <h1>Aliyun SMS API 可用性检测</h1>

    <h2>1. DNS 解析</h2>
    <p class="<?= $dns['ok'] ? 'ok' : 'fail' ?>">
        <?= $dns['ok'] ? '✅' : '❌' ?> <?= htmlspecialchars($dns['msg']) ?>
    </p>

    <h2>2. HTTPS 请求</h2>
    <p class="<?= $curl['ok'] ? 'ok' : 'fail' ?>">
        <?= $curl['ok'] ? '✅' : '❌' ?> <?= htmlspecialchars($curl['msg']) ?>
    </p>

    <h2>3. TCP 端口检测</h2>
    <ul>
    <?php foreach ($sock as $s): ?>
        <li class="<?= $s['ok'] ? 'ok' : 'fail' ?>">
            <?= $s['ok'] ? '✅' : '❌' ?> <?= htmlspecialchars($s['msg']) ?>
        </li>
    <?php endforeach; ?>
    </ul>
</body>
</html>

将此脚本部署后,你可以一键查看三大核心环节的状态,迅速定位是 DNS 解析问题、HTTPS 请求问题,还是网络/防火墙层面阻断。


七、排障后续:与服务商沟通与验证

在自行检查并调整 DNS、hosts、网络策略后,仍可与网络或安全设备服务商(如深信服)进行协作:

  1. 提供检测脚本:将上文脚本发给服务商,让他们在终端执行并反馈结果。
  2. 确认防护策略:要求开放出站 UDP 53(DNS)、TCP 80/443;或配置代理策略。
  3. 验证修改:服务商确认调整后,再次运行检测脚本,确保 DNS 解析成功并能正常建立 HTTP/TCP 连接。

至此,若检测页面全部显示“✅”,即可断定环境网络已恢复正常,EasySms 调用也会恢复短信发送功能。


八、总结与最佳实践

  • 系统化排查:通过脚本化、一键化的检测页面,确保快速定位 DNS、HTTP、TCP 三大层面的问题。
  • 合理超时:根据网络环境,调整 DNS 查询与 cURL 请求的超时时间,避免短超时导致的误判。
  • 运维协同:与网络或安全设备服务商保持良好沟通,提供检测脚本并共享日志,便于他们准确下放策略或修改配置。
  • 可复用脚本:将检测脚本纳入常用诊断工具库,在遇到其他外部服务访问异常时,可快速复用并定位网络问题。

通过本文提供的方法和脚本,你可以在最短时间内完成对 dysmsapi.aliyuncs.com 在 PHP 环境下的可访问性测试,准确分辨并解决 DNS 配置、防火墙策略、域名映射等多种常见故障,保障阿里云短信服务的稳定可用。希望这份实用指南能助你高效排障,恢复系统短信投递能力。