
SMTP命令行交互的原始数据长什么样
直接用 telnet smtp.163.com 25 连上去看到的,就是原始交互流:服务器发回的响应以三位数字开头(如 220、250、334),客户端发出的命令全是纯文本(如 HELO、AUTH LOGIN、MAIL FROM:),每行末尾是 \r\n(不是 \n)。这个细节极关键——漏掉 \r 或错用 \n,多数 SMTP 服务器会静默断连或返回 500 Syntax error。
用C++按行解析时必须处理的边界问题
不能简单用 std::getline 配 \n 切分。因为:
- 原始协议要求行尾是
\r\n,而std::getline默认只认\n,会导致读到\r残留在字符串末尾(比如把"250 OK\r"当成有效响应,实际后续命令可能失败) - 服务器可能一次发多个响应(如
EHLO后连续返回250-AUTH LOGIN、250-8BITMIME、250 OK),需识别-连续响应与终结响应的区别 - Base64 认证响应(如
334 dXNlcm5hbWU6)中间有空格,但空格后内容是编码串,不能按空格粗暴分割
推荐做法:用 recv 或 read 逐字节收,自己累积缓冲区,遇到 \r\n 才切一行,并手动去掉结尾的 \r\n;对响应码用 str.substr(0, 3) 提取,再转整数判断。
如何区分命令和响应,以及多行 DATA 的特殊处理
命令永远由客户端发出,响应永远由服务器返回——但原始字节流里没有方向标记。所以必须靠状态机驱动:
- 连接建立后第一行必为服务器欢迎响应(
220),之后客户端才发EHLO或HELO -
DATA命令发出后,服务器返回354,此后所有客户端发送的数据(包括邮件头、正文、附件)都属于 DATA 内容,直到单独一行只有.(即"\r\n.\r\n")才表示结束 - 这意味着:收到
354后,必须切换解析模式——不再按行提取命令/响应,而是等待\r\n.\r\n边界
示例片段逻辑:
if (state == WAITING_FOR_DATA_END) {
if (line == ".") { // 注意:这里 line 已去除 \r\n,所以直接比 "."
state = WAITING_FOR_RESPONSE;
send("QUIT\r\n");
} else {
// 累积进邮件体 buffer
}
}
认证阶段 Base64 编码字符串的提取陷阱
AUTH LOGIN 流程中,服务器返回的 334 响应后面跟着 Base64 字符串(如 334 dXNlcm5hbWU6),但 RFC 5321 明确规定:该字符串**不包含换行,且与响应码之间只有一个空格**。常见错误是:
- 用
std::istringstream按空格 split,结果把dXNlcm5hbWU6当成独立 token —— 正确做法是找第一个空格位置,取其后全部内容(trim 掉首尾空白即可) - 误以为 Base64 字符串一定对应 "Username" 或 "Password",其实它只是服务器提示你要填什么(
dXNlcm5hbWU6解码是 "Username:",UGFzc3dvcmQ6是 "Password:"),真正要发送的是你自己的 Base64 编码后的账号密码 - 没做 Base64 编码容错:输入含非 ASCII 字符(如中文邮箱)时,必须先 UTF-8 编码再 Base64,否则服务器返回
535 Authentication failed
真正难的不是写 Base64 函数,而是搞清哪一步该编码、哪一步该解码、哪一步只是透传字符串——整个 SMTP 交互里,只有 AUTH 阶段的凭据和部分邮件头字段(如 Subject)需要 Base64,其余全是明文。
最易被忽略的点:很多开源 C++ SMTP 库默认用 \n 分割响应,但在真实公网 SMTP 服务器(如 163、QQ、Gmail 的 587 端口)上,只要有一处没严格遵循 \r\n,就会在 DATA 阶段卡死或被拒绝。协议看似简单,生死全在换行符和状态同步的毫秒级精度上。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











