You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用SSL连接时PHP fwrite()函数报错求助

解决SMTP SSL连接中fwrite无效流资源的问题

看起来你的问题根源在于几个关键点:要么是$conn被意外覆盖成了非资源值,要么是没有读取SMTP服务器的响应导致连接被服务器主动关闭,还有一些SMTP协议的规范没有遵守,这些都可能导致流资源失效。

可能的错误原因分析

  1. 构造函数的错误返回值:PHP4的构造函数(以类名命名的函数)不能返回false,这个返回值会被忽略,即使fsockopen失败,对象依然会被创建,后续调用方法时就会用无效的$conn执行fwrite。
  2. 未读取SMTP服务器响应:SMTP是请求-响应协议,每发送一条命令后必须读取服务器的响应(比如发送HELO后要确认返回250开头的成功信息)。如果连续发送命令不读取响应,服务器可能会因为缓冲区溢出或协议违规关闭连接,导致你的流资源变成无效状态。
  3. Header中的HTML转义错误:你在toHeader里用了&lt;和&gt;,这是HTML的转义字符,但SMTP邮件头需要直接用<和>,转义会导致头格式错误,进而引发服务器异常。
  4. 意外覆盖$conn变量:虽然你贴的代码里没有,但如果某次调用Put时不小心把返回值(fwrite返回的字节数,比如12)赋值给了$this->conn,那后续的fwrite就会把数字当成流资源,触发你看到的错误。

修复后的完整代码

我把你的代码做了针对性修复,添加了响应读取、协议合规处理,修正了语法和格式错误:

class smtp{
    var $conn;
    var $user;
    var $pass;
    var $debug;

    // 改用PHP5+的构造函数(兼容PHP4的话可以保留原构造函数,但推荐这个)
    function __construct($host, $port){
        $this->conn = fsockopen($host, $port, $errno, $errstr, 10);
        if (!$this->conn) {
            die("连接失败: $errstr ($errno)");
        }
        // 读取服务器初始响应
        $this->GetResponse();
        // 发送HELO并确认响应
        $this->Put("HELO " . $_SERVER['HTTP_HOST']);
        $response = $this->GetResponse();
        if (substr($response, 0, 3) != '250') {
            die("HELO命令失败: $response");
        }
    }

    // 兼容PHP4的构造函数(可选)
    function smtp($host, $port){
        $this->__construct($host, $port);
    }

    function Auth(){
        $this->Put("AUTH LOGIN");
        $response = $this->GetResponse();
        if (substr($response, 0, 3) != '334') {
            die("AUTH LOGIN命令失败: $response");
        }
        $this->Put(base64_encode($this->user));
        $response = $this->GetResponse();
        if (substr($response, 0, 3) != '334') {
            die("用户名认证失败: $response");
        }
        $this->Put(base64_encode($this->pass));
        $response = $this->GetResponse();
        if (substr($response, 0, 3) != '235') {
            die("密码认证失败: $response");
        }
    }

    function Send($to, $from, $subject, $msg, $nomeexibicao){
        $this->Auth();
        
        $this->Put("MAIL FROM: <" . $from . ">");
        $response = $this->GetResponse();
        if (substr($response, 0, 3) != '250') {
            die("MAIL FROM命令失败: $response");
        }

        $this->Put("RCPT TO: <" . $to . ">");
        $response = $this->GetResponse();
        if (substr($response, 0, 3) != '250' && substr($response, 0, 3) != '251') {
            die("RCPT TO命令失败: $response");
        }

        $this->Put("DATA");
        $response = $this->GetResponse();
        if (substr($response, 0, 3) != '354') {
            die("DATA命令失败: $response");
        }

        $this->Put($this->toHeader($to, $from, $subject, $nomeexibicao));
        $this->Put("\r\n");
        $this->Put($msg);
        $this->Put("\r\n.");
        $response = $this->GetResponse();
        if (substr($response, 0, 3) != '250') {
            die("邮件发送失败: $response");
        }

        $this->Close();
        return true;
    }

    function Put($value){
        if ($this->conn) {
            return fwrite($this->conn, $value . "\r\n");
        }
        return false;
    }

    // 读取服务器响应
    function GetResponse(){
        $response = '';
        while (!feof($this->conn)) {
            $line = fgets($this->conn, 512);
            $response .= $line;
            // SMTP响应如果以数字+空格结尾,就是完整响应
            if (substr($line, 3, 1) == ' ') {
                break;
            }
        }
        if ($this->debug) {
            echo htmlspecialchars($response) . "<br>";
        }
        return $response;
    }

    function toHeader($to, $from, $subject, $nomeexibicao){
        // 修正Message-Id格式,去掉多余的转义
        $messageId = date('YmdHis') . "." . md5(microtime()) . "." . strtoupper($from);
        $header = "Message-Id: <$messageId>\r\n";
        // 修正From和To的格式,用真实的<>而非转义字符
        $header .= "From: \"$nomeexibicao\" <$from>\r\n";
        $header .= "To: <$to>\r\n";
        // 主题如果有非ASCII字符,需要编码(可选,这里保留原逻辑)
        $header .= "Subject: $subject\r\n";
        $header .= "Date: " . date('D, d M Y H:i:s O') . "\r\n";
        $header .= "X-MSMail-Priority: Normal\r\n";
        $header .= "MIME-Version: 1.0\r\n";
        $header .= "Content-Type: text/html; charset=utf-8\r\n";
        $header .= "X-MimeOLE: By Huawei\r\n";
        $header .= "Return-Path: $from\r\n";
        return $header;
    }

    function Close(){
        $this->Put("QUIT");
        $response = $this->GetResponse();
        if (substr($response, 0, 3) != '221') {
            die("QUIT命令失败: $response");
        }
        fclose($this->conn);
        $this->conn = null;
    }
}

// 使用示例
$host = "ssl://p3plcpnl0808.prod.phx3.secureserver.net";
$port = 465;
$smtp = new smtp($host,$port);
$smtp->debug = true; // 开启调试可以看到所有SMTP交互信息
$smtp->user = "myemail@server.com";
$smtp->pass = "my_password";
$destinatario = "to_email@server.com";
$remetente = "from_email@server.com";
$assunto = "email title";
$mensagem = "email message";
$nome_exibicao = "My name";

try {
    $result = $smtp->Send($destinatario, $remetente, $assunto, $mensagem, $nome_exibicao);
    echo "邮件发送成功!";
} catch (Exception $e) {
    echo "发送失败: " . $e->getMessage();
}

关键修复点说明

  1. 添加了响应读取方法GetResponse():每次发送SMTP命令后都读取并验证服务器响应,确保协议交互合规,避免连接被服务器异常关闭。
  2. 修正了构造函数:移除无效的return false,改用die抛出连接失败信息,同时读取服务器初始响应。
  3. 修复了邮件头格式:去掉了HTML转义字符,改用SMTP标准的<>格式,修正了Message-Id的生成逻辑。
  4. 完善了认证流程:严格按照SMTP AUTH LOGIN的步骤,验证每一步的服务器响应,确保认证成功再继续。
  5. 调试模式:开启debug后可以看到所有SMTP交互信息,方便排查问题。

内容的提问来源于stack exchange,提问作者mlsaraiva

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:39:54