linux下基于SMTP协议的C++邮件客户端
2015-10-14 21:52
751 查看
完整代码下载:
https://github.com/WaPonX/SMTPMail
在网络中使用SMTP登陆的时候,需要将代码转换成base64编码。
下面这个函数是从网上抄的:
使用这个函数就可以完成base64转码
在linux下你可以使用如下命令:
对上面的三个函数进行说明:
1.
这个函数包装了打开日志文件这个功能,使用了O_WRONLY | O_APPEND,因为只需要写,不需要读而且每次都要写到文件的最后。
2.
这个函数为获取系统当前的时间,并按string类型返回。
3.
这个函数就是将错误信息写入日志文件的函数,为什么第二个参数没有命名?一开始我是有命名的,后来发现这个参数没用,所以就不命名了。这样的做法可以避免编译器报错。是一个小技巧。
在这段代码中我只挑一部分函数进行说明,毕竟有的函数很简单,没必要说明:
1.
需要注意的是AddrType是我自己typedef出来的一个类型:
其实,这个做法还有一个更加明智的代替方法:
使用智能指针。并且为这个智能智能指针定义自己的删除器。
具体做法请自行google
2.
这个函数将尝试连接getaddrinfo返回的所有可用的地址,使用setsockopt函数设置端口可以复用,并连接。
如果成功,则返回true。
https://github.com/WaPonX/SMTPMail
在网络中使用SMTP登陆的时候,需要将代码转换成base64编码。
下面这个函数是从网上抄的:
#include <string> std::string Base64Encode(const std::string& src) { using std::string; int i, j, srcLen = src.length(); string dst(srcLen / 3 * 4 + 4, 0); for(i = 0, j= 0; i <=srcLen - 3; i+=3, j+=4) { dst[j] = (src[i] & 0xFC) >> 2; dst[j+1] = ((src[i] & 0x03) << 4) + ((src[i+1] & 0xF0) >> 4); dst[j+2] = ((src[i+1] & 0x0F) << 2) + ((src[i+2] & 0xC0) >> 6); dst[j+3] = src[i+2] & 0x3F; } if( srcLen % 3 == 1 ) { dst[j] = (src[i] & 0xFC) >> 2; dst[j+1] = ((src[i] & 0x03) << 4); dst[j+2] = 64; dst[j+3] = 64; j += 4; } else if( srcLen % 3 == 2 ) { dst[j] = (src[i] & 0xFC) >> 2; dst[j+1] = ((src[i] & 0x03) << 4) + ((src[i+1] & 0xF0) >> 4); dst[j+2] = ((src[i+1] & 0x0F) << 2); dst[j+3] = 64; j+=4; } static unsigned char *base64 = (unsigned char*)("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="); for(i = 0; i < j; ++i) { //map 6 bit value to base64 ASCII character dst[i] = base64[(int)dst[i]]; } return dst; }
使用这个函数就可以完成base64转码
在linux下你可以使用如下命令:
echo string | base64将string替换成你想要转换的字符串,输出的就是对应的base64编码。
#include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string> #include <time.h> namespace Mail { bool OpenLogFile(int &log) { if ( (log = open("./mail.log", O_WRONLY | O_APPEND)) == -1) { printf("open log file error!\n"); printf("please, make sure have a file called mail.log in the path\n"); return false; } return true; } std::string GetTime() { time_t tt = time(NULL); return std::string(ctime(&tt)); } void log(const char *str, size_t) { static int log = -1; if (log < 0) { if (!OpenLogFile(log)) { return ; } } std::string res(GetTime()); res = res + str + "\n"; write (log, res.c_str(), res.length()); } } // namespace Mail
对上面的三个函数进行说明:
1.
bool OpenLogFile(int &log);
这个函数包装了打开日志文件这个功能,使用了O_WRONLY | O_APPEND,因为只需要写,不需要读而且每次都要写到文件的最后。
2.
std::string GetTime();
这个函数为获取系统当前的时间,并按string类型返回。
3.
void log(const char *str, size_t);
这个函数就是将错误信息写入日志文件的函数,为什么第二个参数没有命名?一开始我是有命名的,后来发现这个参数没用,所以就不命名了。这样的做法可以避免编译器报错。是一个小技巧。
#include "base64encode.h" #include "log.h" #include "smtpmail.h" #include <errno.h> #include <netdb.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> namespace { const std::string SMTPPORT("25"); } namespace Mail { SMTPMail::SMTPMail (const String &username, const String &password, const String &host) : _islogin(false), _reuseaddr(true), _fd(-1), _paddrfilter(InitAddrInfoFilter()), _username(username), _password(Base64Encode(password)), _host(host){ //_paddrfilter = InitAddrInfoFilter(); } SMTPMail::~SMTPMail() { if (_fd > 0) { close(_fd); } if (_paddrfilter != NULL) { freeaddrinfo(_paddrfilter); } } SMTPMail::AddrType *SMTPMail::InitAddrInfoFilter() { AddrType *p = new AddrType; if (p == NULL) { return NULL; } memset(p, 0, sizeof(AddrType)); //p->ai_flags = AI_CANONNAME; p->ai_family = AF_UNSPEC; p->ai_socktype = SOCK_STREAM; struct addrinfo *res = NULL; int n = getaddrinfo(_host.c_str(), SMTPPORT.c_str(), p, &res); if (n != 0) { const char *str = gai_strerror(n); log(str, strlen(str)); return NULL; } if (!Socket(res)) { freeaddrinfo(res); const char *str = strerror(errno); log(str, strlen(str)); return NULL; } _islogin = true; return p; } bool SMTPMail::Socket(AddrType *res) { while (res != NULL) { _fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (_fd > 0) { if (setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &_reuseaddr, sizeof(_reuseaddr)) == 0) { if (connect(_fd, res->ai_addr, sizeof(struct sockaddr)) == 0){ return true; } } close(_fd); _fd = -1; } res = res->ai_next; } return false; } bool SMTPMail::Login() { auto Check = [] (const String &res, const String &mode) -> void { if (res.substr(0, 3) != mode) { log(res.c_str(), res.length()); } }; Send(String("HELO "+ _username +"\r\n")); Check(Recv(), "250"); Send(String("auth login\r\n")); Check(Recv(), "334"); Send(Base64Encode(_username) + "\r\n"); Check(Recv(), "334"); Send(String(_password + "\r\n")); Check(Recv(), "334"); return true; } void SMTPMail::SendEmail(const String &to, const String &text) { if(false == _islogin) { return; } if (!Login()) { return ; } Send(String("MAIL FROM: <" + _username + ">\r\n")); Recv(); Send(String("RCPT TO: <" + to + ">\r\n")); Recv(); Send(String("DATA\r\n")); Recv(); Send(String("to:" + to + "\r\n" + text + "\r\n.\r\n")); Recv(); Send(String("QUIT\r\n")); Recv(); } void SMTPMail::Send(const String &msg) { if(send(_fd, msg.c_str(), msg.length(), MSG_CONFIRM) < 0) { const char *str = "send error\n"; log(str, strlen(str)); } } SMTPMail::String SMTPMail::Recv() { const size_t buflen = 512; static char buf[buflen]; if (recv(_fd, buf, buflen, 0) < 0) { const char *str = "recv error\n"; log(str, strlen(str)); return String(""); } return String(buf); } }
在这段代码中我只挑一部分函数进行说明,毕竟有的函数很简单,没必要说明:
1.
SMTPMail::AddrType *SMTPMail::InitAddrInfoFilter();这个函数是为了初始化AddrInfo类型的过滤器。因为是动态分配内存,而且是初始化成员,为了能在内存分配失败的时候更加容易的添加异常处理,我讲这部分代码独立出来。
需要注意的是AddrType是我自己typedef出来的一个类型:
typedef struct addrinfo AddrType;struct addrinfo类型在删除的时候需要调用freeaddrinfo函数进行删除。
其实,这个做法还有一个更加明智的代替方法:
bool SMTPMail::Socket(AddrType *res)
使用智能指针。并且为这个智能智能指针定义自己的删除器。
具体做法请自行google
2.
bool SMTPMail::Socket(AddrType *res);
这个函数将尝试连接getaddrinfo返回的所有可用的地址,使用setsockopt函数设置端口可以复用,并连接。
如果成功,则返回true。
相关文章推荐
- linux raid技术
- linux基础整理4
- linux shell命令快捷获得系统帮助(一)[man-pages定义规范]
- LINUX下图形界面切换到文本模式 以及~和/的区别
- linux中memset的正确用法
- 第 三 十 天:Linux 系 统 优 化 扩 展
- linux dev 常见特殊设备介绍与应用(loop,null,zero,full,random)
- linux----定义命令别名
- CentOS安装swig2.0.9
- Linux学习 -- 文件系统管理
- centos6.3(64位) 安装apr
- linux 打造man中文手册图解(man-pages-zh帮助页)
- linux命令login
- linux whatis与whatis database 使用及查询方法(man使用实例)
- linux 查看端口是否被占用
- Windows/Linux获取网卡地址方法
- centos6.5进入救援模式
- yum命令——Linux下只下载不安装
- Linux数据流重定向
- linux、hdfs、hive、hbase经常使用的命令