基于SMTP协议和POP3协议实现的邮件收发客户端
一、概要设计
1.1 抽象数据类型定义
主要定义了三个抽象数据类型:
Base64
- 功能:用于发送邮件时进行编码,以及接收邮件时进行解码
- 数据部分:无
- 操作部分:编码(encode)、解码(decode)
SMTP
- 功能:简单邮件传输协议类。用于实现SMTP协议中各种命令调用,发送邮件
- 数据部分:套接字
- 操作部分:创建套接字、释放套接字、连接SMTP服务器、状态码检测、发送数据
POP3
- 功能:实现POP3协议中各种命令调用,接收邮件
- 数据部分:套接字、邮件类属性(包括邮件大小、主题、发送方等信息)
- 操作部分:创建套接字、释放套接字、用户名密码检测、POP3协议中相关操作命令(包括STAT、LIST、TOP、NOOP、RETR、QUIT等)
1.2 主程序流程图
1.3 各个模块关系
按功能上可以从总体上大致分为登陆模块、发送模块、接收模块和显示模块,其所对应的模块层次结构如下图所示:
1.4 技术开发思路
1.4.1 明确设计目标和要求
关于要求:编程实现通过用户界面,用户登录信箱认证过程(含base64方式编码)、发送信息及附件(常用格式)、邮件信息验证、伪造邮件地址黑名单。
其实前三点都是比较正常的功能需求,但是对于第四点,实在是难以理解,为什么发送器会有黑白名单?但是既然要求,那就做吧,按我个人的理解是这样的:显然黑白名单的功能不是发送器的,而是接收器的。虽然题目清清楚楚写着发送器设计,但在功能上却要求实现接收器的功能。这意味着除了使用SMTP协议发送邮件外,还需设计使用POP3协议接收邮件,在接收的时候采用黑白名单过滤的功能。
1.4.2 开发环境选择
采用VC6.0,使用MFC框架进行开发。
1.4.3 开发流程
准备先进行发送模块和登陆模块以及部分显示模块的开发,完成之后进行接收模块的开发,并最终完善显示模块。
二、详细设计
抽象数据类型的实现
2.1 Base64类
包括两个成员函数:
Encode
- 参数:包括待编码的字符串、该字符串长度
- 返回值:编码后的字符串
- 实际定义:string Base64::Encode(const unsigned char *str, int length)
- 具体实现(伪代码):
Dim EncodeTable[0…63] as char Dim strEncode as string For i=0 to length/3 strEncode+=EncodeTable[str[i]>>2] strEncode+=EncodeTable[((str[i]<<4)|(str[i+1]>>4))&0x3F] strEncode+=EncodeTable[((str[i+1]<<2)|(str[i+2]>>6))&0x3F] strEncode+=EncodeTable[str[i+2]&0x3F] EndFor
Decode
- 参数:包括待解码的字符串、该字符串长度、解码后的字符串长度
- 返回值:解码后的字符串
- 实际定义:string Base64::Decode(const char *str, int length, int &outlength)
- 具体实现(伪代码):
Dim DecodeTabe[] as char Dim strDecode as string Dim val as int While i<length Val=DecodeTable[str[i]]<<18 Val+=DecodeTable[str[i+1]]<<12 strDecode+=(val&0x00FF00000)>>16 outlength++ if str[i+2]!=’=’ then val+=DecodeTable[str[i+2]|<<6 strDecode+=(val&0x0000FF00)>>8 outlength++ if str[i+3]!=’=’ then val+=DecodeTable[str[i+3]] str+=(val&0x000000FF) outlength++ end if end if i+=4
2.2 SMTP类
2.2.1 数据定义
SOCKET m_socket; //套接字
WSADATA m_wsadata; //存放SOCKET的初始化信息
HOSTENT* m_hostent; //存放主机以及地址
SOCKADDR_IN m_sockaddr_in; //存放地址协议、ip、端口
2.2.2 函数
bool SMTP::CheckResponse(const char *code) //检查返回的状态码与期望结果code是否一致
bool SMTP::Connect(const string addr, const int port) //连接到服务器,参数为服务器名称以及端口
bool SMTP::CreateSocket() //创建套接字
void SMTP::ReleaseSocket() //释放套接字
bool SMTP::isvalid(const string username, const string password)//验证用户名和密码是否正确
然后是发送函数:
bool Send(const string sendaddr, //发送方邮箱
const vector<string> rcvlist, //接收方地址
const string sendname, //发送方姓名
const string rcvname, //接收方姓名
const string subject, //主题
const string content,//内容
const vector<string> file, //文件
int ishtml); //是否以html格式发送
2.2.3 流程图以及关键技术伪代码
发送函数send
连接服务器Connect
** 检查账户信息isvalid**
发送函数伪代码:
Str=”MAIL FROM:”+”发送地址”
If send(str)==TRUE then
If CheckResponse(“250”) then
Str=”RCPT TO”+”接收方地址”
If Send(str)==TRUE then
If CheckResponse(“250”) then
Str=”DATA”
If Send(str)==TRUE then
If CheckResponse(“354”) then
Str=”邮件信息”+”附件”
If Send(str)==TRUE then
If CheckResponse(“250”) then
ReleaseSocket()
Return TRUE
End if
End if
End if
End if
End if
End if
End if
End if
ReleaseSocket()
Return FALSE
2.3 POP3类
2.3.1 数据定义
SOCKET m_socket; //套接字
WSADATA m_wsadata; //存放SOCKET的初始化信息
HOSTENT* m_hostent; //存放主机以及地址
SOCKADDR_IN m_sockaddr_in; //存放地址协议、ip、端口
unsigned int mailnum,mailsize; //邮件总数、总大小
CString m_response; //存放响应信息
CStringArray m_subject; //CString数组存放各个邮件主题
CStringArray m_size; //存放各个邮件大小
CStringArray m_sender; //存放发送方
CStringArray m_date; //存放发送时间
2.3.2 主要函数
bool CheckResponse(bool bDouble); //检查响应,参数用于识别响应末尾是一个回车换行还是2个
void ReleaseSocket();//释放套接字
bool CreateSocket();//创建套接字
bool isvalid(const string username,const string password);//用户名密码检测
bool Connect(const string addr,const int port);//连接POP服务器
bool STAT(); //STAT命令,获取邮件总数及总大小
bool RETR(UINT nIndex,CString &strMsg);//获取序号为nIndex的邮件内容存放于strMsg中
bool Noop(); //NOOP命令,确认连接
bool TOP(); //TOP命令,返回邮件头信息
bool LIST(); //列出邮件序号及大小
bool QUIT(); //断开连接
2.3.3 流程图及关键函数伪代码
*接收响应(CheckResponse)
**LIST函数 **
TOP函数
接收响应函数源码:
```c++ TCHAR pChar[100005]; CString strTemp; // 读取回应信息 BOOL bEnd = FALSE; UINT nReceived = 0; DWORD dwStart = ::GetTickCount(); while (!bEnd) { // 尝试时间到 if ((::GetTickCount() - dwStart) > 2000) { pChar[nReceived] = '\0'; // 保存当前回应的消息 m_response = pChar; AfxMessageBox("接收响应消息超时"); return FALSE; }
// 看套接字是否可读
timeval timeout = {0, 0};
fd_set fds;
FD_ZER