由于实验室Windows服务器经常由于各种原因导致IP变动,所以就想在IP变动后自动获取新的IP并发邮件告诉我。
PS.由于我们实验室的服务器的校园网只需要连接上就能上网,而且是动态IP,所以就没有在这里写网络登录,或者去网络中心申请固定IP(自己申请太麻烦了,嘿嘿)。
PPS.文章原创,欢迎转载,转载请注明出处。文中贴出了所有代码,想支持下作者可以去对应CSDN下载支持。
PPPS.感谢作者写作过程中参考的文章和资料作者。
最新更新时间:2019/10/10 22:00 by seu_wangpeng
根据经验,这里主要就分三步走了
获取本地IP,判断IP是否变化。
实现发邮件功能。
设置服务器计划任务,在开机,或者定时运行检查。
东西比较简单,就想用最简单的方式,最快完成就好了。
Windows获取IP好像平时用的最多的就是直接cmd
敲出来,用ipconfig
指令咯,就像这样
一般找到 以太网适配器 下面的 IPv4就是我们要的了。
然后C/C++
程序里获取这个保存为文件可以使用重定向符 >
和 system()
就可以保存查询的结果到ipInfo.txt
文件了。
system("ipconfig >ipInfo.txt")
获取到的IP数据信息有时候就有点多,而我只想关注以太网IP,或者无线网IP等信息,注意到这里的输出信息是一节一节输出的,而且每一节有一个标题,标题每行置顶,而每节内部要么是空行,要么是有空格开头。根据这些特点,这里我就写了一个处理数据的类,算是一点美化(我不会说这是强迫症)。
代码也贴出来,东西不多就不注释了:
/*
* readconfig.hpp --- read result of "ipconfig"
* @How to : using system("ipconfig >ipInfo.txt") to get ip info file first.
* @author : wangpeng([email protected])
* @version : v1.0 2019/10/10
*/
#pragma once
#include
#include
#include
#include
#include
using namespace std;
class ReadConfig
{
struct Section
{
string header;
vector contents;
};
public:
ReadConfig(std::string path)
{
std::ifstream fin(path.c_str());
string line;
bool secFlag=false;
Section section;
while(getline(fin,line))
{
if(line[0]!=' ' && line[0]!='\t' && line!="")
{
if(secFlag)
{
sections.push_back(section);
}
secFlag=true;
section.header=line;
section.contents.clear();
section.contents.push_back(line);
}
else
{
if(secFlag)
{
section.contents.push_back(line);
}
}
content+=(line+"\n");
}
sections.push_back(section);
fin.close();
}
~ReadConfig()
{
}
std::string getSection(std::string sec)
{
std::string result="";
for(int i=0;i sections;
};
调用示例:
ReadConfig infofile("ipInfo.txt");
std::string ip_now = infofile.getSection("以太网适配器 以太网");//获取节信息
std::string contents = cfgfile.getContent();//获取完整文件内容
效果嘛
舒服了~
然后就可以和上次保存下来的结果对比来判断网络状况有没有发生变化啦~~~
使用C++发邮件,我查了下,用SMTP
就可以了。
协议内容好像很简单,搜了两篇文章介绍的
SMTP协议介绍_heart威名海的博客-CSDN博客_smtp协议是指
SMTP协议详解及工作过程_kerry0071的专栏-CSDN博客_smtp协议
常用指令大概就是
SMTP命令不区分大小写,但参数区分大小写。常用命令如下:
HELO ——向服务器标识用户身份发送者能欺骗、说谎,但一般情况下服务器都能检测到
RCPT TO: ——用来标志邮件接收者的地址,常用在MAIL FROM后,可以有多个RCPT TO
DATA ——将之后的数据作为数据发送,以.标志数据的结尾
REST ——重置会话,当前传输被取消
NOOP ——要求服务器返回OK应答,一般用作测试
QUIT ——结束会话
VRFY ——验证指定的邮箱是否存在,由于安全方面的原因,服务器大多禁止此命令
EXPN ——验证给定的邮箱列表是否存在,由于安全方面的原因,服务器大多禁止此命令
HELP ——查询服务器支持什么命令
直接偷两张图
过程敲敲代码试试就能感觉差不多。
嘿嘿,敲代码!我干脆写了个简单的类
/*
* smtp.hpp --- send mails in windows
* @author : wangpeng([email protected])
* @version : v1.0 2019/10/10
*/
#pragma once
#include
#include
#include
#include
#include
#include
#include
#pragma comment(lib, "ws2_32.lib")
#define SMTP_BUFSIZE 10240
class SMTP
{
public:
SMTP(std::string account, std::string passwd, std::string to, std::string nickname="",int port = 25)
{
m_account = account;
m_password = passwd;
m_toList.push_back(to);
if (nickname != "")
{
m_nickname = nickname;
}
else
{
m_nickname = account;
}
m_port = port;
m_smtpSvr = account.substr(account.find('@') + 1);
}
SMTP(std::string account, std::string passwd, std::vector toList, std::string nickname = "", int port = 25)
{
m_account = account;
m_password = passwd;
m_toList = toList;
if (nickname != "")
{
m_nickname = nickname;
}
else
{
m_nickname = account;
}
m_port = port;
m_smtpSvr = account.substr(account.find('@') + 1);
}
static std::vector AddRecByFile(std::string path)
{
std::ifstream in(path);
std::string tmp;
std::vector toList;
while (in >> tmp)
{
toList.push_back(tmp);
}
return toList;
}
void AddRecipient(std::vector to)
{
m_toList = to;
}
void AddRecipient(std::string to)
{
m_toList.push_back(to);
}
void CleanRecipient()
{
m_toList.clear();
}
int SendMail(std::vector& toList, std::string &subject, std::string &content)
{
return SendMail(m_nickname, m_account, m_password, m_port, toList, subject, content, m_smtpSvr);
}
int SendMail(std::string to, std::string &subject, std::string &content)
{
std::vector toList;
toList.push_back(to);
return SendMail(m_nickname, m_account, m_password, m_port, toList, subject, content, m_smtpSvr);
}
int SendMail(std::string &subject, std::string &content)
{
return SendMail(m_nickname, m_account, m_password, m_port, m_toList, subject, content, m_smtpSvr);
}
int SendMail(std::string &nickname,std::string &from,std::string &passwd,int port,std::vector& toList,std::string &subject, std::string &content,std::string smtpSvr="qq.com")
{
char buf[SMTP_BUFSIZE] = { 0 };
std::string buf_str;
// 连接邮件服务器
WSADATA WSAData;
WSAStartup(MAKEWORD(2, 2), &WSAData);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
hostent* phost = gethostbyname(std::string("smtp." + smtpSvr).c_str());
memcpy(&addr.sin_addr.S_un.S_addr, phost->h_addr_list[0], phost->h_length);
SOCKET sockfd = socket(PF_INET, SOCK_STREAM, 0);
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
std::cerr<<"smtp socket() error";
closesocket(sockfd);
WSACleanup();
return 1;
}
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)
{
std::cerr << "smtp connect() error" ;
closesocket(sockfd);
WSACleanup();
return 2;
}
// EHLO
char pcname[128] = { 0 };
DWORD size = 128;
GetComputerName(pcname, &size); // 获取本地计算机名
buf_str = "EHLO " + std::string(pcname) + "\r\n";
send(sockfd, buf_str.c_str(), buf_str.size(), 0);
recv(sockfd, buf, SMTP_BUFSIZE, 0);
// AUTH LOGIN
buf_str = "AUTH LOGIN\r\n";
send(sockfd, buf_str.c_str(), buf_str.size(), 0);
recv(sockfd, buf, SMTP_BUFSIZE, 0);
// 邮箱账号
std::string account;
EncodeBase64(from, account);
buf_str = account + "\r\n";
send(sockfd, buf_str.c_str(), buf_str.size(), 0);
recv(sockfd, buf, SMTP_BUFSIZE, 0);
// 登录密码
std::string password;
EncodeBase64(passwd, password);
buf_str = password + "\r\n";
send(sockfd, buf_str.c_str(), buf_str.size(), 0);
recv(sockfd, buf, SMTP_BUFSIZE, 0);
// MAIL FROM
buf_str = "MAIL FROM:<"+ from + ">\r\n";
send(sockfd, buf_str.c_str(), buf_str.size(), 0);
recv(sockfd, buf, SMTP_BUFSIZE, 0);
// RCPT TO
std::string to;
for (int i = 0; i < toList.size(); i++)
{
buf_str = "RCPT TO:<" + toList[i] + ">\r\n";
send(sockfd, buf_str.c_str(), buf_str.size(), 0);
recv(sockfd, buf, SMTP_BUFSIZE, 0);
to += (toList[i] + " ");
}
// DATA
buf_str = "DATA\r\n";
send(sockfd, buf_str.c_str(), buf_str.size(), 0);
recv(sockfd, buf, SMTP_BUFSIZE, 0);
// 内容
buf_str = "From: \"" + nickname + "\"<" + from + ">\r\n"
"To: \"user\"<" + to + ">\r\n"
"Subject: " + subject + "\r\n\r\n" + content + "\r\n.\r\n";
send(sockfd, buf_str.c_str(), buf_str.size(), 0);
recv(sockfd, buf, SMTP_BUFSIZE, 0);
// QUIT
buf_str = "QUIT\r\n";
send(sockfd, buf_str.c_str(), buf_str.size(), 0);
recv(sockfd, buf, SMTP_BUFSIZE, 0);
if (strlen(buf) >= 3)
{
if (buf[0] == '2' && buf[1] == '5' && buf[2] == '0')
{
printf("sucess\n");
}
else
{
printf("failed\n");
}
}
closesocket(sockfd);
WSACleanup();
return 0;
}
private:
void EncodeBase64(std::string src, std::string& encode)
{
char base64_table[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '=' };
int len = src.length();
encode = "";
int i = 0;
for (i = 0; i < len / 3; i++)
{
int temp = byte(src[3 * i + 2]) + (byte(src[3 * i + 1]) << 8) + (byte(src[3 * i]) << 16);
encode.push_back(base64_table[(temp & 0xfc0000) >> 18]);
encode.push_back(base64_table[(temp & 0x3f000) >> 12]);
encode.push_back(base64_table[(temp & 0xfc0) >> 6]);
encode.push_back(base64_table[temp & 0x3f]);
}
if (0 == len % 3)
{
encode.push_back(0);
}
else if (1 == len % 3)
{
int temp = byte(src[3 * i]) << 16;
encode.push_back(base64_table[(temp & 0xfc0000) >> 18]);
encode.push_back(base64_table[(temp & 0x3f000) >> 12]);
encode.push_back(base64_table[64]);
encode.push_back(base64_table[64]);
encode.push_back(0);
}
else if (2 == len % 3)
{
int temp = (byte(src[3 * i + 1]) << 8) + (byte(src[3 * i]) << 16);
encode.push_back(base64_table[(temp & 0xfc0000) >> 18]);
encode.push_back(base64_table[(temp & 0x3f000) >> 12]);
encode.push_back(base64_table[(temp & 0xfc0) >> 6]);
encode.push_back(base64_table[64]);
encode.push_back(0);
}
}
private:
std::string m_account;
std::string m_password;
std::string m_nickname;
std::string m_smtpSvr;
int m_port;
std::vector m_toList;
std::string content;
};
需要的模块都准备好了,就可以测试下了~
我的测试代码(隐去了我的密码):
/*
* 服务器自动获取IP信息并发送到对应邮箱
* @author : wangpeng([email protected])
* @version : v1.0 2019/10/10
*/
#include
#include "readconfig.hpp"
#include "smtp.hpp"
using namespace std;
// account info ,you can replace by reading file
std::string mail_account = "***@****";
std::string mail_password = "你的密码:授权码";
std::string mail_nickname = "seu_lab228_server";
int mail_port = 25;
std::string mail_ipUpToDate = "ipLast.txt";
std::string mail_sendto = "sendto.txt";
std::string mail_subject = "IP has changed";
int main()
{
std::string cmd = "ipconfig >ipInfo.txt";
system(cmd.c_str());
ReadConfig infofile("ipInfo.txt");
std::string ip_now = infofile.getSection("以太网适配器 以太网");
//std::string contents = cfgfile.getContent();
std::ifstream in(mail_ipUpToDate);
std::ostringstream tmp;
tmp << in.rdbuf();
std::string ip_last = tmp.str();
in.close();
if (ip_last != ip_now)
{
std::vector tolist = SMTP::AddRecByFile(mail_sendto);
if (tolist.size() > 0)
{
SMTP mail(mail_account, mail_password, tolist, mail_nickname, mail_port);
mail.SendMail(mail_subject, ip_now);
}
ofstream out(mail_ipUpToDate);
out << ip_now;
out.close();
}
//system("pause");//test
return 0;
}
该代码运行的时候,注意需要准备好接收人的邮件的txt文件,文件里面每个人写一行就行了。
嘿嘿,可以顺利收到呢~~
需要注意的是:邮箱需要先设置开启SMTP/pop3功能,然后呢,登录的密码不是邮箱的密码,而是给你的授权码!具体操作自行百度。
他已经是个成熟的程序了,是时候让他自己跑起来了!
在windows端,一般的操作就是设置任务计划程序,让他按照你的要求,想啥时候跑,跑多久都行。
设置计划任务参考
参考已经很详细了,就不多说。需要注意一点就是:
在操作页
设置启动程序
的时候,如果有文件,地址的操作(比如我这里要保存文件到工作路径),一定把起始于
这个可选的项写同一路径。
整体难度不大,然后也是一时兴起(气愤),所以就研究并实践了下,代码软件都测试过没有问题。
整体项目工程传上来了可以参考:
smtpmail.zip-C++代码类资源-CSDN下载