使用SSL连接时PHP fwrite()函数报错求助
解决SMTP SSL连接中fwrite无效流资源的问题
看起来你的问题根源在于几个关键点:要么是$conn被意外覆盖成了非资源值,要么是没有读取SMTP服务器的响应导致连接被服务器主动关闭,还有一些SMTP协议的规范没有遵守,这些都可能导致流资源失效。
可能的错误原因分析
- 构造函数的错误返回值:PHP4的构造函数(以类名命名的函数)不能返回
false,这个返回值会被忽略,即使fsockopen失败,对象依然会被创建,后续调用方法时就会用无效的$conn执行fwrite。 - 未读取SMTP服务器响应:SMTP是请求-响应协议,每发送一条命令后必须读取服务器的响应(比如发送
HELO后要确认返回250开头的成功信息)。如果连续发送命令不读取响应,服务器可能会因为缓冲区溢出或协议违规关闭连接,导致你的流资源变成无效状态。 - Header中的HTML转义错误:你在
toHeader里用了<和>,这是HTML的转义字符,但SMTP邮件头需要直接用<和>,转义会导致头格式错误,进而引发服务器异常。 - 意外覆盖
$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(); }
关键修复点说明
- 添加了响应读取方法
GetResponse():每次发送SMTP命令后都读取并验证服务器响应,确保协议交互合规,避免连接被服务器异常关闭。 - 修正了构造函数:移除无效的
return false,改用die抛出连接失败信息,同时读取服务器初始响应。 - 修复了邮件头格式:去掉了HTML转义字符,改用SMTP标准的
<>格式,修正了Message-Id的生成逻辑。 - 完善了认证流程:严格按照SMTP AUTH LOGIN的步骤,验证每一步的服务器响应,确保认证成功再继续。
- 调试模式:开启
debug后可以看到所有SMTP交互信息,方便排查问题。
内容的提问来源于stack exchange,提问作者mlsaraiva
相关产品推荐
相关产品推荐

