为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

Sender邮件发送器

2017-11-27 50页 doc 205KB 34阅读

用户头像

is_713593

暂无简介

举报
Sender邮件发送器Sender邮件发送器 Mail Sender邮件发送器 班 级:电气14 指导教师:罗建军 目录 需求分析„„„„„„„„„„„„„„„„„„„„„„ 2 (A)开发背景„„„„„„„„„„„„„„„„„„ 2 (B)应用对象„„„„„„„„„„„„„„„„„„ 2 (C)项目目标„„„„„„„„„„„„„„„„„„ 2 (D)运行环境„„„„„„„„„„„„„„„„„„ 2 技术路线„„„„„„„„„„„„„„„„„„„„„„ 2 (A)开发环境„„„„„„„„„„„„„„„„„2 (B)总体设计„„„„„„„„...
Sender邮件发送器
Sender邮件发送器 Mail Sender邮件发送器 班 级:电气14 指导教师:罗建军 目录 需求分析„„„„„„„„„„„„„„„„„„„„„„ 2 (A)开发背景„„„„„„„„„„„„„„„„„„ 2 (B)应用对象„„„„„„„„„„„„„„„„„„ 2 (C)项目目标„„„„„„„„„„„„„„„„„„ 2 (D)运行环境„„„„„„„„„„„„„„„„„„ 2 技术路线„„„„„„„„„„„„„„„„„„„„„„ 2 (A)开发环境„„„„„„„„„„„„„„„„„2 (B)总体设计„„„„„„„„„„„„„„„„„„2 (C)详细设计„„„„„„„„„„„„„„„„„„ 3 总效果图„„„„„„„„„„„„„„„„„„„„„„„„ 30 个人„„„„„„„„„„„„„„„„„„„„„„ 33 1 一、需求分析 (A)开发背景:现代社会是互联网大发展的时代,网络邮件发展更是迅猛,我们编写一个邮件发送程序,对于以后方便的发送电子邮件有很大的好处,从编写这个程序了解电子邮件的工作原理,有很大的帮助。这里我运用smtp来发送电子邮件。SMTP(简单邮件传输协议)协议是一种邮件发送协议,目前全世界几乎所有的邮件服务器都支持SMTP协议,可以说没有SMTP协议电子邮件寸步难行。 (B)应用对象: 这个软件对于那些经常发送电子邮件的朋友,有很大的方便,可以大大的提高他们的工作效率。 (C)项目目标:实现带有附件的邮件发送功能,并有地址薄的功能。 (D) 运行环境:此软件可在Windows 2000/XP下正常运行,现行的一般微机都可满足其要求。 二、技术路线 (A)开发环境: 操作系统:Windows XP 开发软件:Visual C++,Microsoft Access (B)总体设计: SMTP(简单邮件传输协议)协议是一种邮件发送协议,他的目标是可靠、高效的传送邮件,他独立于传送子系统,而且仅要求一条可以保证传送数据单元顺序的通道。SMTP协议的一个重要特点是它能够在传送中接力传送邮件。也就是说,如果该邮件服务器知道目的地址的邮件服务器则直接发送;如果不知道目的邮件服务器,则将这封信件按照某种策略转发给就近的一个邮件服务器。通过这种接力式的传送方式,完成邮件的发送。 SMTP的通信过程:针对用户的邮件请求,在发送SMTP和接收SMTP之间建立一个双向通道。接收SMTP可以使最终的接收者也可以是中间的传送者。SMTP命令由发送SMTP发出,由接收SMTP接收,而应答则反方向传送。过程如下: 用户 发 送 接 收 SMTP SMTP 文 件 文件系统 系 统 因为支持邮件附件,所以整个程序显得有点凌乱,程序中共有12个类。分别对SMTP,MIME,邮件以及编码技术进行封装: , CSMTPEMailAPP类 2 , CSMTPEMailDlg类 , CAboutDlg类 , CAttachmentsDl类 , CSMTP类 , CMailMessage类 , CMIMEMessage类 , CMIMEContentAgent类 , CTextPlain类 , CAppOctetStrea类 , CMIMECode类 , CBase64类 下面从整体上来讲程序结构理清楚: CSMTPEMailAPP,CSMTPEMailDlg,CAboutDlg这几个类就不用说了。他们是由Visual C++ 6.0的应用程序向导自动生成的框架代码,分别是应用程序类、主对话框类和“关于”对话框类。 CAttachmentsDlg类也是一个对话框类,他封装了添加/移除邮件附件的对话框。 CSMTP类则是程序中的一个关键类,他对SMTP协议进行了包装。也就是说,它实现了SMTP协议的客户端功能。使用该类的成员函数可以登陆到指定的SMTP服务器,设置特定的用户帐号并最后发送撰写好的邮件。 CMailMessage类和CMIMEMessage类对待发送的邮件进行了包装,其中CMailMessage类是CMIMEMessage类的父类,他封装了格式与RFC822定义的格式兼容的电子邮件。而CMIMEMessage类则对MIME扩展各式的邮件进行了包装。 CMIMEContentAgent类,CTextPlain类,CAppOctetStrea类这三个类对MIME邮件格式进行了必要的包装。其中CMIMEContentAgent类是父类,CTextPlain类和CAppOctetStrea类都是从它派生而来的。CTextPlain类封装的是文本格式,CAppOctetStrea类封装的是应用程序文档格式。 CMIMECode类和CBase64类对MIME的编码/解码技术进行了包装。其中CMIMECode是父类,CBase64是子类。 (C)详细设计: (1).CSMTP类 这个类实现同服务器的交互。利用SMTP协议发送邮件的过程为:首先建立TCP连接,然后使用命令HELO打开通信通道。如: 键入:user:j1010 $telnet 202.117.35.170 25 接收:220 X1 NT-ESMTP Server ctec.xjtu.edu.cn 发送:HELO student@ctec.xjtu.edu.cn 接收:250 hello ctec.xjtu.edu.cn 关闭连接使用命令QUIT,如下: 发送:QUIT 接受:221 Goodbye 3 打开连接通道后,需要进行邮件信息的通信交互,命令介绍如下: MAIL 初始化邮件传输 mail from: RCPT 标识单个的邮件接收人;常在MAIL命令后面 可有多个rcpt to: DATA 在单个或多个RCPT命令后,表示所有的邮件接收人已标识,并初始化 数据传输,以.结束。 VRFY 用于验证指定的用户/邮箱是否存在;由于安全方面的原因,服务器常 禁止此命令 EXPN 验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用 HELP 查询服务器支持什么命令 NOOP 无操作,服务器应响应OK RSET 重置会话,当前传输被取消。 下面是一个例子演示在主机202.117.35.170的student给主机mail.xjtu.edu.cn上的BOB和JANE发送邮件的过程。 发送: MAIL FROM:〈student@ctec.xjtu.edu.cn〉 接收:250 OK RCPT TO:〈BOB@mail.xjtu.edu.cn〉 发送: 接收: 250 OK 发送:RCPT TO: 〈YONG@mail.xjtu.edu.cn〉 550 No such user here 接收: 发送:RCPT TO:〈JANE@mail.xjtu.edu.cn〉 接收:250 OK 发送:DATA 接收:354 ok, send it; end with . 发送:Hi, I am in XJTU now,Where are you? //注:这里为邮件征文 发送:How are you? 发送:. //邮件结束标志 接收:250 OK 下面是程序的具体实现过程: 应答代码数组定义如下: CSMTP::response_code CSMTP::response_table[] = { { 250, _T( "SMTP server error" ) }, { 220, _T( "SMTP server not available" ) }, { 354, _T( "SMTP server not ready for data" ) }, { 221, _T( "SMTP server didn't terminate session" ) } }; 建立同SMTP服务器连接的函数CCMTP::Connect,并发送HELO命令登陆服务器。为了接受服务器的响应,预先分配缓冲区response_buf;调用CSockt类的Creat()函数创建套接字;调用CSockt类的Connect()函数试图连接到SMTP服务器;调用get_response()函数测试是否成功连接到CSMTP服务器;如果连接成功则给服务器发送HELO命令;如果接受到服务器对此指令的回应则表示已经建 4 立起与SMTP服务器的连接,可以进行后续操作了。具体代码如下: //连接SMTP服务器 BOOL CSMTP::Connect() { CString sHello; //本地计算机名称 TCHAR local_host[ 80 ]; if( m_bConnected ) return TRUE; try { //接收缓冲区 response_buf = new TCHAR[ RESPONSE_BUFFER_SIZE ]; if( response_buf == NULL ) { m_sError = _T( "Not enough memory" ); return FALSE; } } //捕获内存异常 catch( CException *e ) { response_buf = NULL; m_sError = _T( "Not enough memory" ); delete e; return FALSE; } //创建Socket if( !m_wsSMTPServer.Create() ) { m_sError = _T( "Unable to create the socket." ); delete response_buf; response_buf = NULL; return FALSE; } //连接服务器 if( !m_wsSMTPServer.Connect( GetServerHostName(), GetPort() ) ) { m_sError = _T( "Unable to connect to server" ); m_wsSMTPServer.Close(); delete response_buf; response_buf = NULL; return FALSE; 5 } //获取服务器响应 if( !get_response( CONNECT_SUCCESS ) ) { m_sError = _T( "Server didn't respond." ); m_wsSMTPServer.Close(); delete response_buf; response_buf = NULL; return FALSE; } gethostname( local_host, 80 ); //发送HELO 命令 sHello.Format( _T( "HELO %s\r\n" ), local_host ); m_wsSMTPServer.Send( (LPCTSTR)sHello, sHello.GetLength() ); //接收服务器响应 if( !get_response( GENERIC_SUCCESS ) ) { m_wsSMTPServer.Close(); delete response_buf; response_buf = NULL; return FALSE; } m_bConnected = TRUE; return TRUE; } 使用成员函数Connect()连接到SMTP服务器之后,就可以调用SendMessage()韩书法送邮件了;SendMessage()函数分三步完成邮件的发送任务。(1)检查到SMTP服务器的连接是否已经建立。如果还没有,则返回FALSE表示发送失败。 (2)调用FormatMailMessage()函数对待发送的邮件进行格式化。(3)调 transmit_message()函数将邮件安装一定的规则发送个服务器。 BOOL CSMTP::SendMessage(CMailMessage * msg) { ASSERT( msg != NULL ); if( !m_bConnected ) { m_sError = _T( "Must be connected" ); return FALSE; } if( FormatMailMessage( msg ) == FALSE ) { return FALSE; } if( transmit_message( msg ) == FALSE ) { 6 return FALSE; } return TRUE; } 前面提到的FormatMailMessage()函数定义如下,它主要调用了CMailMessage类的FormatMessage()函数,对于这个函数间后面关于CMailMessage类的具体介绍. BOOL CSMTP::FormatMailMessage( CMailMessage* msg ) { ASSERT( msg != NULL ); if( msg->GetNumRecipients() == 0 ) { m_sError = _T( "No Recipients" ); return FALSE; } msg->FormatMessage(); return TRUE; } 邮件的发送是通过CSMTP::transmit_message函数完成的,该函数按照前面介绍的发送过程,依次发送MAIL FROM,RCPT TO, DATA命令完成信件的发送。它发送邮件的次序是这样的:格式化发件人E-Mail地址;格式化收件人E-Mail地址;发送DATA指令;格式化邮件头部;格式化邮件正文;发送作为邮件结束标志的符号。 //发送邮件 BOOL CSMTP::transmit_message(CMailMessage * msg) { CString sFrom; CString sTo; CString sTemp; CString sEmail; ASSERT( msg != NULL ); if( !m_bConnected ) { m_sError = _T( "Must be connected" ); return FALSE; } //发送 MAIL FROM:命令 sFrom.Format( _T( "MAIL From: <%s>\r\n" ), (LPCTSTR)msg->m_sFrom ); m_wsSMTPServer.Send( (LPCTSTR)sFrom, sFrom.GetLength() ); if( !get_response( GENERIC_SUCCESS ) ) return FALSE; //发送RCPT TO:命令 //可以连续发送多次,这样可以给多个人发送邮件 7 for( int i = 0; i < msg->GetNumRecipients(); i++ ) { msg->GetRecipient( sEmail, sTemp, i ); sTo.Format( _T( "RCPT TO: <%s>\r\n" ), (LPCTSTR)sEmail ); m_wsSMTPServer.Send( (LPCTSTR)sTo, sTo.GetLength() ); get_response( GENERIC_SUCCESS ); } //发送DATA命令 sTemp = _T( "DATA\r\n" ); m_wsSMTPServer.Send( (LPCTSTR)sTemp, sTemp.GetLength() ); if( !get_response( DATA_SUCCESS ) ) { return FALSE; } //发送信件头 m_wsSMTPServer.Send( (LPCTSTR)msg->m_sHeader, msg->m_sHeader.GetLength() ); //处理邮件正文 sTemp = cook_body( msg ); //发送邮件正文 m_wsSMTPServer.Send( (LPCTSTR)sTemp, sTemp.GetLength() ); //发送邮件数据结束标志 回车.回车 sTemp = _T( "\r\n.\r\n" ); m_wsSMTPServer.Send( (LPCTSTR)sTemp, sTemp.GetLength() ); if( !get_response( GENERIC_SUCCESS ) ) { return FALSE; } return TRUE; } 接受并验证服务器的响应函数get_response完成。该函数通过Socket读取返回的 信息,分离出相应代码(250等),判断是否发送成功。 BOOL CSMTP::get_response( UINT response_expected ) { //输入的响应代码是否合理 ASSERT( response_expected >= GENERIC_SUCCESS ); ASSERT( response_expected < LAST_RESPONSE ); CString sResponse; UINT response; response_code* pResp; //接受响应 if( m_wsSMTPServer.Receive( response_buf, RESPONSE_BUFFER_SIZE ) == 8 SOCKET_ERROR ) { m_sError = _T( "Socket Error" ); return FALSE; } sResponse = response_buf; //获取前三个字符(250等) sscanf( (LPCTSTR)sResponse.Left( 3 ), _T( "%d" ), &response ); //获取响应的代码项 pResp = &response_table[ response_expected ]; //检验是否发送成功 if( response != pResp->nResponse ) { m_sError.Format( _T( "%d:%s" ), response, (LPCTSTR)pResp->sMessage ); return FALSE; } return TRUE; } CSMTP::cook_body函数的功能是将邮件正文中的〈CRLF〉.〈CRLF〉替换为〈CRLF〉..〈CRLF〉,具体代码如下: CString CSMTP::cook_body(CMailMessage * msg) { ASSERT( msg != NULL ); CString sTemp; CString sCooked = _T( "" ); //需要删除的字符串标志 LPTSTR szBad = _T( "\r\n.\r\n" ); LPTSTR szGood = _T( "\r\n..\r\n" ); int nPos; int nStart = 0; int nBadLength = strlen( szBad ); sTemp = msg->m_sBody; //文件首部发现 该字符串标志 if( sTemp.Left( 3 ) == _T( ".\r\n" ) ) sTemp = _T( "." ) + sTemp; //将 szBad替换为szGood while( (nPos = sTemp.Find( szBad )) > -1 ) { sCooked = sTemp.Mid( nStart, nPos ); sCooked += szGood; sTemp = sCooked + sTemp.Right( sTemp.GetLength() - (nPos + nBadLength) ); } return sTemp; 9 } CSMTP::Disconnect函数的功能是推出并断开同SMTP服务器的连接。该函数首先向服务器发送QUIT命令,然后关闭Socket。具体代码如下: BOOL CSMTP::Disconnect() { BOOL ret; if( !m_bConnected ) return TRUE; 发送QUIT命令 // CString sQuit = _T( "QUIT\r\n" ); m_wsSMTPServer.Send( (LPCTSTR)sQuit, sQuit.GetLength() ); //关闭Socket ret = get_response( QUIT_SUCCESS ); m_wsSMTPServer.Close(); if( response_buf != NULL ) { delete[] response_buf; response_buf = NULL; } m_bConnected = FALSE; return ret; } (2).CMailMessage类CMIMEMessage类 在电子邮件的发送和接收中,邮件本身就是一个较为复杂的实体。因此,为了结构化的需要,我们有CMailMessage类CMIMEMessage类对待发送的邮件进行了包装,其中CMailMessage类是CMIMEMessage类的父类,他封装了格式与RFC822定义的格式兼容的电子邮件。而CMIMEMessage类则对MIME扩展各式的邮件进行了包装。 下面是对CMailMessage类的定义,其中virtual类型的函数将会被CMIMEMessage类重载。定义如下: class CMailMessage { public: CMailMessage(); virtual ~CMailMessage(); //接收人的类型,枚举结构 enum RECIPIENTS_TYPE { TO, CC, BCC }; // //格式化信息,调用了附件编码程序 void FormatMessage(); //获取接收人的数量 int GetNumRecipients(RECIPIENTS_TYPE type = TO /* */); 10 //获取接收人信息 BOOL GetRecipient( CString& sEmailAddress, CString& sFriendlyName, int nIndex = 0, RECIPIENTS_TYPE type = TO /* */ ); //添加接收人 BOOL AddRecipient( LPCTSTR szEmailAddress, LPCTSTR szFriendlyName = "", RECIPIENTS_TYPE type = TO /* */ ); //添加多个接收人 BOOL AddMultipleRecipients( LPCTSTR szRecipients = NULL, RECIPIENTS_TYPE type = TO /* */ ); //获取每行的字符数 UINT GetCharsPerLine(); //设定每行的字符数 void SetCharsPerLine( UINT nCharsPerLine ); //发信人 CString m_sFrom; //主题 CString m_sSubject; CString m_sEnvelope; //发信程序名称 CString m_sMailerName; //信件头 CString m_sHeader; //时间戳 CTime m_tDateTime; //邮件正文 CString m_sBody; private: UINT m_nCharsPerLine; //接收人结构 class CRecipient { public: //接收人地址 CString m_sEmailAddress; //接收人名称 CString m_sFriendlyName; }; //TO 链表 CArray m_Recipients; //CC链表 CArray m_CCRecipients; //BCC链表 CArray m_BCCRecipients; 11 protected: //准备邮件头格式 virtual void prepare_header(); //准备邮件正文格式 virtual void prepare_body(); //结束邮件头 virtual void end_header(); //结束正文 virtual void start_header(); virtual void add_header_line( LPCTSTR szHeaderLine ); }; 在这个类的声明中,CMailMessage类提供了几个成员变量以保存成员变量以保 存有件的主要参数,其中重要的包括如下: , m_sForm 保存发件人的E-mail地址 , m_sMailerName 保存发件人的名称 , m_Recipients 保存收件人列表 , m_sSubject: 保存待发送邮件的主题 , m_tDateTime 保存发信日期时间 , m_sHeader 保存邮件头部 , m_sBody 保存邮件正文 在成员函数中,GetNumRecipients(), GetRecipient(), AddRecipient() 以及 AddMultipleRecipient()都和收件人列表有关,他们分别用来获取列表中的人数、 收件人列表以及向列表中添加收件人。 int CMailMessage::GetNumRecipients(RECIPIENTS_TYPE type /* */) { //*** old line -> return m_Recipients.GetSize(); int number = 0; switch(type) { case TO: number = m_Recipients.GetSize(); break; case CC: number = m_CCRecipients.GetSize(); break; case BCC: number = m_BCCRecipients.GetSize(); break; } return number; } BOOL CMailMessage::GetRecipient(CString & sEmailAddress, CString & sFriendlyName, int nIndex, RECIPIENTS_TYPE type /* */) { CRecipient to; if( nIndex < 0 || nIndex > m_Recipients.GetUpperBound() ) return FALSE; //*** Begin 12 //*** old line -> to = m_Recipients[ nIndex ]; { switch(type) { case TO: to = m_Recipients[ nIndex ]; break; case CC: to = m_CCRecipients[ nIndex ]; break; case BCC: to = m_BCCRecipients[ nIndex ]; break; } } //*** End sEmailAddress = to.m_sEmailAddress; sFriendlyName = to.m_sFriendlyName; return TRUE; } BOOL CMailMessage::AddRecipient( LPCTSTR szEmailAddress, LPCTSTR szFriendlyName) { ASSERT( szEmailAddress != NULL ); ASSERT( szFriendlyName != NULL ); CRecipient to; to.m_sEmailAddress = szEmailAddress; to.m_sFriendlyName = szFriendlyName; m_Recipients.Add( to ); return TRUE; } //添加多个接收人,szRecepients为接收人字符串,格式为: //名称1<接收人1地址>;名称2<接收人2地址>;...... BOOL CMailMessage::AddMultipleRecipients(LPCTSTR szRecipients, RECIPIENTS_TYPE type ) { TCHAR* buf; UINT pos; UINT start; CString sTemp; CString sEmail; CString sFriendly; UINT length; int nMark; int nMark2; ASSERT( szRecipients != NULL ); length = strlen( szRecipients ); buf = new TCHAR[ length + 1 ]; 13 strcpy( buf, szRecipients ); //剥离出接收人 for( pos = 0, start = 0; pos <= length; pos++ ) { if( buf[ pos ] == ';' || buf[ pos ] == 0 ) { buf[ pos ] = 0; sTemp = &buf[ start ]; nMark = sTemp.Find( '<' ); if( nMark >= 0 ) { //分离出名称 sFriendly = sTemp.Left( nMark ); nMark2 = sTemp.Find( '>' ); if( nMark2 < nMark ) { delete[] buf; return FALSE; } nMark2 > -1 ? nMark2 = nMark2 : nMark2 = sTemp.GetLength() - 1; //分离出接收地址 sEmail = sTemp.Mid( nMark + 1, nMark2 - (nMark + 1) ); } else { sEmail = sTemp; sFriendly = _T( "" ); } //添加接收人 AddRecipient( sEmail, sFriendly, type ); start = pos + 1; } } delete[] buf; return TRUE; } 上文提到的关于FormatMessage()函数,这里来做一介绍,这个函数用来对邮件进行格式化处理,他的定义如下: void CMailMessage::FormatMessage() { start_header(); 14 prepare_header(); end_header(); prepare_body(); } FormatMessage()函数调用了几个可被其子类重载的虚拟函数,在CmailMessage ,正是类中,这几个虚拟函数的定义非常简单,而在其子类CMIMEMessage中由于这几个虚拟函数的重载才使得CMIMEMessage对原来的邮件格式进行了扩展。在CmailMessag中,这几个函数及其相关函数是这样定义的: void CMailMessage::start_header() { m_sHeader = _T( "" ); } void CMailMessage::end_header() { m_sHeader += _T( "\r\n" ); } 函数prepare_header()的功能是格式化邮件头信息。该函数为邮件添加FROM, TO, CC等邮件头信息。 void CMailMessage::prepare_header() { CString sTemp; sTemp = _T( "" ); // From: sTemp = _T( "From: " ) + m_sFrom; add_header_line( (LPCTSTR)sTemp ); // To: sTemp = _T( "To: " ); CString sEmail = _T( "" ); CString sFriendly = _T( "" ); for( int i = 0; i < GetNumRecipients(); i++ ) { GetRecipient( sEmail, sFriendly, i ); sTemp += ( i > 0 ? _T( "," ) : _T( "" ) ); sTemp += sFriendly; sTemp += _T( "<" ); sTemp += sEmail; sTemp += _T( ">" ); } add_header_line( (LPCTSTR)sTemp ); // Date: 15 m_tDateTime = m_tDateTime.GetCurrentTime(); sTemp = _T( "Date: " ); sTemp += m_tDateTime.Format( "%a, %d %b %y %H:%M:%S %Z" ); add_header_line( (LPCTSTR)sTemp ); // Subject: sTemp = _T( "Subject: " ) + m_sSubject; add_header_line( (LPCTSTR)sTemp ); // X-Mailer sTemp = _T( "X-Mailer: " ) + m_sMailerName; add_header_line( (LPCTSTR)sTemp ); } void CMailMessage::prepare_body() { // Append a CR/LF to body if necessary. if( m_sBody.Right( 2 ) != _T( "\r\n" ) ) m_sBody += _T( "\r\n" ); } E-Mail传送过程中都要对附件文件进行编码,因为E-Mail只能传送ASCII码格式的文字信息,ASCII码为7位码。非ASCII码格式的文件在传送中必须经过编码编成相应的ASCII码后进行传输,在接受到后,接受后要进行编码。若不这样,就会在传输过程总出现编码截位的问题,导致接受方出现“乱码”问题。 MIME(Multipurpose Internet Mail Extention)是常用的编码,MIME定义的是一种编码规格,也可以说是一类编码的统称,能过符合MIME标准的编码方式有多种,而只要符合MIME规格便可以顺利传送,在MIME定义下有两种编码方式Base64和QP(Quote-Printable),QP的规则是对中的7位无重复编码,仅将8位数据转成7位,QP编码适用传送非ASCII码的文字内容,例如中文文件。而Base64的规则是将整个文件重新编码成7位,通常用于传送二进制文件。后文详细介绍Base64算法。 类CMIMEMessage继承与CMailMessage,该类主要完成将附件文件添加到邮件信息中的功能,定义如下: // 继承于CMailMessage class CMIMEMessage : public CMailMessage { public: CMIMEMessage(); virtual ~CMIMEMessage(); // MIME Type Codes enum eMIMETypeCode { 16 //文本类型 TEXT_PLAIN = 0, //二进制数据流 APPLICATION_OCTETSTREAM, NEXT_FREE_MIME_CODE }; //编码方式 enum eMIMEEncodingCode { _7BIT = 0, _8BIT, BINARY, QUOTED_PRINTABLE, BASE64, NEXT_FREE_ENCODING_CODE }; //添加多媒体信息 BOOL AddMIMEPart( LPCTSTR szContent, int nContentType = APPLICATION_OCTETSTREAM, LPCTSTR szParameters = _T( "" ), int nEncoding = BASE64, BOOL bPath = TRUE ); protected: void insert_message_end( CString& sText ); void register_mime_type( CMIMEContentAgent* pMIMEType ); void insert_boundary( CString& sText ); virtual void append_mime_parts(); virtual void prepare_header(); virtual void prepare_body(); CString m_sNoMIMEText; //信息边界标志 CString m_sPartBoundary; //MIME type CString m_sMIMEContentType; private: //多媒体信息管理 class CMIMEPart { public: //编码类型 int m_nEncoding; //媒体类型 17 int m_nContentType; CString m_sParameters; BOOL m_bPath; CString m_sContent; }; 媒体链表 // CList m_MIMEPartList; class CMIMETypeManager { public: CMIMEContentAgent* GetHandler( int nContentType ); void RegisterMIMEType( CMIMEContentAgent* pMIMEType); virtual ~CMIMETypeManager(); CMIMETypeManager(); private: CCriticalSection m_csAccess; CList < CMIMEContentAgent*, CMIMEContentAgent* > m_MIMETypeList; }; static CMIMETypeManager m_MIMETypeManager; }; 该类中绝大多数函数的功能都是进行数据链表的操作,并没有实质性的算法操作。 AddMIMEPart函数的功能是添加附件信息,该函数将需要发送的附件信息(文件路径等)添加到附件链表中,但是不进行编码。具体代码如下: BOOL CMIMEMessage::AddMIMEPart(LPCTSTR szContent, int nContentType, LPCTSTR szParameters, int nEncoding, BOOL bPath ) { CMIMEPart part; part.m_nContentType = nContentType; part.m_sParameters = szParameters; part.m_nEncoding = nEncoding; part.m_bPath = bPath; part.m_sContent = szContent; part.m_sContent.TrimLeft(); part.m_sContent.TrimRight(); if( nContentType == TEXT_PLAIN ) 18 m_MIMEPartList.AddHead( part ); else m_MIMEPartList.AddTail( part ); return TRUE; } append_mime_parts()函数的功能是调用编码函数进行编码操作,该函数被 prepare_body()这两个函数将被prepare_body()调用,而prepare_header(), CMailMessage::FormatMessage()函数调用。 void CMIMEMessage::prepare_header() { CString sTemp; // Let the base class add its headers CMailMessage::prepare_header(); add_header_line( _T( "MIME-Version: 1.0" ) ); sTemp.Format( _T( "Content-Type: %s; boundary=%s" ), (LPCTSTR)m_sMIMEContentType, (LPCTSTR)m_sPartBoundary ); add_header_line( (LPCTSTR)sTemp ); } void CMIMEMessage::prepare_body() { if( m_sBody != _T( "" ) ) AddMIMEPart( (LPCTSTR)m_sBody, TEXT_PLAIN, "", _7BIT, FALSE ); // Initialize the body (replace current contents). m_sBody = m_sNoMIMEText; m_sBody += _T( "\r\n\r\n" ); append_mime_parts(); insert_message_end( m_sBody ); CMailMessage::prepare_body(); } void CMIMEMessage::insert_boundary( CString& sText ) { CString sTemp; if( sText.Right( 2 ) != _T( "\r\n" ) ) sText += _T( "\r\n" ); sTemp.Format( _T( "--%s\r\n" ), (LPCTSTR)m_sPartBoundary ); sText += sTemp; } void CMIMEMessage::insert_message_end( CString& sText ) { 19 CString sTemp; if( sText.Right( 2 ) != _T( "\r\n" ) ) sText += _T( "\r\n" ); sTemp.Format( _T( "--%s--\r\n" ), (LPCTSTR)m_sPartBoundary ); sText += sTemp; } void CMIMEMessage::register_mime_type(CMIMEContentAgent* pMIMEType) { ASSERT( pMIMEType != NULL ); if( pMIMEType == NULL ) return; m_MIMETypeManager.RegisterMIMEType( pMIMEType ); } CMIMEMessage::append_mime_parts函数的功能是调用编码函数进行邮件编码 操作,该函数被CMIMEMessag::prepare_body调用,而后者又被FormatMessage调用。具体实现如下: void CMIMEMessage::append_mime_parts() { POSITION part_position; CMIMEPart* pMIMEPart = NULL; //CMIMEContentAgent为一父类,真正的处理过程 //在其子类CAppOctetStream完成 CMIMEContentAgent* pMIMEType = NULL; part_position = m_MIMEPartList.GetHeadPosition(); while( part_position != NULL ) { //获取MIME类型和数据信息 pMIMEPart = & m_MIMEPartList.GetNext( part_position ); pMIMEType = m_MIMETypeManager.GetHandler( pMIMEPart->m_nContentType ); if( pMIMEType != NULL ) { //添加边界标志 insert_boundary( m_sBody ); pMIMEType->AppendPart( pMIMEPart->m_sContent, pMIMEPart->m_sParameters, pMIMEPart->m_nEncoding, pMIMEPart->m_bPath, m_sBody ); } } } 20 (3)CMIMEContentAgent, CTextPlain和CAppOctectStream类 上文我们提到MIME邮件格式,MIME支持多种邮件格式,对于这些邮件格式本身,就需要一个封装。这就是由CMIMEContentAgent, CAppOctectStream和CTextPlain类这三个类来完成的。这三个类中,CMIMEContentAgent是父类,其它两个类都是从它派生来的。封装的是应用程序文档格式,CTextPlain类封装的是文本格式。 在父类CMIMEContentAgent中,QueryType()成员函数被用来询问他所支持的MIME格式,当然,该函数也被CTextPlain和CAppOctectStream也继承。此外CMIMEContentAgent还提供了两个纯粹的虚拟函数(即没有给出实现,且必须被其子类重载),一个是GetContentTypeString(),用来返回描述所支持的MIME格式字符串,另一个是AppendPart(),用来在邮件中附加指定格式的特定内容。 CTextPlain和CAppOctectStream对GetContentTypeString()的实现分别如下(它们返回的描述字符串分别是text/plain和application/octet-stream): CString CTextPlain::GetContentTypeString() { CString s; s = _T( "text/plain" ); return s; } CString CAppOctetStream::GetContentTypeString() { CString s; s = _T( "application/octet-stream" ); return s; } 而对于AppendPart()函数,CTextPlain和CAppOctectStream类采取了完全不同的实现方法。当然,这也是一定的,因为邮件格式的特点正是在这个函数中得到体现的。CTextPlain中的AppendPart()函数首先调用build_sub_header()函数创建子邮件头,然后将要添加的内容用wrap_text()函数进行包装,最后将两部分内容相加即可,如下: BOOL CTextPlain::AppendPart(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath, CString & sDestination) { CString sSubHeader; CString sWrapped; sSubHeader = build_sub_header( szContent, szParameters, 21 nEncoding, bPath ); sWrapped = wrap_text( szContent ); sDestination += (sSubHeader + sWrapped); return TRUE; } build_sub_header()函数和wrap_text()函数如下: CString CTextPlain::build_sub_header(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath) { CString sSubHeader; sSubHeader.Format( _T( "Content-Type: %s%s\r\n" ), (LPCTSTR)GetContentTypeString(), szParameters ); sSubHeader += _T( "Content-Transfer-Encoding: " ); switch( nEncoding ) { // This class handles only 7bit encoding, but others // may be added here. default: //Fall through to... case CMIMEMessage::_7BIT: sSubHeader += _T( "7Bit" ); } sSubHeader += _T( "\r\n\r\n" ); return sSubHeader; } CString CTextPlain::wrap_text(LPCTSTR szText) { CString sTemp; CString sLeft; CString sRight; int lp = 0; UINT nCount = 0; int nSpacePos = 0; ASSERT( szText != NULL ); if( szText == NULL ) 22 return sTemp; sTemp = szText; while( lp < sTemp.GetLength() ) { if( sTemp[ lp ] == ' ' ) nSpacePos = lp; // Reset counter on newline if( sTemp.Mid( lp, 2 ) == _T( "\r\n" ) ) nCount = 0; // Wrap text at last found space if( nCount > m_nWrapPos ) { sLeft = sTemp.Left( nSpacePos ); sRight = sTemp.Right( sTemp.GetLength() - nSpacePos ); sLeft.TrimRight(); sRight.TrimLeft(); sLeft += _T( "\r\n" ); sTemp = sLeft + sRight; nCount = 0; } else nCount++; lp++; } return sTemp; } 但是在CAppOctetStream类的AppendPart()函数中,不但创建子邮件头的函数定义是不一样的,而且获得了子邮件头之后,CAppOctetStream类接下来的操作是调用attach_file()将附件添加到邮件中,这就是说该函数调用attch_file函数来编码附件,调用buile_sub_header()来创建邮件附件信息头,代码如下: BOOL CAppOctetStream::AppendPart(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath, CString & sDestination) { CStdioFile fAttachment; ASSERT( szContent != NULL ); if( szContent == NULL ) return FALSE; 23 //打开附件文件 if( !fAttachment.Open( szContent, (CFile::modeRead | CFile::shareDenyWrite | CFile::typeBinary) ) ) return FALSE; //创建信息头 sDestination += build_sub_header( szContent, szParameters, nEncoding, TRUE ); //对附件文件进行编码 attach_file( &fAttachment, CMIMEMessage::BASE64, sDestination ); fAttachment.Close(); return TRUE; } CString CAppOctetStream::build_sub_header(LPCTSTR szContent, LPCTSTR szParameters, int nEncoding, BOOL bPath) { CString sSubHeader; CString sTemp; TCHAR szFName[ _MAX_FNAME ]; TCHAR szExt[ _MAX_EXT ]; //分离出文件目录 _tsplitpath( szContent, NULL, NULL, szFName, szExt ); if( bPath ) sTemp.Format( "; file=%s%s", szFName, szExt ); else sTemp = _T( "" ); //创建Content-Type信息头 sSubHeader.Format( _T( "Content-Type: %s%s\r\n" ), (LPCTSTR)GetContentTypeString(), (LPCTSTR)sTemp ); //表明编码方式为Base64 sSubHeader += _T( "Content-Transfer-Encoding: base64\r\n" ); sTemp.Format( _T( "Content-Disposition: attachment; filename=%s%s\r\n" ), szFName, szExt ); sSubHeader += sTemp; //表明信息头结束 24 sSubHeader += _T( "\r\n" ); return sSubHeader; } // 读取附件文件进行编码 //pFileAtt:文件指针 //nEncoding:编码类型 //sDestination:目标字符串 void CAppOctetStream::attach_file(CStdioFile* pFileAtt, int nEncoding, CString & sDestination) { //编码类 CMIMECode* pEncoder; int nBytesRead; TCHAR szBuffer[ BYTES_TO_READ + 1 ]; ASSERT( pFileAtt != NULL ); if( pFileAtt == NULL ) return; switch( nEncoding ) { default: case CMIMEMessage::BASE64: try { pEncoder = new CBase64; } catch( CMemoryException* e ) { delete e; return; } } if( pEncoder == NULL ) return; do { try { //读取一行文件数据进行编码 nBytesRead = pFileAtt->Read( szBuffer, BYTES_TO_READ ); } 25 catch( CFileException* e ) { delete e; break; } szBuffer[ nBytesRead ] = 0; //进行base64编码 sDestination += pEncoder->Encode( szBuffer, nBytesRead ); sDestination += _T( "\r\n" ); } while( nBytesRead == BYTES_TO_READ ); sDestination += _T( "\r\n" ); delete pEncoder; } (4)CBase64类和CMIMECode类 Base64是MIME邮件中常用的编码方式之一。它的主要思想是将输入的字符串或数据编码成只含有{'A'-'Z', 'a'-'z', '0'-'9', '+', '/'}这64个可打印字符的串,故称为“Base64”。 Base64编码的方法是,将输入数据流每次取6 bit,用此6 bit的值(0-63)作为索引去查表,输出相应字符。这样,每3个字节将编码为4个字符(3×8 ? 4×6);不满4个字符的以'='填充。 CBase64类实现了对于数据流的编码和解码算法,类的定义如下: class CBase64 : public CMIMECode { public: CBase64(); virtual ~CBase64(); //编码、解码函数 virtual int Decode( LPCTSTR szDecoding, LPTSTR szOutput ); virtual CString Encode( LPCTSTR szEncoding, int nSize ); protected: //编码 void write_bits( UINT nBits, int nNumBts, LPTSTR szOutput, int& lp ); //解码 UINT read_bits( int nNumBits, int* pBitsRead, int& lp ); //输入的字符串长度 int m_nInputSize; //剩余的字节数 int m_nBitsRemaining; ULONG m_lBitStorage; //输入缓冲区 LPCTSTR m_szInput; 26 //掩码 static int m_nMask[]; //转换后的字符表 static CString m_sBase64Alphabet; private: }; Encode()函数是用来对附件进行编码的,具体代码如下: //编码函数 CString CBase64::Encode(LPCTSTR szEncoding, int nSize) { CString sOutput = _T( "" ); int nNumBits = 6; UINT nDigit; int lp = 0; ASSERT( szEncoding != NULL ); if( szEncoding == NULL ) return sOutput; m_szInput = szEncoding; m_nInputSize = nSize; m_nBitsRemaining = 0; nDigit = read_bits( nNumBits, &nNumBits, lp ); while( nNumBits > 0 ) { //获取映射后的编码字符 sOutput += m_sBase64Alphabet[ (int)nDigit ]; nDigit = read_bits( nNumBits, &nNumBits, lp ); } //如果长度不是4的倍数 //需要用=补足 while( sOutput.GetLength() % 4 != 0 ) { sOutput += '='; } return sOutput; } Encode()函数调用进行read_bits()函数bit流的处理工作。read_bits()函数的代码实现如下: //nNumBits:打算读取的字节数 //pBitsRead:已经读取得字节数 //lp总共读取的字节数 UINT CBase64::read_bits(int nNumBits, int * pBitsRead, int& lp) { 27 ULONG lScratch; while( ( m_nBitsRemaining < nNumBits ) && ( lp < m_nInputSize ) ) { //从输入的字符串中获取一个 //8bits数据 int c = m_szInput[ lp++ ]; //右移8位 m_lBitStorage <<= 8; //获取的数据 m_lBitStorage |= (c & 0xff); m_nBitsRemaining += 8; } //根据读取得字节数 //取出不同的6位数据,通过掩码进行映射 if( m_nBitsRemaining < nNumBits ) { lScratch = m_lBitStorage << ( nNumBits - m_nBitsRemaining ); *pBitsRead = m_nBitsRemaining; m_nBitsRemaining = 0; } else { lScratch = m_lBitStorage >> ( m_nBitsRemaining - nNumBits ); *pBitsRead = nNumBits; m_nBitsRemaining -= nNumBits; } return (UINT)lScratch & m_nMask[nNumBits]; } 解码函数为Decode()。解码就是编码的逆过程,具体代码如下: int CBase64::Decode(LPCTSTR szDecoding, LPTSTR szOutput) { CString sInput; int c, lp =0; int nDigit; int nDecode[ 256 ]; ASSERT( szDecoding != NULL ); ASSERT( szOutput != NULL ); if( szOutput == NULL ) return 0; if( szDecoding == NULL ) return 0; sInput = szDecoding; 28 if( sInput.GetLength() == 0 ) return 0; //创建解码表 for( int i = 0; i < 256; i++ ) nDecode[i] = -2; // Illegal digit for( i=0; i < 64; i++ ) { nDecode[ m_sBase64Alphabet[ i ] ] = i; //忽略第8位 nDecode[ m_sBase64Alphabet[ i ] | 0x80 ] = i; //忽略附加的'=' nDecode[ '=' ] = -1; nDecode[ '=' | 0x80 ] = -1; } //初始化输出缓冲区 memset( szOutput, 0, sInput.GetLength() + 1 ); //循环将编码的字符串转换为原来的6bits数据 for( lp = 0, i = 0; lp < sInput.GetLength(); lp++ ) { c = sInput[ lp ]; nDigit = nDecode[ c & 0x7F ]; if( nDigit < -1 ) { return 0; } else if( nDigit >= 0 ) write_bits( nDigit & 0x3F, 6, szOutput, i ); } return i; } void CBase64::write_bits(UINT nBits, int nNumBits, LPTSTR szOutput, int& i) { UINT nScratch; m_lBitStorage = (m_lBitStorage << nNumBits) | nBits; m_nBitsRemaining += nNumBits; while( m_nBitsRemaining > 7 ) { nScratch = m_lBitStorage >> (m_nBitsRemaining - 8); szOutput[ i++ ] = nScratch & 0xFF; m_nBitsRemaining -= 8; 29 } } (三)总效果图 基本界面 短信功能 30 发送界面 添加附件 31 地址薄 (四)个人小结 通过这次网络创新实验及一部加深了对于网络编程的理解,扩展了学习的范围,例如:我们用到了基本的控件制作,网络编程,注册表,数据库等知识。也用到了编码方式,对于以后学习密码学很有帮助。在这里总结了以下几个方面: 1. 当进行一个任务或者计划时一定要有统筹安排,这样才能事半功倍。 2. 过程进行当中,一定要循序渐进,这样在整体上才能达到所预期的效果。 3. 任何事情的成功都难免遇到挫折困难,这时不应气馁,要有耐心,决心, 信心,克服艰难险阻,去实现目标。 4. 在过程中,充分利用网络资源,网络上有很多有用的资料供我们来学习, 通过网络来学习是大势所趋。 32 5. 要提高工作效率,不要做多余的无用功,这样是浪费时间,与己与人, 都是没有好处的,要用短时间做多的事情。 6. 阳光总在风雨后,当看到努力的成果时,那份成功的喜悦是不言而喻的。 通过大作业的学习,增加了我对软件应用的兴趣,知道自己的知识还很少,要不断学习去增加自己的知识。知识的海洋无穷无尽,只有那有力的舵手才能在这片海洋上任意遨游。 在最后要感谢罗建军老师提供的这次很好的机会和好的条件、无私的指导,感谢电气14张亮同学帮我提供的一些技术支持,感谢很多帮助我测试软件,给我软件编制过程中提出建议的朋友、同学。 33 34
/
本文档为【Sender邮件发送器】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索