CHAPTER 18
EmailSender 函数手册
依据源码:include/EmailSender.h、src/EmailSender.cpp。
0. 模块定位与总体结构
EmailSender 是一个轻量 SMTP 客户端封装,支持:
- 单例获取
- SMTP 参数初始化
- 单收件人 / 多收件人发送
- AUTH LOGIN 认证
- 纯文本邮件正文发送
关键状态成员
smtpHost_,smtpPort_,username_,password_lastError_std::mutex mutex_static EmailSender* instance_
1. 单例与初始化
1.1 static EmailSender* getInstance()
- 位置:
EmailSender.cpp - 实现:
static EmailSender inst;instance_ = &inst;- 返回
instance_
1.2 void init(const std::string& smtpHost, unsigned short smtpPort, const std::string& username, const std::string& password)
- 作用:设置 SMTP 连接参数并清空上次错误。
- 并发:函数内部
lock_guard<mutex_>,确保参数更新与发送互斥。 - 行为:
- 覆盖旧配置
lastError_.clear()
2. 编码辅助
2.1 static std::string base64Encode(const std::string& raw)
- 作用:SMTP
AUTH LOGIN需要对用户名/密码做 Base64 编码。 - 实现要点:
- 3 字节分组编码
- 尾部不足 3 字节时补
= - 返回标准 Base64(不是 URL 安全变体)
3. 网络连接与协议收发
3.1 bool smtpConnect(int* sockOut)
- 作用:建立到 SMTP 服务器的 TCP 连接。
步骤
- Windows 下
WSAStartup(每次调用都初始化一次) getaddrinfo(smtpHost_, smtpPort_)socket(...)connect(...)- 成功后
*sockOut = sock
失败路径
- 每个失败分支都设置
lastError_ - Windows 下在失败时调用
WSACleanup() - 释放
addrinfo、关闭 socket(如已创建)
注意点
hints.ai_family = AF_INET:当前只走 IPv4- 无连接超时参数(依赖系统默认)
3.2 bool smtpCommandRecv(int sock, std::string* outLine)
- 作用:从 socket 读取一行 SMTP 响应(简化实现)。
逻辑
- 循环
recv到本地line - 若
n <= 0返回 false - 终止条件:
line.size() >= 4 && line[3] == ' '(SMTP 最终行)- 或已包含
\r\n
- 截断到首个
\r\n之前 - 若
outLine非空则写入
边界
- 对多行响应处理较简化,主要适配当前使用的命令流。
3.3 bool smtpCommand(int sock, const std::string& cmd, int expectCode)
- 作用:发送 SMTP 命令并校验响应码。
步骤
- 发送
cmd + "\r\n" - 调用
smtpCommandRecv收响应 - 解析前 3 位数字为
code return code == expectCode
注意
- 若
send或recv失败,直接返回 false - 不自动写
lastError_(多数错误信息由调用方设置)
4. 发送主流程
4.1 bool sendMailImpl(const std::string& from, const std::vector<std::string>& toList, const std::string& subject, const std::string& body)
- 作用:完整 SMTP 会话(核心函数)。
0) 前置检查
- 若
smtpHost_或username_为空:lastError_ = "EmailSender not initialized. Call init() first."- 返回 false
1) 建连与服务器问候
smtpConnect(&sock)失败返回 false- 首次
smtpCommandRecv,要求响应220 - 否则:
lastError_ = "SMTP greeting failed: " + line- 关闭 socket,返回 false
2) 标识与认证
- 先
EHLO localhost期待250 - 失败则回退
HELO localhost期待250 AUTH LOGIN期待334- 发送
base64(username_)期待334 - 发送
base64(password_)期待235
任一步失败都设置 lastError_ 并关闭连接返回 false。
3) 邮件信封
MAIL FROM:<from>期待250- 遍历
toList:- 去掉空白字符
- 空字符串跳过
RCPT TO:<recipient>期待250
- 若任何收件人失败:
lastError_ = "RCPT TO failed: ..."- 关闭连接返回 false
4) 邮件正文
DATA期待354- 构造数据:
From: ...To: ...(用逗号拼接)Subject: ...Content-Type: text/plain; charset=UTF-8- 空行
- 正文
body - 终止符
\r\n.
- 实际发送字符串为
data + "\r\n"(即 SMTP data block 的<CRLF>.<CRLF>) - 读响应,要求
250
失败则设置 lastError_ 并返回 false。
5) 结束会话
smtpCommand(sock, "QUIT", 221)(返回值未强制检查)- 关闭 socket
- Windows 下
WSACleanup() - 返回 true
5. 对外发送接口
5.1 bool sendMail(const std::string& from, const std::string& to, const std::string& subject, const std::string& body)
- 作用:字符串收件人版本(支持逗号或分号分隔)。
- 逻辑:
- 解析
to:- 分隔符
,或; - 忽略空白
- 分隔符
- 形成
toList - 调用向量版本
sendMail(from, toList, subject, body)
- 解析
5.2 bool sendMail(const std::string& from, const std::vector<std::string>& toList, const std::string& subject, const std::string& body)
- 作用:线程安全入口。
- 并发控制:
lock_guard<mutex_>- 保护配置读写与
lastError_
- 实际执行:
- 调用
sendMailImpl(...)
- 调用
6. 只读接口
std::string getLastError() const
- 定义在头文件内联
- 返回
lastError_的副本
说明:该接口本身未加锁;但主写路径在
sendMail和init中已加锁。若极端并发下要求严格一致性,可考虑读也加锁或使用原子/线程安全日志通道。
7. 跨平台实现差异
Windows
- 使用 Winsock:
WSAStartupclosesocketWSACleanup
_MSC_VER下链接ws2_32.lib
Linux/Unix
- POSIX socket:
socket/connect/send/recv/close
8. 语义与边界说明(面试高频)
- 协议支持范围
- 当前是明文 SMTP + AUTH LOGIN,未实现 STARTTLS/SMTPS。
- 超时控制
- 代码中无 socket 超时设置,网络异常可能阻塞较久。
- 邮件格式
- 仅文本正文,未做 MIME multipart、附件、HTML。
- 响应解析
- 多行响应处理简化,适配常见服务器但并非完整 RFC 严格实现。
- 资源清理
- 成功路径与失败路径基本都有 socket 关闭;Windows 清理在多个分支执行。
9. 改进建议(可直接面试回答)
- 增加 TLS(STARTTLS / SMTPS 465)
- 增加 socket 超时与重试策略
- 完整 SMTP 多行响应解析
- MIME/附件/HTML 支持
- 引入异步发送队列与失败重投
- 抽象错误码,而非仅
lastError_文本
下一模块(顺序)
JsonObjectView → docs/modules/detail/JsonObjectView_FUNCTIONS.md