# 基于SMTP协议和POP3协议实现的邮件收发客户端
# 一、概要设计
## 1.1 抽象数据类型定义
主要定义了三个抽象数据类型:
- Base64
- 功能:用于发送邮件时进行编码,以及接收邮件时进行解码
- 数据部分:无
- 操作部分:编码(encode)、解码(decode)
- SMTP
- 功能:简单邮件传输协议类。用于实现SMTP协议中各种命令调用,发送邮件
- 数据部分:套接字
- 操作部分:创建套接字、释放套接字、连接SMTP服务器、状态码检测、发送数据
- POP3
- 功能:实现POP3协议中各种命令调用,接收邮件
- 数据部分:套接字、邮件类属性(包括邮件大小、主题、发送方等信息)
- 操作部分:创建套接字、释放套接字、用户名密码检测、POP3协议中相关操作命令(包括STAT、LIST、TOP、NOOP、RETR、QUIT等)
## 1.2 主程序流程图
![](http://www.writebug.com/myres/static/uploads/2021/10/19/7dbf34761a12cd13fb7d62f07df265d3.writebug)
## 1.3 各个模块关系
按功能上可以从总体上大致分为登陆模块、发送模块、接收模块和显示模块,其所对应的模块层次结构如下图所示:
![](http://www.writebug.com/myres/static/uploads/2021/10/19/ff4adcb1e72df2cc4dca1b63fb4e4dcd.writebug)
## 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)
- 具体实现(伪代码):
```pascal
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)
- 具体实现(伪代码):
```pascal
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 数据定义
```c++
SOCKET m_socket; //套接字
WSADATA m_wsadata; //存放SOCKET的初始化信息
HOSTENT* m_hostent; //存放主机以及地址
SOCKADDR_IN m_sockaddr_in; //存放地址协议、ip、端口
```
### 2.2.2 函数
```c++
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**
![](http://www.writebug.com/myres/static/uploads/2021/10/19/782c851fe18b85f162ac42bc390205b1.writebug)
**连接服务器Connect**
![](http://www.writebug.com/myres/static/uploads/2021/10/19/f44513415e1e00af87dec4d5aa3e8325.writebug)
** 检查账户信息isvalid**
![](http://www.writebug.com/myres/static/uploads/2021/10/19/a4da8a1220d31076b1c2408bbfbdebe7.writebug)
发送函数伪代码:
```pascal
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 数据定义
```c++
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 主要函数
```c++
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)**
![](http://www.writebug.com/myres/static/uploads/2021/10/19/0aaec3ad3f5f242c4899a133dab53bbc.writebug)
**LIST函数 **
![](http://www.writebug.com/myres/static/uploads/2021/10/19/50aa5c53b405cc4ae5d42b784db79e1a.writebug)
**TOP函数**
![](http://www.writebug.com/myres/static/uploads/2021/10/19/6b7e0ef6d53a7c08a6c6e0f81ce77315.writebug)
接收响应函数源码:
```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