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

SMTP 協議封裝類 CSMTP 完整設計與實作:基於 CSocket 的郵件發送引擎

     編輯:關於C語言

SMTP 協議核心流程 (RFC 5321)

  1. TCP 連線至埠 25 (或 465 SSL / 587 STARTTLS)。
  2. 伺服器回應 220 就緒。
  3. 客戶端發送 HELO/EHLO domain → 250。
  4. MAIL FROM: → 250。
  5. RCPT TO: (可多次) → 250。
  6. DATA → 354 進入資料模式。
  7. 發送郵件內容 (Headers + 空行 + Body),以 \r\n.\r\n 結束 → 250。
  8. QUIT → 221 關閉。

CSMTP 類別設計 (Header)


// SMTP.h
#pragma once
#include  // CSocket

class CSMTP
{
public:
    CSMTP();
    virtual ~CSMTP();

    // 連線管理
    BOOL Connect(CString strHost, CString strLocalName = _T("localhost"));
    BOOL Connect(); // 使用 SetHost/SetFrom 預設值
    BOOL Disconnect();

    // 寄件者/收件者
    void SetFrom(CString strFrom); CString GetFrom();
    BOOL SetTo(CString strTo);   CString GetTo(); // 支援多收件者內部用陣列
    void AddTo(CString strTo);   // 追加收件者

    // 郵件內容
    BOOL Data(CString strSubject, CString strBody); // 組裝並發送 DATA

    // 低階命令 (進階用途)
    BOOL Hello();   // EHLO
    BOOL Mail();    // MAIL FROM
    BOOL Rcpt();    // RCPT TO
    BOOL Quit();

    // 錯誤處理
    CString GetErrorMessage() const;

private:
    // 內部狀態
    CSocket m_socket;
    CString m_strHost;
    CString m_strFrom;
    CStringArray m_arrTo; // 多收件者
    CString m_strErrorMsg;
    int m_nState; // 狀態機: CONNECTED, HELO_DONE, MAIL_DONE, RCPT_DONE, DATA_DONE

    // 輔助
    BOOL SendCommand(LPCSTR pszCmd, CString& strResponse);
    BOOL CheckResponse(int nExpectedCode, const CString& strResponse);
    CString FormatAddress(const CString& strAddr); // 加 < >
};

核心實作細節 (CPP 節選)

連線與應答讀取


BOOL CSMTP::Connect(CString strHost, CString strLocalName)
{
    m_strHost = strHost;
    m_strErrorMsg.Empty();
    m_nState = 0;

    if (!m_socket.Create()) { m_strErrorMsg = _T("Create socket failed"); return FALSE; }
    if (!m_socket.Connect(strHost, 25)) { m_strErrorMsg = _T("Connect failed: ") + m_strHost; return FALSE; }

    CString resp;
    if (!ReadResponse(resp) || !CheckResponse(220, resp)) return FALSE;

    m_nState = 1; // CONNECTED
    return Hello(); // 發送 EHLO
}

BOOL CSMTP::ReadResponse(CString& strResp)
{
    strResp.Empty();
    char buf[1024];
    while (true) {
        int n = m_socket.Receive(buf, sizeof(buf)-1, 0);
        if (n <= 0) return FALSE;
        buf[n] = '\0';
        strResp += CA2T(buf);
        // SMTP 回應以 "XXX " (代碼+空格) 結尾為完整行,或最後一行 "XXX\r\n"
        if (strResp.GetLength() >= 4 && strResp[3] == ' ') break; // 簡化判斷
    }
    return TRUE;
}

命令發送與狀態機


BOOL CSMTP::SendCommand(LPCSTR pszCmd, CString& strResp)
{
    CString cmd(pszCmd); cmd += _T("\r\n");
    if (m_socket.Send(cmd, cmd.GetLength()) == SOCKET_ERROR) return FALSE;
    return ReadResponse(strResp);
}

BOOL CSMTP::CheckResponse(int nExpected, const CString& resp)
{
    if (resp.GetLength() < 3) return FALSE;
    int code = _ttoi(resp.Left(3));
    if (code != nExpected) {
        m_strErrorMsg.Format(_T("SMTP Error %d (expected %d): %s"), code, nExpected, resp);
        return FALSE;
    }
    return TRUE;
}

BOOL CSMTP::Hello()
{
    CString cmd; cmd.Format(_T("EHLO %s\r\n"), m_strLocalName);
    CString resp;
    if (!SendCommand(cmd, resp) || !CheckResponse(250, resp)) return FALSE;
    m_nState = 2; return TRUE;
}

BOOL CSMTP::Mail()
{
    CString cmd; cmd.Format(_T("MAIL FROM:<%s>\r\n"), m_strFrom);
    CString resp;
    if (!SendCommand(cmd, resp) || !CheckResponse(250, resp)) return FALSE;
    m_nState = 3; return TRUE;
}

BOOL CSMTP::Rcpt()
{
    for (int i = 0; i < m_arrTo.GetSize(); ++i) {
        CString cmd; cmd.Format(_T("RCPT TO:<%s>\r\n"), m_arrTo[i]);
        CString resp;
        if (!SendCommand(cmd, resp) || !CheckResponse(250, resp)) return FALSE;
    }
    m_nState = 4; return TRUE;
}

BOOL CSMTP::Data(CString strSubject, CString strBody)
{
    CString resp;
    if (!SendCommand(_T("DATA"), resp) || !CheckResponse(354, resp)) return FALSE;

    // 組裝郵件內容 (RFC 5322)
    CString msg;
    msg.Format(_T("From: <%s>\r\n"), m_strFrom);
    for (int i = 0; i < m_arrTo.GetSize(); ++i)
        msg.Format(_T("%sTo: <%s>\r\n"), msg, m_arrTo[i]);
    msg.Format(_T("%sSubject: %s\r\n"), msg, strSubject);
    msg += _T("MIME-Version: 1.0\r\n");
    msg += _T("Content-Type: text/plain; charset=\"utf-8\"\r\n");
    msg += _T("\r\n"); // 空行分隔 Header/Body
    msg += strBody;
    msg += _T("\r\n.\r\n"); // 結束標記

    if (m_socket.Send(msg, msg.GetLength()) == SOCKET_ERROR) return FALSE;
    if (!ReadResponse(resp) || !CheckResponse(250, resp)) return FALSE;

    m_nState = 5; return TRUE;
}

使用範例


void CSendMailDlg::OnSend()
{
    CSMTP smtp;
    smtp.SetHost(_T("smtp.gmail.com")); // 或公司郵件伺服器
    smtp.SetFrom(_T("sender@example.com"));
    smtp.AddTo(_T("recipient1@domain.com"));
    smtp.AddTo(_T("recipient2@domain.com"));

    if (!smtp.Connect()) {
        AfxMessageBox(_T("連線失敗: ") + smtp.GetErrorMessage());
        return;
    }

    if (!smtp.Data(_T("測試主旨"), _T("這是郵件內容測試。\n第二行。"))) {
        AfxMessageBox(_T("發送失敗: ") + smtp.GetErrorMessage());
    }
    else {
        AfxMessageBox(_T("發送成功!"));
    }
    smtp.Disconnect();
}

進階議題:認證、SSL/TLS、附件

功能實作方向
SMTP AUTH (LOGIN/PLAIN/CRAM-MD5)EHLO 後檢查 250-AUTH,發送 AUTH LOGIN,Base64 編碼用戶名/密碼。
SMTPS (Port 465, Implicit SSL)使用 CSslSocket (MFC 無內建,需 OpenSSL/SChannel 封裝) 或 STARTTLS 升級。
STARTTLS (Port 587, Explicit SSL)EHLO 後發送 STARTTLS,收到 220 後在現有 Socket 上進行 TLS 握手。
附件 (MIME Multipart)Content-Type: multipart/mixed,Body 分為多個 Part,各含 Content-Disposition: attachment、Base64 編碼檔案內容。
HTML 郵件Content-Type: text/html 或 multipart/alternative 同時提供 text/plain 版本。

常見錯誤與除錯

  • 550 Relay denied:伺服器拒絕轉發,需認證或 IP 白名單。
  • 421 Service not available:連線數超限、IP 信譽差、反垃圾郵件機制。
  • 554 Transaction failed:內容觸發垃圾郵件過濾 (關鍵字、附件類型、IP 信譽)。
  • 逾時:m_socket.Send/Receive 預設阻塞,建議設定 SO_RCVTIMEO/SO_SNDTIMEO 或使用非阻塞 + select。
  • 編碼問題:Header 含非 ASCII 需 =?utf-8?B?base64?= (RFC 2047)。

總結

CSMTP 以 CSocket 為基礎,實作 SMTP 指令狀態機、應答碼驗證、郵件內容組裝。核心難點在於同步阻塞 I/O 的流程控制、多行應答解析、MIME 編碼、SSL/TLS 整合。生產環境建議:使用成熟庫 (VMime, POCO, Casablanca) 或系統 CDO/MAPI,自行封裝僅適合教學、輕量嵌入或特定舊版相容場景。

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