POP3 协议: 采用对等的会话方式完成邮件的收取。过程为交互式的请求应答模式。 客户端首先要和POP3服务器110号端口进行连接。然后使用标准命令进行对话 //-----------------------------------标准命令 USER 标识用户进行验证 PASS发送密码进行验证 APOP转换验证机制 QUIT终止会话 NOOP空操作 STAT提供信箱大小信息 LIST提供邮件大小信息 RETR从服务器取出邮件 TOP取出信头和邮件的前N行 DELTE标记邮件被删除 RSET复位POP会话 UIDL取出邮件的唯一标识符 //------------------------------------------------------ 1(USER)该命令用来验证用户信箱 Client:USER caucy Server:+OK/-ERR 2(PASS) 该命令用来为USER命令指定的信箱提供密码 Client:PASS ***** Server:+OK/-ERR 3(QUIT) 该命令终止会话,断开与服务器的连接 Client:QUIT ***** Server:+OK/-ERR 如果会话在验证状态,QUIT引起服务器关闭连接,如果会话在事务状态,POP服务器进入更新状态,在关闭连接前删除标记为删除的任何邮件。如果用户不是通过QUIT命令关闭连接的,而是在客户端强行关闭,则在事务状态标记删除的邮件并没有被删除如果在更新状态时邮件碰到错误,服务器应答一个错误信息。但不管QUIT命令成功与否,信箱锁都被释放,连接关闭 4NOOP 命令什么都不做,只是检测服务器的连接 该命令只能在事务状态中使用,除了检测与服务器的连接是否正常,还可以防止自动注销定时器定时关 闭连接 5(STAT) 该命令请求服务器返回信箱大小的信息,但是不包过标记为删除的邮件 Client:STAT Server:+OK 2 4065 服务器的应答包含信箱的数量以及所有邮件的大小。stat命令仅在事务状态时可用的 6列表(LIST) 该命令请求服务器返回一个信箱中特定邮件的大小信息或者没有删除标记的所有邮件的大小信息 Client: LIST 1 Server:+OK 1 1046 该命令有两种使用情形,带参和不带参。List后面如果指定邮件,则返回邮件大小信息,如上所示。 List指定的邮件如果被标识为删除或者不存在,则出错,如下所示: Client:LIST 5 Server:-ERR no such message ,only message 1 thru 1 are PResent in your inbox 如果List不带参数,如下所示: Client:LIST Server将返回多行应答。 +OK 1 1045 2 2204 . 应答成功先是相应+OK,接着每一行含有一个邮件号和邮件的大小(字节数),最后是以句点"."作为结束 行。 (7)取出邮件(RETR) 该命令用于取出指定邮件号的邮件 Client:RETR 1 Server: +OK ... 如果请求成功,服务器返回的是对多行应答,先是"+OK"表示相应成功,接着返回邮件的所有内容,包括信头和信体。如果有附件,附件的内容也以文本的形式返回。最后以一个句点"."表示结束。为了防止单个句点引起客户机提前结束邮件的读取,对Retr命令使用与前面介绍SMTP标准DATA相同的点填补 8提取前几行(TOP) Client:TOP 1 1 Server: +OK ... 9(Top命令格式)(Top 邮件序号 行数) TOP命令取出指定邮件的信头和指定信体的行数。该命令仅在事务状态中才是可用的 TOP 1 2 取出的是邮件号为1的信件头和前2行的内容 与Retr命令一样,使用Top命令。服务器返回的内容结束标志为句点"." Top命令对于服务器是可选的,有些服务器上并没有实现。只是在可以实现Top的服务器才可以使用该命 令 10(DELE) Client:DELE 1 Server:+OK message 1 deleted 该命令标志指定的邮件为删除邮件,用于事务状态中 需要注意的是只有QUIT命令之后进入更新状态,标志为删除的邮件才会被真正地删除 11(复位RSET) Client:RSET Server:+OK 该命令在事务状态中复位POP会话,被标记为删除的邮件取消标记,以后发出的QUIT命令不删除以前标记为删除的邮件 12唯一ID列表(UIDL) Client:UIDL Server: +OK ... 该命令用户请求返回邮件的唯一标志符 UIDL命令可以返回该POP3服务器上的邮件的唯一编号,客户机可以在不同的POP3会话中辨认邮件 命令格式:UIDL[邮件序号] 可选的 如果带参数 返回指定邮件的唯一的ID,且应答是但行的;如果不带参数,则返回多行应答,最后以句点 "."作为结束。 在整个会话过程中,客户端处于3个状态中:验证状态(authorization status),事务状态(transaction status)和更新状态(update status). 每个状态是会话过程中的特定阶段。 当连接服务器后,首选进入验证状态,在这个阶段里,可以使用的POP3命令是USER,PASS,APOP和QUIT 通过服务器验证后,服务器锁定信箱,这样做可以防止多个POP客户端进行邮件操作,比如删除,取信 等,但可以让新邮件加入。这时会话过程转变为事务状态,接受邮件的POP对话大部分时间都是处在事 务状态中 事务状态可以使用POP3命令有:Noop,Stat,Quit,List,Retr,Top,Dele,Rset和Uidl。 会话过程的最后一个状态为更新状态,在事务状态结束后,发出Quit命令进入该状态,但是由于异常原因导致的与服务器终止对话并没进入更新状态。步骤: 1连接POP3服务器 2发送用户信箱名 3发送用户信箱密码 4对信箱邮件进行操作 5接受邮件完毕,结束会话
例子:需要建一个POP3类class CPop3 {public: BOOL Reset(void); //获得邮件主题 LPCTSTR GetMailHeader(UINT nIndex); //发送NOOP命令,进行一次空操作 BOOL Noop(void); //获得出错信息 LPCTSTR GetErrorString(void); //发送STAT命令,获得邮件总体的状态信息 BOOL GetState(void); //发送List命令,获得邮件的具体状态信息 BOOL List(void); //发送RETR命令,接受邮件正文 BOOL RetrieveMail(UINT nIndex,CString &strMsg); //发送DELE命令,将所选邮件标记为删除 BOOL Delete(UINT nIndex); //发送TOP命令,获得所有的邮件主题 BOOL ListHeader(void); //私有变量接口函数,返回给定索引的邮件大小 DWord GetMsgSize(UINT nID); //私有变量接口函数,返回邮件数目 UINT GetMailCount(void)const; //发送命令 BOOL Send(LPCTSTR lpszSend,UINT nLength); //断开连接,发送QUIT命令,并请求真正删除所要求的邮件 BOOL Disconnect(void); //建立与服务器的连接,其中包括登陆的过程 BOOL Connect(LPCTSTR strAddress,LPCTSTR strUser,LPCTSTR strPassword,UINT nPort=110);
CPop3(); virtual ~CPop3();
protected: //所有邮件的总体大小 UINT m_nSize; //通信的套接字 SOCKET m_sock; //邮件数目,以GetMailCount为接口函数 UINT m_nMails; //记录是否发送过有效的List命令 BOOL m_bList; //记录是否发送过有效的STAT命令 BOOL m_bState; //记录每个邮件大小的MFC数组变量 CDWordArray m_arSizes; //记录每个邮件主题的MFC数组变量 CStringArray m_arHeaders; //记录是否已经与服务器建立连接 BOOL m_bConnected; //记录回应的消息 CString m_sResponse; //确定是否超时,只要用去的时间超过该值就表示超时 DWORD m_dwTimeout;private: //记录出错信息,以GetErrorString为接口函数 CString m_sError; //去除开头的+OK标识,获得回应的有效信息 //从返回的信息中提取数目和大小信息,主要用于List命令的回应信息 BOOL ParseNumAndSize(UINT &nNum,UINT &nSize,LPCTSTR lpszBuf,UINT &nEnd); //读取回应信息,与GetResponse函数不同,它进行实际的工作 BOOL ReadResponse(TCHAR* pChar,UINT nSize,BOOL bDouble); BOOL GetResponse(void); //读取针对LIST命令的返回信息 //由于对LIST命令的返回信息较特殊,所以用单独的函数 BOOL ReadList(void); //对STAT命令的返回信息,原因同上 BOOL ReadState(void);};
1初始化DlgBOOL CPOP3Dlg::OnInitDialog(){ SetDlgItemText(IDC_ADDRESS,"pop.126.com"); SetDlgItemText(IDC_USERNAME,"mazhiyuan_"); SetDlgItemText(IDC_PASSWORD,"");
CString strText; this->SetDlgItemInt(IDC_PORT,110,FALSE); strText=_T("序号"); m_lstMail.InsertColumn(0,strText,LVCFMT_CENTER,48); strText=_T("邮件主题"); m_lstMail.InsertColumn(1,strText,LVCFMT_CENTER,318); strText=_T("大小"); m_lstMail.InsertColumn(2,strText,LVCFMT_CENTER,48);
(this->GetDlgItem(IDC_REVERSE))->EnableWindow(FALSE); (this->GetDlgItem(IDC_DELETE))->EnableWindow(FALSE); (this->GetDlgItem(IDC_DISCONNECT))->EnableWindow(FALSE); return TRUE; } 2收取邮件:void CPOP3Dlg::OnReceive() { // TODO: Add your control notification handler code here GetDlgItemText(IDC_ADDRESS,m_sAddr); GetDlgItemText(IDC_USERNAME,m_sUser); GetDlgItemText(IDC_PASSWORD,m_sPassword);
if(m_sAddr.IsEmpty()||m_sUser.IsEmpty()||m_sPassword.IsEmpty()) { AfxMessageBox(_T("输入POP3信息不全")); return ; }
if(!m_pop3.Connect(m_sAddr,m_sUser,m_sPassword))//连接POP3服务器 { // 连接失败 AfxMessageBox(_T("无法与服务器建立连接!")); return; }
//进行一次空操作 if(!m_pop3.Noop()) { AfxMessageBox(m_pop3.GetErrorString()); return ; }
Refresh();//获得邮件信息 // 更新界面 GetDlgItem(IDC_REVERSE)->EnableWindow(TRUE); GetDlgItem(IDC_DELETE)->EnableWindow(TRUE); GetDlgItem(IDC_DISCONNECT)->EnableWindow(TRUE);}
//连接POP3服务器并发送用户名和密码的功能是在COnnect中完成BOOL CPop3::Connect(LPCTSTR strAddress,LPCTSTR strUser,LPCTSTR strPassword,UINT nPort){ //初始化本地套接字 m_sock=socket(AF_INET,SOCK_STREAM,0); if(m_sock==INVALID_SOCKET) { //AfxMessageBox(_T("初始化套接字失败")); m_sError=_T("初始化套接字失败"); return FALSE; }
SOCKADDR_IN sockAddr; memset(&sockAddr,'/0',sizeof(sockAddr)); sockAddr.sin_family=AF_INET; sockAddr.sin_port=htons((u_short)nPort); sockAddr.sin_addr.S_un.S_addr=inet_addr(strAddress);
//如果主机名不是ip地址,用gethostbyname获得其IP地址 if(sockAddr.sin_addr.S_un.S_addr==INADDR_NONE) { LPHOSTENT lphost; lphost=gethostbyname(strAddress); if(lphost) sockAddr.sin_addr.s_addr=((LPIN_ADDR)lphost->h_addr)->s_addr; else { m_sError=_T("无法解析地址"); return FALSE; } }
//尝试与服务器连接 if(connect(m_sock,(SOCKADDR*)(&sockAddr),sizeof(sockAddr))==SOCKET_ERROR) { m_sError=_T("无法与服务器建立连接!"); return FALSE; }
//接受回应信息 if(!GetResponse()) { //回应失败 m_sError=_T("无法收到回应信息或收到错误信息"); Disconnect(); return FALSE; }
//发送用户名 CString strTemp; strTemp.Format("USER %s/r/n",strUser); if(!Send((LPCTSTR)strTemp,strTemp.GetLength())) { m_sError=_T("发送用户名失败"); return FALSE; }
//接受回应信息 if(!GetResponse()) { //接受回应信息回应失败 m_sError=_T("无法收到回应信息或收到错误信息"); Disconnect(); return FALSE; }
//发送密码 strTemp.Format("PASS %s/r/n",strPassword); if(!Send((LPCTSTR)strTemp,strTemp.GetLength())) { //发送密码失败 m_sError=_T("发送密码失败"); Disconnect(); return FALSE; }
//接受回应信息 if(!GetResponse()) { //接受回应信息回应失败 m_sError=_T("无法收到回应信息或收到错误信息"); Disconnect(); return FALSE; }
m_bConnected=TRUE; return TRUE;}
//得到服务器端的响应码是在GetResponse函数中完成的,他通过调用ReadResponse函数完成的BOOL CPop3::GetResponse(void){ TCHAR chArray[1188]; if(!ReadResponse(chArray,1188,FALSE)) { return FALSE; }
m_sResponse=chArray; return TRUE;}
BOOL CPop3::ReadResponse(TCHAR* pChar,UINT nSize,BOOL bDouble){ CString strTemp; //读取回应信息 BOOL bEnd=FALSE; UINT nReceived=0; DWORD dwStart=::GetTickCount(); while(!bEnd) { //------------------------------------------------------------------------------------------------------------- //尝试时间到 if((::GetTickCount()-dwStart)>m_dwTimeout) { pChar[nReceived]='/0'; m_sError=_T("超时"); //保存当前回应的消息 m_sResponse=pChar; return FALSE; } //------------------------------------------------------------------------------------------------------------- //看到套接字是否可读 timeval timeout={0,0}; fd_set fds; FD_ZERO(&fds); FD_SET(m_sock,&fds); int nStatus=select(0,&fds,NULL,NULL,&timeout); if(nStatus==SOCKET_ERROR) { //套接字不可读 m_sError=_T("套接字不可读"); return FALSE; } else if(!nStatus) { //没有接受到数据 ::Sleep(688); continue; }
//------------------------------------------------------------------------------------------------------------------- //从套接字中接受数据 ::Sleep(288); nStatus=recv(m_sock,pChar+nReceived,nSize,0); if(nStatus==SOCKET_ERROR) { pChar[nReceived]='/0'; //套接字错误 m_sError=_T("未能从套接字中收到数据"); //保存当前回应信息 m_sResponse=pChar; return FALSE; } else if(nStatus) { //重置计时器 dwStart=::GetTickCount(); //已经收到的数据又增加了 nReceived+=nStatus; }
//将pChar设为字符串,并赋给CString 型的变量 pChar[nReceived]='/0'; strTemp=pChar;
//检查是否收到了结束标志 LPCTSTR lpszComp=bDouble ? "/r/n./r/n" : "/r/n"; bEnd=(strTemp.Find(lpszComp)!=-1); //--------------------------------------------------------------------------------------------------------------------- }
//--------------------------------------------------------------------------------------------------------------------------- //去掉结束标志 nReceived-=bDouble ? 3 : 0; pChar[nReceived]='/0'; //--------------------------------------------------------------------------------------------------------------------------- //检查回应信息是否有效 strTemp=pChar; strTemp.MakeUpper(); int nStart=strTemp.Find("+OK"); if(nStart==-1) { //收到无效信息 m_sError=_T("回应信息无效"); return FALSE; } //-------------------------------------------------------------------------------------------------------------------------- //提取有效信息 strTemp=pChar; m_sResponse=strTemp.Right(strTemp.GetLength()-nStart-3); return TRUE;}
//进行一次空操作BOOL CPop3::Noop(){ CString strTemp="NOOP/r/n"; if(!Send((LPCTSTR)strTemp,strTemp.GetLength())) { // 发送失败 m_sError = _T("进行空操作失败!"); Disconnect(); return FALSE; }
// 接收回应信息 if (!GetResponse()) { // 接收回应信息失败 m_sError = _T("无法收到回应信息或收到无效信息!"); Disconnect(); return FALSE; } return TRUE;}
//获得邮件信息void CPOP3Dlg::Refresh(){
// 查询邮箱状态 if(!m_pop3.GetState()) { AfxMessageBox(m_pop3.GetErrorString()); return ; }
// 列举邮件大小 if(!m_pop3.List()) { AfxMessageBox(m_pop3.GetErrorString()); return ; }
// 接收邮件的主题 if(!m_pop3.ListHeader()) { AfxMessageBox(m_pop3.GetErrorString()); return ; }
m_lstMail.DeleteAllItems(); UINT nIndex,nMails=m_pop3.GetMailCount(); CString strText; for(nIndex=0;nIndex<nMails;nIndex++) { strText.Format("%d",nIndex+1); m_lstMail.InsertItem(nIndex,strText); m_lstMail.SetItemText(nIndex,1,m_pop3.GetMailHeader(nIndex)); strText.Format("%d",m_pop3.GetMsgSize(nIndex)); m_lstMail.SetItemText(nIndex,2,strText); }}
BOOL CPop3::GetState(){ CString strTemp = "STAT/r/n"; if (m_bState) return TRUE; if (!Send((LPCTSTR)strTemp,strTemp.GetLength())) { // 发送失败 m_sError = _T("查询邮箱状态失败!"); Disconnect(); return FALSE; }
if (!ReadState()) { // 接收回应信息失败 m_sError = _T("无法收到邮箱状态信息或收到无效信息!"); Disconnect(); return FALSE; }
return TRUE;}
BOOL CPop3::ReadState(){ // 获得返回的状态信息 if (!GetResponse()) { // 接收返回信息失败 m_sError = _T("接收返回信息失败!"); return FALSE; }
// 看返回的信息是否有效 m_sResponse.MakeUpper(); UINT nEnd = 0; if (m_sResponse.Find("+OK") == -1) { // 收到无效的回应信息 m_sError = _T("返回信息无效!"); return FALSE; } else { // 分析邮件数和大小,首先分析数目 return this->ParseNumAndSize(m_nMails,m_nSize,(LPCTSTR)m_sResponse,nEnd); }}
BOOL CPop3::ParseNumAndSize(UINT &nNum,UINT &nSize,LPCTSTR lpszBuf,UINT &nEnd){ BOOL bNum=TRUE; for(int i=nEnd,j=-1;lpszBuf[i];i++) { if(isdigit(lpszBuf[i]))//是数字 j=(j==-1) ? i : j; else { if(j==-1) { //还未读入数据 continue; } else if(bNum)//分析完一个字段 { //分析数目 m_nMails=atoi(&lpszBuf[j]); nNum=m_nMails; bNum=FALSE; j=-1; } else { //分析大小 m_nSize=atoi(&lpszBuf[j]); nSize=m_nSize; nEnd=i; return TRUE; } } } nEnd=i; return FALSE;}
//列取邮件的功能BOOL CPop3::List(){ CString strTemp="LIST/r/n"; if(m_bList) return TRUE; if(!Send((LPCTSTR)strTemp,strTemp.GetLength())) { //发送失败 m_sError=_T("查询邮箱状态失败"); Disconnect(); return FALSE; }
if(!ReadList()) { //接受回应信息回应失败 m_sError=_T("无法收到回应信息或收到错误信息"); Disconnect(); return FALSE; } return TRUE;}
BOOL CPop3::ReadList(){ UINT nBuf=m_nMails*18+188; char *pBuf=new char[nBuf]; if(!pBuf) { return FALSE; }
if(!ReadResponse(pBuf,nBuf,TRUE)) { return FALSE; }
m_arSizes.RemoveAll(); m_arSizes.SetSize(m_nMails); UINT nNum,nSize,nEnd=0; while(ParseNumAndSize(nNum,nSize,m_sResponse,nEnd)) { m_arSizes.SetAt(nNum-1,nSize); } return TRUE;}
BOOL CPop3::ListHeader(){ m_arHeaders.RemoveAll(); m_arHeaders.SetSize(m_nMails); CString strTemp; for(UINT nMsg=1;nMsg<=m_nMails;nMsg++) { strTemp.Format("TOP %d 0/r/n",nMsg); if(!Send((LPCTSTR)strTemp,strTemp.GetLength())) { m_sError = _T("发送TOP命令失败!"); return FALSE; }
if (!GetResponse()) { m_sError = _T("列举邮件主题失败!"); return FALSE; }
strTemp=m_sResponse; strTemp.MakeUpper();
// 查找SUBJECT段 UINT nStart=strTemp.Find("/r/nSUBJECT:"); if(nStart==-1) { m_arHeaders.SetAt(nMsg - 1,_T("")); return TRUE; } strTemp = m_sResponse.Right(m_sResponse.GetLength() - nStart - 10); nStart = strTemp.Find("/r/n"); if (nStart != -1) strTemp = strTemp.Left(nStart); strTemp.TrimLeft(); strTemp.TrimRight(); m_arHeaders.SetAt(nMsg - 1,strTemp); } return TRUE;
}
3断开函数void CPOP3Dlg::OnDisconnect() { // TODO: Add your control notification handler code here if(!m_pop3.Disconnect()) { return ; }
// 重置用户界面状态 GetDlgItem(IDC_REVERSE)->EnableWindow(FALSE); GetDlgItem(IDC_DELETE)->EnableWindow(FALSE); GetDlgItem(IDC_DISCONNECT)->EnableWindow(FALSE); m_lstMail.DeleteAllItems();
MessageBox("断开连接...");}
//断开连接BOOL CPop3::Disconnect(){ BOOL bSuccess;
//如果还保持连接状态则断开 if(m_bConnected) { CString strSend="QUIT/r/n"; //发送断开请求 if(!this->Send((LPCTSTR)strSend,strSend.GetLength())) { AfxMessageBox(_T("发送断开请求失败")); return FALSE; }
bSuccess=this->GetResponse();
//将有关的变量复位 m_bConnected=FALSE; m_bList=FALSE; m_bState=FALSE; m_nMails=0; m_nSize=0; m_arHeaders.RemoveAll(); m_arSizes.RemoveAll();
//关闭套接字 closesocket(m_sock); m_sock=INVALID_SOCKET; } return bSuccess;}
4删除函数 void CPOP3Dlg::OnDelete() { POSITION pos=m_lstMail.GetFirstSelectedItemPosition(); while(pos) { if(!m_pop3.Delete(m_lstMail.GetNextSelectedItem(pos)+1)) { return ; } }
MessageBox("删除成功..."); }
//删除邮件功能BOOL CPop3::Delete(UINT nIndex){ CString strSend; strSend.Format("DELE %d/r/n",nIndex); //发送DELE命令 if(!Send((LPCTSTR)strSend,strSend.GetLength())) { //未能成功发送 m_sError=_T("删除邮件失败"); return FALSE; }
//接受返回信息 if(!GetResponse()) { m_sError=_T("收到无效信息"); return FALSE; }
//上一次STAT和LIST命令得到的信息无效 m_bState=FALSE; m_bList=FALSE; return TRUE;}
//打开邮件4 void CPOP3Dlg::OnClickListMail(NMHDR* pNMHDR, LRESULT* pResult) { // TODO: Add your control notification handler code here if (m_lstMail.GetSelectedCount() != 1) { AfxMessageBox("请明确要打开的email!"); return; }
POSITION pos = m_lstMail.GetFirstSelectedItemPosition(); UINT nIndex = m_lstMail.GetNextSelectedItem(pos); CString strMail; m_pop3.RetrieveMail(2 ,strMail); this->SetDlgItemText(IDC_EDIT_TEXT,strMail);
*pResult = 0;}
//接受邮件的功能BOOL CPop3::RetrieveMail(UINT nIndex,CString &strMsg){ CString strSend; strSend.Format("RETR%d/r/n",nIndex); if(!Send((LPCTSTR)strSend,strSend.GetLength())) { m_sError=_T("收取邮件失败"); return FALSE; }
if(!GetResponse()) { m_sError=_T("无法受到送回的邮件"); return FALSE; }
//以CONTENT-DISPOSTION:INLINE为其实标志 strMsg=m_sResponse; strMsg.MakeUpper(); int nStart=strMsg.Find("CONTENT-DISPOSTION: INLINE/r/n"); strMsg=m_sResponse.Right(m_sResponse.GetLength()-nStart-4); //空行表示邮件正文结束 nStart=strMsg.Find("/r/n/r/n");
if(nStart!=-1) strMsg=strMsg.Left(nStart); return TRUE;}
//-------------------OK下面是附加函数
CPop3::CPop3(){ // 初始化变量 m_sError = _T(""); m_sock = INVALID_SOCKET; m_nMails = 0; m_nSize = 0; m_bList = FALSE; m_bState = FALSE; m_arSizes.RemoveAll(); m_arHeaders.RemoveAll(); m_bConnected = FALSE; m_dwTimeout = 2000;}
CPop3::~CPop3(){
}
DWORD CPop3::GetMsgSize(UINT nID){ BOOL bSuccess; if(!m_bList) bSuccess=this->List(); if (!bSuccess) return 0;
return m_arSizes.GetAt(nID);
}
LPCTSTR CPop3::GetMailHeader(UINT nIndex){ return (LPCTSTR)m_arHeaders.GetAt(nIndex);}
UINT CPop3::GetMailCount() const{ return m_nMails;}
新闻热点
疑难解答