首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 基於 Socket 實現 SMTP 發送郵件完整指南

     編輯:PHP 相關知識

在不依賴 PHPMailer 等第三方庫的情況下,直接使用 PHP 的 Socket 擴展實現 SMTP 郵件發送,能夠更深入地理解郵件協議原理,也適合輕量級項目或教學場景。本文提供一個功能完整、支持 HTML 格式、帶錯誤處理的 SMTP 發送類實現。

核心原理

SMTP(簡單郵件傳輸協議)基於 TCP 連接,通過文本命令與郵件服務器交互。PHP 的 socket_create、socket_connect、socket_write、socket_read 等函數提供了底層網絡通信能力。發送流程主要包括:建立連接 → EHLO/HELO 問候 → AUTH LOGIN 認證 → MAIL FROM/RCPT TO 指定收發件人 → DATA 傳輸郵件內容 → QUIT 結束會話。

完整類實現

<?php
class SmtpMailer {
    private $host;
    private $port = 25;
    private $username;
    private $password;
    private $from;
    private $to;
    private $subject;
    private $body;
    private $isHtml = false;
    private $socket;
    private $error = '';

    public function __construct(array $config = []) {
        foreach ($config as $key => $value) {
            if (property_exists($this, $key)) {
                $this->$key = $value;
            }
        }
    }

    public function setServer(string $host, int $port = 25): bool {
        if (empty($host) || !is_string($host)) {
            $this->error = 'Invalid server address';
            return false;
        }
        $this->host = $host;
        $this->port = $port;
        return true;
    }

    public function setMailInfo(array $info): bool {
        $required = ['from', 'to', 'subject', 'body', 'username', 'password'];
        if (count(array_intersect_key(array_flip($required), $info)) !== count($required)) {
            $this->error = 'Missing required mail fields';
            return false;
        }
        foreach ($info as $key => $value) {
            if (property_exists($this, $key)) {
                $this->$key = $value;
            }
        }
        return true;
    }

    public function send(): bool {
        if (!$this->connect()) return false;
        
        $commands = $this->buildCommands();
        foreach ($commands as [$cmd, $expectedCode]) {
            if (!$this->sendCommand($cmd, $expectedCode)) {
                $this->close();
                return false;
            }
        }
        
        $this->close();
        return true;
    }

    public function getError(): string {
        return $this->error;
    }

    private function connect(): bool {
        if (!function_exists('socket_create')) {
            $this->error = 'PHP sockets extension not enabled';
            return false;
        }
        
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if (!$this->socket) {
            $this->error = socket_strerror(socket_last_error());
            return false;
        }
        
        if (!socket_connect($this->socket, $this->host, $this->port)) {
            $this->error = socket_strerror(socket_last_error());
            return false;
        }
        
        // 讀取服務器歡迎信息
        $response = socket_read($this->socket, 1024);
        return $this->checkResponse($response, 220);
    }

    private function buildCommands(): array {
        $headers = "From: {$this->from}\r\n";
        $headers .= "To: {$this->to}\r\n";
        $headers .= "Subject: {$this->subject}\r\n";
        
        if ($this->isHtml) {
            $headers .= "MIME-Version: 1.0\r\n";
            $headers .= "Content-Type: text/html; charset=UTF-8\r\n";
        } else {
            $headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
        }
        
        $message = $headers . "\r\n" . $this->body . "\r\n.\r\n";
        
        return [
            ["EHLO localhost\r\n", 250],
            ["AUTH LOGIN\r\n", 334],
            [base64_encode($this->username) . "\r\n", 334],
            [base64_encode($this->password) . "\r\n", 235],
            ["MAIL FROM: <{$this->from}>\r\n", 250],
            ["RCPT TO: <{$this->to}>\r\n", 250],
            ["DATA\r\n", 354],
            [$message, 250],
            ["QUIT\r\n", 221]
        ];
    }

    private function sendCommand(string $command, int $expectedCode): bool {
        if (!socket_write($this->socket, $command, strlen($command))) {
            $this->error = 'Send failed: ' . socket_strerror(socket_last_error());
            return false;
        }
        
        $response = trim(socket_read($this->socket, 1024));
        return $this->checkResponse($response, $expectedCode);
    }

    private function checkResponse(string $response, int $expectedCode): bool {
        if (empty($response)) {
            $this->error = 'Empty response from server';
            return false;
        }
        
        if (!preg_match("/^{$expectedCode}/", $response)) {
            $this->error = "SMTP Error: {$response}";
            return false;
        }
        return true;
    }

    private function close(): void {
        if ($this->socket) {
            socket_shutdown($this->socket);
            socket_close($this->socket);
        }
    }
}

使用示例

<?php
require_once 'SmtpMailer.php';

$config = [
    'host' => 'smtp.163.com',
    'port' => 465, // SSL 端口需配合 stream_socket_client 或 openssl
    'username' => 'your_email@163.com',
    'password' => 'your_auth_code', // 授權碼,非登錄密碼
    'from' => 'your_email@163.com',
    'to' => 'recipient@example.com',
    'subject' => 'PHP Socket SMTP 測試',
    'body' => '

這是一封 HTML 郵件

通過原生 Socket 發送。

', 'isHtml' => true ]; $mailer = new SmtpMailer($config); if ($mailer->send()) { echo '郵件發送成功!'; } else { echo '發送失敗:' . $mailer->getError(); }

重要注意事項

  1. SSL/TLS 支持:現代郵件服務器多要求加密連接(465 或 587 端口)。原生 Socket 需配合 stream_socket_client + STREAM_CRYPTO_METHOD_TLS_CLIENT 實現,或使用 OpenSSL 封裝。
  2. 授權碼而非密碼:主流郵箱(QQ、163、Gmail 等)SMTP 認證需使用「授權碼」或「應用專用密碼」。
  3. 錯誤處理:生產環境建議增加重試機制、超時設置、日誌記錄。
  4. 編碼問題:郵件頭與正文均應明確指定 UTF-8,中文主題需 Base64 編碼(=?UTF-8?B?base64_string?=)。
  5. 反垃圾郵件:配置 SPF、DKIM、DMARC 記錄,避免進垃圾箱。

總結

原生 Socket 實現 SMTP 雖然代碼量較大,但能幫助開發者深入理解郵件發送底層流程。實際項目中,推薦使用成熟的 PHPMailer、Symfony Mailer 或 Laravel Mail 等庫,它們封裝了加密、附件、嵌入圖片、隊列發送等豐富功能,更加穩健高效。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved