基于SMTP协议和POP3协议实现的邮件收发客户端

Barefoot

发布日期: 2019-02-09 17:07:18 浏览量: 2174
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

一、概要设计

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)
    • 具体实现(伪代码):
      1. Dim EncodeTable[063] as char
      2. Dim strEncode as string
      3. For i=0 to length/3
      4. strEncode+=EncodeTable[str[i]>>2]
      5. strEncode+=EncodeTable[((str[i]<<4)|(str[i+1]>>4))&0x3F]
      6. strEncode+=EncodeTable[((str[i+1]<<2)|(str[i+2]>>6))&0x3F]
      7. strEncode+=EncodeTable[str[i+2]&0x3F]
      8. EndFor
  • Decode

    • 参数:包括待解码的字符串、该字符串长度、解码后的字符串长度
    • 返回值:解码后的字符串
    • 实际定义:string Base64::Decode(const char *str, int length, int &outlength)
    • 具体实现(伪代码):
      1. Dim DecodeTabe[] as char
      2. Dim strDecode as string
      3. Dim val as int
      4. While i<length
      5. Val=DecodeTable[str[i]]<<18
      6. Val+=DecodeTable[str[i+1]]<<12
      7. strDecode+=(val&0x00FF00000)>>16
      8. outlength++
      9. if str[i+2]!=’=’ then
      10. val+=DecodeTable[str[i+2]|<<6
      11. strDecode+=(val&0x0000FF00)>>8
      12. outlength++
      13. if str[i+3]!=’=’ then
      14. val+=DecodeTable[str[i+3]]
      15. str+=(val&0x000000FF)
      16. outlength++
      17. end if
      18. end if
      19. i+=4

2.2 SMTP类

2.2.1 数据定义

  1. SOCKET m_socket; //套接字
  2. WSADATA m_wsadata; //存放SOCKET的初始化信息
  3. HOSTENT* m_hostent; //存放主机以及地址
  4. SOCKADDR_IN m_sockaddr_in; //存放地址协议、ip、端口

2.2.2 函数

  1. bool SMTP::CheckResponse(const char *code) //检查返回的状态码与期望结果code是否一致
  2. bool SMTP::Connect(const string addr, const int port) //连接到服务器,参数为服务器名称以及端口
  3. bool SMTP::CreateSocket() //创建套接字
  4. void SMTP::ReleaseSocket() //释放套接字
  5. bool SMTP::isvalid(const string username, const string password)//验证用户名和密码是否正确
  6. 然后是发送函数:
  7. bool Send(const string sendaddr, //发送方邮箱
  8. const vector<string> rcvlist, //接收方地址
  9. const string sendname, //发送方姓名
  10. const string rcvname, //接收方姓名
  11. const string subject, //主题
  12. const string content,//内容
  13. const vector<string> file, //文件
  14. int ishtml); //是否以html格式发送

2.2.3 流程图以及关键技术伪代码

发送函数send

连接服务器Connect

检查账户信息isvalid

发送函数伪代码:

  1. Str=”MAIL FROM:”+”发送地址”
  2. If send(str)==TRUE then
  3. If CheckResponse(“250”) then
  4. Str=”RCPT TO”+”接收方地址”
  5. If Send(str)==TRUE then
  6. If CheckResponse(“250”) then
  7. Str=”DATA
  8. If Send(str)==TRUE then
  9. If CheckResponse(“354”) then
  10. Str=”邮件信息”+”附件”
  11. If Send(str)==TRUE then
  12. If CheckResponse(“250”) then
  13. ReleaseSocket()
  14. Return TRUE
  15. End if
  16. End if
  17. End if
  18. End if
  19. End if
  20. End if
  21. End if
  22. End if
  23. ReleaseSocket()
  24. Return FALSE

2.3 POP3类

2.3.1 数据定义

  1. SOCKET m_socket; //套接字
  2. WSADATA m_wsadata; //存放SOCKET的初始化信息
  3. HOSTENT* m_hostent; //存放主机以及地址
  4. SOCKADDR_IN m_sockaddr_in; //存放地址协议、ip、端口
  5. unsigned int mailnum,mailsize; //邮件总数、总大小
  6. CString m_response; //存放响应信息
  7. CStringArray m_subject; //CString数组存放各个邮件主题
  8. CStringArray m_size; //存放各个邮件大小
  9. CStringArray m_sender; //存放发送方
  10. CStringArray m_date; //存放发送时间

2.3.2 主要函数

  1. bool CheckResponse(bool bDouble); //检查响应,参数用于识别响应末尾是一个回车换行还是2个
  2. void ReleaseSocket();//释放套接字
  3. bool CreateSocket();//创建套接字
  4. bool isvalid(const string username,const string password);//用户名密码检测
  5. bool Connect(const string addr,const int port);//连接POP服务器
  6. bool STAT(); //STAT命令,获取邮件总数及总大小
  7. bool RETR(UINT nIndex,CString &strMsg);//获取序号为nIndex的邮件内容存放于strMsg中
  8. bool Noop(); //NOOP命令,确认连接
  9. bool TOP(); //TOP命令,返回邮件头信息
  10. bool LIST(); //列出邮件序号及大小
  11. bool QUIT(); //断开连接

2.3.3 流程图及关键函数伪代码

*接收响应(CheckResponse)

LIST函数

TOP函数

接收响应函数源码:

  1. TCHAR pChar[100005];
  2. CString strTemp;
  3. // 读取回应信息
  4. BOOL bEnd = FALSE;
  5. UINT nReceived = 0;
  6. DWORD dwStart = ::GetTickCount();
  7. while (!bEnd)
  8. {
  9. // 尝试时间到
  10. if ((::GetTickCount() - dwStart) > 2000)
  11. {
  12. pChar[nReceived] = '\0';
  13. // 保存当前回应的消息
  14. m_response = pChar;
  15. AfxMessageBox("接收响应消息超时");
  16. return FALSE;
  17. }
  18. // 看套接字是否可读
  19. timeval timeout = {0, 0};
  20. fd_set fds;
  21. FD_ZERO(&fds);
  22. FD_SET(m_socket,&fds);
  23. Int nStatus = select(0,&fds,NULL,NULL,&timeout);
  24. if (nStatus == SOCKET_ERROR)
  25. {
  26. // 套接字不可读
  27. AfxMessageBox("套接字不可达");
  28. return FALSE;
  29. }
  30. else if (!nStatus)
  31. {
  32. // 没有接收到数据
  33. ::Sleep(688);
  34. continue;
  35. }
  36. // 从套接字中接收数据
  37. ::Sleep(288);
  38. nStatus=recv(m_socket,pChar + nReceived,sizeof(pChar),0);
  39. if (nStatus == SOCKET_ERROR)
  40. {
  41. pChar[nReceived] = '\0';
  42. // 套接字错误
  43. // 保存当前回应信息
  44. m_response = pChar;
  45. AfxMessageBox("未能从套接字中收到数据");
  46. return FALSE;
  47. }
  48. else if (nStatus)
  49. {
  50. // 重置计时器
  51. dwStart = ::GetTickCount();
  52. // 已收到的数据又增加了
  53. nReceived += nStatus;
  54. }
  55. // 将pChar设为字符串,并赋给CString型的变量
  56. pChar[nReceived] = '\0';
  57. strTemp = pChar;
  58. // 检查是否收到了结束标志
  59. LPCTSTR lpszComp = bDouble ? "\r\n.\r\n" : "\r\n";
  60. bEnd = (strTemp.Find(lpszComp) != -1);
  61. }
  62. // 去掉结束标志
  63. nReceived -= bDouble ? 3 : 0;
  64. pChar[nReceived] = '\0';
  65. // 检查回应信息是否有效
  66. strTemp = pChar;
  67. strTemp.MakeUpper();
  68. int nStart = strTemp.Find("+OK");
  69. if (nStart == -1)
  70. {
  71. // 收到无效信息
  72. AfxMessageBox("回应消息无效");
  73. return FALSE;
  74. }
  75. // 提取有效信息
  76. strTemp = pChar;
  77. m_response = strTemp.Right(strTemp.GetLength() - nStart - 3);

三、调试分析

3.1 遇到的问题及解决办法

  • 问题一:一开始使用的是126邮箱进行测试,没有问题。到后面换成了QQ邮箱,发现无法连接服务器。原因是QQ的POP服务器名不是以POP3开始的,而是POP,即:pop.qq.com,而126和163的都是以pop3开头。

    • 解决办法:对不同的服务器采用不同的处理方式。
  • 问题二:测试邮箱事先没有开启SMTP/POP服务,导致登陆失败。

    • 解决办法:开启SMTP/POP服务。
  • 问题三:发送大附件的问题。

    • 解决办法:采取分段发送。开设一定的缓冲区,缓存分段,一个附件分为多个段经多次发送。
  • 问题四:部分邮件的主题是经过base64加密的。一开始没有考虑,导致部分邮件主题没有得到解析。如下图所示。

    • 解决办法:根据字段”=?GBK?B?”和”=?UTF-8?B”识别邮件主题是否被加密,对加密的邮件主题进行base64解密。

  • 问题五:部分邮件内容是html格式的,不能直接解码显示。

    • 解决办法:若发现Content-type字段值为text/html,则直接显示其html文本。(或许应该在网页中打开??)
  • 问题六:一开始在pop3检查响应时,采用和SMTP检查响应一样的函数进行处理。导致无法识别接收响应消息和邮件。其原因在于:对于长文本内容可能会分段发送并且具有延时现象。

    • 解决办法:这个问题比较麻烦。找了很多资料,最终采用了计时处理的方式。代码已经在前面列出,可以参考。虽然接收速度比较慢(需等待),但是响应信息的准确性得到了保证。
  • 问题七:黑白名单。

    • 解决办法:设定两个数组分别存放当前用户的黑白名单成员,同时使用文件操作维护之。当用户登录的时候,检查相关目录中是否存在该用户信息,若存在,则加载该用户黑白名单到指定数组中。当用户接收邮件时,检查黑名单,看是否存在当前邮件发送者,若不存在,则接收邮件,否则不接收。

3.2 尚存在的问题和改进设想

时间紧迫,个人能力有限,尚存如下问题,待解决。(实际上,均是和邮件接收有关的问题,好像跟题目没什么关系。)

  • 问题一:对于html格式的邮件内容无法解析

    • 改进设想:将获取的内容存放到一个html文件中,然后通过调用打开浏览器在浏览器中查看该邮件内容。
  • 问题二:无法接收附件。

    • 改进设想:这个,我想需要研究一下各种文件的格式。
  • 问题三:邮件编码问题。不同服务器编码方式不一样,无法用同一种解码方式对邮件内容进行解码显示。

    • 改进设想:研究编码方式,对不同编码的邮件,分别解码处理。
  • 问题四:当账号中邮件数量较多时,接收缓慢。这个问题主要是由于上面提到的检查响应的函数造成的。

    • 改进设想:寻找更好的检查响应算法。

3.3 经验和体会

经过这次计网课设,对SMTP协议和POP3协议有了更加深刻的理解,在实践中碰到了许多问题,通过不断解决这些问题,使得自己在编程能力、理解能力、学习能力等各方面能力都得到了有效的锻炼。虽然时间很紧迫,进行课设的同时还得上课,课余时间基本上都花在了课设上,经过努力,完成了所有的基本要求,感觉很开心。但还是有一些问题没有得到解决,希望能够在今后的学习中将这些问题解决掉。

四、操作说明

  • 登陆:输入账号密码,在下拉列表中选择对应服务器,点击登陆邮箱按钮即可

  • 发送邮件:输入发送方姓名(可省)、接收方地址(发送到),主题(可选),附件(可选),填写邮件内容。确认无误之后,点击发送邮件

  • 添加附件:点击添加按钮,弹出对话框,选择需要添加的文件(可以多选,最多20个),确定之后返回主界面,在附件列表中可以查看到添加的附件名称

  • 删除附件:点击下拉列表,选中需要删除的附件名称之后,点击删除按钮,即可删除当前选择的附件

  • 接收邮件:需要先进行登陆才可。登陆之后,点击列出邮件,如果邮箱中邮件数目较多,请耐心等待。(中途可能有些邮件无法正确接收,会弹出提示框,点击确定即可)一段时间后,邮件列表中将显示出成功接收到的邮件信息

  • 设置黑白名单:点击设置黑/白名单,弹出设置框,输入账号,点击添加,完成添加。选择列表中已经添加的账号,然后点击删除,可将账号信息从名单中删除

上传的附件 cloud_download 基于SMTP协议和POP3协议的邮件收发客户端.7z ( 542.59kb, 55次下载 )
error_outline 下载需要13点积分

keyboard_arrow_left上一篇 : 基于JavaWeb和MySQL的网上书店的设计与实现 基于JSP和MySQL的宠物网站设计与实现 : 下一篇keyboard_arrow_right



Barefoot
2019-02-09 17:07:02
基于VC6.0开发,使用SMTP协议和POP3协议实现的邮件收发客户端
薛定饿了吗
2020-07-05 09:33:34
为什么运行就报错啊
aasdad
2021-01-12 01:27:09
怎么使用有人知道吗

发送私信

任何值得做的,就把它做好

10
文章数
21
评论数
最近文章
eject