PHP原生Socket实现SMTP邮件发送完整示例

Elena
Elena
发布时间:2026-10-14| 6人浏览过
原创

在PHP开发中,有时需要脱离庞大的邮件库(如PHPMailer)直接底层控制邮件发送。本文提供基于PHP Socket扩展的SMTP客户端实现方案,通过手动构建TCP命令序列,兼容纯文本与HTML格式的邮件收发。

SMTP协议通信原理

使用Socket直接发送邮件的核心在于遵循SMTP(简单邮件传输协议)的标准交互流程。服务器监听指定端口(通常为25或加密协议的465/587),客户端建立连接后需按顺序执行握手、身份验证、信封定义和数据传输等指令。

主要交互步骤包括:

  • HELO/EHLO:客户端向服务器标识自身主机名。
  • AUTH LOGIN:进入登录模式,随后依次发送Base64编码后的用户名和密码以完成认证。
  • MAIL FROM / RCPT TO:分别定义发件人和收件人地址。
  • DATA:开始传输邮件头部(MIME版本、类型、主题)及正文内容。
  • QUIT:结束会话并关闭连接。

环境准备

本方案依赖PHP内置的Sockets扩展。请在执行前确认您的服务器已启用该扩展:

  1. 打开 php.ini 配置文件。
  2. 确保 extension=sockets 行未被注释(无分号 ; 开头)。
  3. 重启Web服务使配置生效。
  4. 检查函数 socket_create() 是否存在:运行 var_dump(function_exists('socket_create'));。

第1步:构建SMTP连接管理类

目的:封装Socket生命周期,管理连接状态并处理异常响应码。

该类包含属性初始化、连接建立、命令执行校验及资源释放方法。关键参数包括服务器地址、端口、账号密码及邮件元数据。

class MySendMail {
    private $_userName;
    private $_password;
    protected $_sendServer;
    protected $_port = 25;
    protected $_from;
    protected $_to;
    protected $_subject;
    protected $_body;
    protected $_isHTML = false;
    protected $_socket;
    protected $_errorMessage;

    // 构造函数用于接收初始化参数
    public function __construct($from="", $to="", $subject="", $body="", 
                                $server="", $username="", $password="", $isHTML="", $port="") {
        if(!empty($from)) $this->_from = $from;
        if(!empty($to)) $this->_to = $to;
        if(!empty($subject)) $this->_subject = $subject;
        if(!empty($body)) $this->_body = $body;
        if(!empty($isHTML)) $this->_isHTML = $isHTML;
        if(!empty($server)) $this->_sendServer = $server;
        if(!empty($port)) $this->_port = $port;
        if(!empty($username)) $this->_userName = $username;
        if(!empty($password)) $this->_password = $password;
    }

    /**
     * 设置SMTP服务器信息
     */
    public function setServer($server, $port=25) {
        if(!isset($server) || empty($server) || !is_string($server)) {
            $this->_errorMessage = "Invalid server parameter";
            return false;
        }
        if(!is_numeric($port)) {
            $this->_errorMessage = "Invalid port parameter";
            return false;
        }
        $this->_sendServer = $server;
        $this->_port = $port;
        return true;
    }

    /**
     * 设置邮件内容及认证信息
     */
    public function setMailInfo($config) {
        if(!is_array($config) || count($config) < 6){
            $this->_errorMessage = "Missing parameters";
            return false;
        }
        $this->_from = $config['from'];
        $this->_to = $config['to'];
        $this->_subject = $config['subject'];
        $this->_body = $config['body'];
        $this->_userName = $config['username'];
        $this->_password = $config['password'];
        if(isset($config['isHTML'])){
            $this->_isHTML = $config['isHTML'];
        }
        return true;
    }

    // ... (其他辅助方法见下文代码块)
}

第2步:实现Socket连接与命令分发

目的:建立TCP通道,读取初始 Banner,并解析后续响应。

使用 socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp')) 创建流式套接字,并通过 socket_connect() 连接目标SMTP服务器。连接成功后需读取服务器欢迎信息。


    /**
     * 建立网络连接
     */
    private function socket() {
        if(!function_exists("socket_create")) {
            $this->_errorMessage = "Extension php-sockets must be enabled";
            return false;
        }
        $this->_socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
        if(!$this->_socket) {
            $this->_errorMessage = socket_strerror(socket_last_error());
            return false;
        }
        if(!socket_connect($this->_socket, $this->_sendServer, $this->_port)) {
            $this->_errorMessage = socket_strerror(socket_last_error());
            return false;
        }
        // 读取服务器Banner
        socket_read($this->_socket, 1024);
        return true;
    }

第3步:发送命令并校验响应

目的:构造符合RFC规范的指令序列,并严格匹配服务器返回的状态码(如250表示成功,334表示等待输入)。

getCommand() 方法根据是否启用HTML模式生成邮件头部结构。若为HTML邮件,需添加 MIME-Version 和 Content-type: text/html; charset=utf-8 头。用户名和密码在发送前需进行 Base64 编码。

sendCommand() 负责单次指令的发送与校验。它对比实际收到的状态码是否与预期一致,不一致则记录错误。


    /**
     * 获取完整的SMTP命令序列
     */
    protected function getCommand() {
        // 构建邮件体头部
        if($this->_isHTML) {
            $mail = "MIME-Version:1.0\r\n";
            $mail .= "Content-type:text/html;charset=utf-8\r\n";
            $mail .= "FROM:test<" . $this->_from . ">\r\n";
            $mail .= "TO:<" . $this->_to . ">\r\n";
            $mail .= "Subject:" . $this->_subject ."\r\n\r\n";
            $mail .= $this->_body . "\r\n.\r\n";
        } else {
            $mail = "FROM:test<" . $this->_from . ">\r\n";
            $mail .= "TO:<" . $this->_to . ">\r\n";
            $mail .= "Subject:" . $this->_subject ."\r\n\r\n";
            $mail .= $this->_body . "\r\n.\r\n";
        }

        // 定义命令序列及期望响应码
        $command = array(
            array("HELO sendmail\r\n", 250),
            array("AUTH LOGIN\r\n", 334),
            array(base64_encode($this->_userName) . "\r\n", 334),
            array(base64_encode($this->_password) . "\r\n", 235), // 235通常表示认证成功
            array("MAIL FROM:<" . $this->_from . ">\r\n", 250),
            array("RCPT TO:<" . $this->_to . ">\r\n", 250),
            array("DATA\r\n", 354),
            array($mail, 250),
            array("QUIT\r\n", 221)
        );
        return $command;
    }

    /**
     * 发送单条命令并验证返回值
     */
    protected function sendCommand($command, $code) {
        echo 'Send command:' . $command . ',expected code:' . $code . '<br />';
        try{
            if(socket_write($this->_socket, $command, strlen($command))){
                $data = trim(socket_read($this->_socket, 1024));
                echo 'response:' . $data . '<br /><br />';
                if($data) {
                    $pattern = "/^".$code."/";
                    if(preg_match($pattern, $data)) {
                        return true;
                    } else{
                        $this->_errorMessage = "Error:" . $data . "|**| command:";
                        return false;
                    }
                } else{
                    $this->_errorMessage = "Error:" . socket_strerror(socket_last_error());
                    return false;
                }
            } else{
                $this->_errorMessage = "Error:" . socket_strerror(socket_last_error());
                return false;
            }
        }catch(Exception $e) {
            $this->_errorMessage = "Error:" . $e->getMessage();
        }
    }

    public function close() {
        if(isset($this->_socket) && is_object($this->_socket)) {
            $this->_socket->close();
            return true;
        }
        return false;
    }
}

第4步:调用测试脚本

目的:实例化类并填入真实的SMTP服务器凭据进行测试。

请替换示例中的邮箱账号、密码及目标收件人为有效值。对于国内常用邮箱(如126、QQ、163),通常需要使用开启SMTP服务后生成的“授权码”作为密码。

$config = array(
    "from" => "your_email@126.com",
    "to" => "recipient@example.com",
    "subject" => "Test via Socket",
    "body" => "<b>Hello World</b>", // HTML内容
    "username" => "your_username",
    "password" => "your_auth_code",
    "isHTML" => true
);

$mail = new MySendMail();
$mail->setServer("smtp.126.com"); // 修改为你的SMTP服务器
$mail->setMailInfo($config);

if(!$mail->sendMail()) {
    echo "Send Failed: " . $mail->error();
} else {
    echo 'Mail OK!';
}

常见问题排查

如果发送失败,请按以下逻辑检查:

  1. 端口连接超时:默认端口 25 在许多云服务器上被运营商封堵。尝试改用 465 (SSL) 或 587 (TLS)。注意:原代码未实现STARTTLS或SSL封装,升级至加密协议需引入 ssl:// 前缀并调整上下文。
  2. 认证失败 (535/550):检查用户名是否为完整邮箱地址;密码是否为第三方登录授权码而非登录密码。
  3. 语法错误:确保字符串拼接时使用正确的换行符 \r\n,部分老旧服务器对 \n 识别不佳。

总结

通过上述步骤,您可以完全掌握基于Socket的原生邮件发送机制。此方法轻量且透明,适合学习网络协议或在不允许安装Composer依赖的受限环境中部署。对于生产环境的大规模邮件服务,仍推荐使用成熟的库以更好地处理反垃圾邮件策略和多线程并发。

以上就是PHP基于Socket实现SMTP发送邮件的详细内容,更多关于PHP邮件开发的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。