在C++中实现SMTP邮件发送,核心是通过socket建立与SMTP服务器的TCP连接,按照SMTP协议规定的指令顺序完成交互,最终实现邮件内容的投递。整个过程不需要依赖复杂的第三方库,只需要使用系统自带的socket相关接口即可完成。

SMTP协议基础交互流程
SMTP协议是邮件传输的标准协议,发送邮件时客户端和服务器的基本交互顺序如下:
- 建立TCP连接,SMTP默认端口是25,加密端口通常是465或者587
- 接收服务器的欢迎信息
- 发送EHLO指令标识客户端身份
- 如果需要认证,发送AUTH LOGIN指令完成账号密码校验
- 发送MAIL FROM指令指定发件人
- 发送RCPT TO指令指定收件人
- 发送DATA指令开始传输邮件内容
- 邮件内容结束后发送换行加点号的结束标识
- 发送QUIT指令断开连接
核心代码实现
socket连接建立
首先需要使用socket创建TCP连接,这里以Windows平台为例,Linux平台只需要调整头文件和初始化部分即可:
#include <iostream>
#include <winsock2.h>
#include <string>
#include <base64.h> // 需要自行实现或引入base64编码库
#pragma comment(lib, "ws2_32.lib")
// 初始化socket环境
bool initSocket() {
WSADATA wsaData;
return WSAStartup(MAKEWORD(2, 2), &wsaData) == 0;
}
// 建立与SMTP服务器的连接
SOCKET connectSmtpServer(const std::string& server, int port) {
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
std::cout << "创建socket失败" << std::endl;
return INVALID_SOCKET;
}
struct hostent* host = gethostbyname(server.c_str());
if (host == NULL) {
std::cout << "解析服务器地址失败" << std::endl;
closesocket(sock);
return INVALID_SOCKET;
}
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
memcpy(&serverAddr.sin_addr, host->h_addr, host->h_length);
if (connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::cout << "连接服务器失败" << std::endl;
closesocket(sock);
return INVALID_SOCKET;
}
return sock;
}
SMTP指令发送与响应接收
需要实现发送指令和接收服务器响应的通用函数,方便后续调用:
// 发送数据到socket
bool sendData(SOCKET sock, const std::string& data) {
int ret = send(sock, data.c_str(), data.length(), 0);
return ret != SOCKET_ERROR && ret == data.length();
}
// 接收服务器响应
std::string recvResponse(SOCKET sock) {
char buffer[1024] = {0};
int ret = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (ret > 0) {
buffer[ret] = ' ';
return std::string(buffer);
}
return "";
}
// 等待服务器返回指定状态码的响应
bool waitForResponse(SOCKET sock, const std::string& expectedCode) {
std::string response = recvResponse(sock);
return response.find(expectedCode) == 0;
}
完整邮件发送流程
以下是完整的邮件发送示例,包含认证、构造邮件内容、发送等步骤:
// base64编码函数示例,实际使用时可以替换为成熟的实现
std::string base64Encode(const std::string& input) {
// 这里省略具体base64编码实现,可参考开源库或自行编写
// 返回编码后的字符串
return "";
}
bool sendEmail(const std::string& smtpServer, int port,
const std::string& username, const std::string& password,
const std::string& from, const std::string& to,
const std::string& subject, const std::string& body) {
// 初始化socket
if (!initSocket()) {
return false;
}
// 连接SMTP服务器
SOCKET sock = connectSmtpServer(smtpServer, port);
if (sock == INVALID_SOCKET) {
WSACleanup();
return false;
}
// 等待服务器欢迎信息
if (!waitForResponse(sock, "220")) {
std::cout << "服务器欢迎信息错误" << std::endl;
closesocket(sock);
WSACleanup();
return false;
}
// 发送EHLO指令
if (!sendData(sock, "EHLO clientrn") || !waitForResponse(sock, "250")) {
std::cout << "EHLO指令失败" << std::endl;
closesocket(sock);
WSACleanup();
return false;
}
// 发送AUTH LOGIN认证
if (!sendData(sock, "AUTH LOGINrn") || !waitForResponse(sock, "334")) {
std::cout << "AUTH LOGIN指令失败" << std::endl;
closesocket(sock);
WSACleanup();
return false;
}
// 发送base64编码的用户名
std::string encodedUser = base64Encode(username);
if (!sendData(sock, encodedUser + "rn") || !waitForResponse(sock, "334")) {
std::cout << "用户名认证失败" << std::endl;
closesocket(sock);
WSACleanup();
return false;
}
// 发送base64编码的密码
std::string encodedPass = base64Encode(password);
if (!sendData(sock, encodedPass + "rn") || !waitForResponse(sock, "235")) {
std::cout << "密码认证失败" << std::endl;
closesocket(sock);
WSACleanup();
return false;
}
// 指定发件人
if (!sendData(sock, "MAIL FROM:<" + from + ">rn") || !waitForResponse(sock, "250")) {
std::cout << "设置发件人失败" << std::endl;
closesocket(sock);
WSACleanup();
return false;
}
// 指定收件人
if (!sendData(sock, "RCPT TO:<" + to + ">rn") || !waitForResponse(sock, "250")) {
std::cout << "设置收件人失败" << std::endl;
closesocket(sock);
WSACleanup();
return false;
}
// 开始传输邮件内容
if (!sendData(sock, "DATArn") || !waitForResponse(sock, "354")) {
std::cout << "DATA指令失败" << std::endl;
closesocket(sock);
WSACleanup();
return false;
}
// 构造邮件内容
std::string emailContent = "From: " + from + "rn";
emailContent += "To: " + to + "rn";
emailContent += "Subject: " + subject + "rn";
emailContent += "rn";
emailContent += body + "rn";
emailContent += ".rn"; // 邮件内容结束标识
if (!sendData(sock, emailContent) || !waitForResponse(sock, "250")) {
std::cout << "发送邮件内容失败" << std::endl;
closesocket(sock);
WSACleanup();
return false;
}
// 断开连接
sendData(sock, "QUITrn");
closesocket(sock);
WSACleanup();
return true;
}
// 调用示例
int main() {
// 以ipipp.com的SMTP服务为例,实际替换为自己的SMTP服务器信息
bool ret = sendEmail("smtp.ipipp.com", 25,
"test@ipipp.com", "password123",
"test@ipipp.com", "receiver@ipipp.com",
"C++ SMTP测试邮件", "这是一封通过C++调用SMTP协议发送的测试邮件");
if (ret) {
std::cout << "邮件发送成功" << std::endl;
} else {
std::cout << "邮件发送失败" << std::endl;
}
return 0;
}
注意事项
- 如果使用加密端口,需要额外添加SSL/TLS的支持,Windows下可以使用OpenSSL库实现加密连接
- 邮件内容如果需要支持中文,需要对主题和正文进行UTF-8编码,并且添加对应的MIME头标识
- 不同的SMTP服务器可能有不同的限制,比如单日发送量、发件人域名校验等,使用前需要确认服务器规则
- 代码中涉及的<code>base64Encode</code>函数需要自行实现或者引入成熟的base64编码库,确保编码结果符合SMTP协议要求
- 实际开发中需要添加更完善的错误处理,比如超时重试、网络异常捕获等逻辑