了解POP3协议,使用简单的代码监控pop3邮箱,或者不用代码,直接使用telnet

本文介绍了POP3协议要点,包括命令、响应格式,状态码及三种状态转换。给出最小的Client - Server会话过程,还可在命令行测试。列举客户可发命令及含义,对比Foxmail和Outlook使用POP3的差异,最后提及可用C#代码实现POP3邮箱监控。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

POP3协议要点 ,更详细的信息可以查看 RFC1939

(1) POP3命令由一个命令和一些参数组成。所有命令以一个CRLF对结束
(2) 命令和参数由可打印的ASCII字符组成,它们之间由空格间隔
(3) POP3响应由一个状态码和一个可能跟有附加信息的命令组成。所有响应也是由CRLF对结束
(4) 有两种状态码,"确定" ("+OK")和"失败" ("-ERR")。
(5) 当所有信息发送结束时,发送最后一行,包括一个结束字符(十进制码46,也就是".")和一个CRLF对。
(6) 在POP3协议中有三种状态,认可状态,处理状态,和更新状态
  当客户机与服务器建立联系时,一旦客户机提供了自己身份并成功确认,即由认可状态转入处理状态,在完成相应的操作后客户机发出QUIT命令,则进入更新状态,更新之后最后重返认可状态。如下图

 等待连接        身份确认         QUIT命令
   ——  |认可|————— |处理|——————|更新|
           |__________________________________|
                  重返认可状态

一个最小的,成功的Client-Server会话过程

         POP3 Client                                           POP3 Server                        
         Socket()                                                   listen()
        
         Connect()        ------------------------------->  accept()
                              <------------------------------ +OK  发送确认消息 (进入"确认"状态)
                   
         发送USER命令------------------------------>
                               <----------------------------- +OK 发送确认消息
         发送PASS命令------------------------------>
                               <----------------------------- +OK 发送确认消息  (进入"操作"状态)
                    
         发送QUIT命令------------------------------>
                               <----------------------------- +OK 发送确认消息  (进入"更新"状态)
                                                        如果客户在"确认"状态下发送QUIT后,会话并不进入"更新"状态

可以在命令行(cmd.exe)里测试一下:
    c:\telnet pop3.163.com 110  //连接网易的邮件服务器
    +OK coremail ....                //服务器返回+OK
    USER 'yourusername'          //客户端发出用户名,可能在yourusername上需要一对单引号
    PASS 'yourpassword'          //客户端发出密码,可能在yourpassword上需要一对单引号
    +OK mm nn                       // 返回邮件数和大小
    STAT                                //得到邮箱的状态
    +OK xxx xxx                     // 返回邮件数和大小

客户可以发出的命令列表以及含义,源自ChinaUnix

   命令  参数    状态     描述
------------------------------------------------------------------------------------------------------------------------------
USER  username    认可  此命令与下面的pass命令若成功,将导致状态转换
PASS  password    认可     
APOP Name,Digest        认可  Digest是MD5消息摘要,windows系统多不支持
------------------------------------------ -----------------------------------------------------------------------------------
STAT  None     处理         请求服务器发回关于邮箱的统计资料,如邮件总数和总字节数
UIDL [Msg#]     处理        返回邮件的唯一标识符,POP3会话的每个标识符都将是唯一的
LIST  [Msg#]       处理  返回邮件数量和每个邮件的大小
RETR [Msg#]      处理  返回由参数标识的邮件的全部文本
DELE [Msg#]      处理  服务器将由参数标识的邮件标记为删除,由quit命令执行
RSET None      处理  服务器将重置所有标记为删除的邮件,用于撤消DELE命令
TOP [Msg#]     处理  服务器将返回由参数标识的邮件前n行内容,n必须是正整数
NOOP None     处理  服务器返回一个肯定的响应
------------------------------------------------------------------------------------------------------------------------------
QUIT  None     更新 

Foxmail 和 Outlook 使用 pop3时的异同 源自LinuxForum.net
假定服务器上有三封邮件等待客户机接收。用foxmail与OE的不同之处在于foxmail每收一封标记删除一封,而Outlook则等全部接收完后再全部标记为删除最后执行quit命令。
Foxmail               OutLook
-------------------------------------------
RETR 1                        RETR  1
DELE  1                        RETR  2
RETR 2                        RETR  3             
DELE  2                        DELE  1              
RETR 3                        DELE  2              
DELE  3                        DELE  3             
QUIT                            QUIT 
-------------------------------------------
     
简单的C#代码就可以实现pop3邮箱的监控

None.gif namespace  Sky.MailMonitor
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
InBlock.gif    
public class Pop3
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
private const int bufsize = 1024//default 1kbytes buffers
InBlock.gif
InBlock.gif        
//服务器确认消息的状态码,必须是大写的
InBlock.gif
        private const string OKFlag = "+OK";
InBlock.gif        
private const string ERRFlag = "-ERR";
InBlock.gif        
private int _port = 110;
InBlock.gif        
public int Port
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
return _port;
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
set
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                _port 
= value;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

InBlock.gif        
private int mailCount = 0;
InBlock.gif        
public int MailCount
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
return mailCount;
ExpandedSubBlockEnd.gif            }

InBlock.gif            
set
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                mailCount 
= value;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        
private int mailSpace = 0;
InBlock.gif        
public int MailSpace
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
return mailSpace;
ExpandedSubBlockEnd.gif            }

InBlock.gif            
set
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                mailSpace 
= value;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        
private Socket socket = null;
InBlock.gif        
public int port
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get dot.gifreturn Port; }
ExpandedSubBlockEnd.gif        }

InBlock.gif      
InBlock.gif        
//step 1: 连接一个POP3服务器,希望得到+OK的确认消息
InBlock.gif
        public void Connect(string hostname)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
//建立一个套接字
InBlock.gif
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
InBlock.gif
InBlock.gif            IPHostEntry hostInfo 
= Dns.GetHostByName(hostname);
InBlock.gif            IPEndPoint endPoint 
= new IPEndPoint(hostInfo.AddressList[0], Port);
InBlock.gif
InBlock.gif            socket.Connect(endPoint);
InBlock.gif            
string tmp = RecvLine(); //blocking,wait for '+OK'
InBlock.gif
            Console.WriteLine(tmp);
ExpandedSubBlockEnd.gif        }
 
InBlock.gif        
//step 2:发送命令字USER和PASS
InBlock.gif
        public void Login(string username,string password)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
InBlock.gif            SendCommand(
"USER " + username);
InBlock.gif            
string tmp=RecvLine();
InBlock.gif            Console.WriteLine(
"Server say: {0}", tmp);
InBlock.gif
InBlock.gif            SendCommand(
"PASS " + password);
InBlock.gif            
string tmp1=RecvLine();
InBlock.gif            Console.WriteLine(
"Server say: {0}", tmp1);
ExpandedSubBlockEnd.gif        }

InBlock.gif        
//step 3:发送命令字STAT,请求服务器发回关于邮箱的统计资料,如邮件总数和总字节数 
InBlock.gif        
//C:STAT
InBlock.gif        
//S:+OK nn mm 
InBlock.gif        
//nn是邮件数量,mm是大小
InBlock.gif
        public void Stat()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            SendCommand(
"STAT");
InBlock.gif            
string tmp=RecvLine();
InBlock.gif            tmp 
= tmp.Substring(tmp.IndexOf(" "+ 1); //在+OK和一个空格之后得到邮件数量,字节数
InBlock.gif
            string tmp1 = tmp.Substring(0, tmp.IndexOf(" ")).Trim();
InBlock.gif            MailCount
=Int32.Parse(tmp.Substring(0, tmp.IndexOf(" ")).Trim());
InBlock.gif            Console.WriteLine(
"Server say: {0}", tmp);
InBlock.gif            
// MailSpace = Int32.Parse(tmp1.Substring(0, tmp1.IndexOf(" ")).Trim());
ExpandedSubBlockEnd.gif
        }

InBlock.gif        
// LIST [msg] ,msg参数是可选的, 返回邮件数量和每个邮件的大小,如果没有msg参数,服务器将返回所有的邮件信息
InBlock.gif        
// C:LIST
InBlock.gif        
// S:+OK 187 9703827
InBlock.gif        
//1  31502
InBlock.gif        
//2  1168
InBlock.gif        
//3  1826
InBlock.gif        
//dot.gif.
InBlock.gif
        public void List()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            SendCommand(
"LIST");
InBlock.gif            
string tmp = RecvLine();
InBlock.gif            Console.WriteLine(
"Server say: {0}", tmp);
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif 
InBlock.gif        
public void Dele(string mailNumber)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            SendCommand(
"DELE " + mailNumber);
InBlock.gif            
string tmp = RecvLine();
InBlock.gif            Console.WriteLine(
"Server say: {0}", tmp);
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        
public void Quit()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            SendCommand(
"QUIT");
InBlock.gif            
string tmp = RecvLine();
InBlock.gif            Console.WriteLine(
"Server say: {0}", tmp);
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        
// 
InBlock.gif        
//关闭打开的连接并且发送QUIT命令字,QUIT命令没有参数
InBlock.gif
        public void Close()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
if (socket == null)
InBlock.gif                
return;
InBlock.gif            
try
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                Quit(); 
//first invoke Quit method
ExpandedSubBlockEnd.gif
            }

InBlock.gif            
finally
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                socket.Close();
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

InBlock.gif        
private void SendCommand(string command)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            command 
+= " ";//必须在命令字后添加CRLF对
InBlock.gif
            byte[] buffer = Encoding.ASCII.GetBytes(command.ToCharArray());
InBlock.gif
InBlock.gif            
// 发送缓冲
InBlock.gif
            int bytesSent = socket.Send(buffer, buffer.Length, 0);
InBlock.gif            
InBlock.gif            
if (bytesSent != buffer.Length)
InBlock.gif                
throw new Exception("failed to send request to server");
ExpandedSubBlockEnd.gif        }

InBlock.gif        
// TODO :服务器以Multi-line应答
InBlock.gif
        private string RecvMultiLine()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockEnd.gif        }

InBlock.gif        
// 服务器以line+CRLF形式应答
InBlock.gif
        private string RecvLine()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
//需要添加一个空的终结符
InBlock.gif
            byte[] buffer = new byte[bufsize + 1];
InBlock.gif            StringBuilder message 
= new StringBuilder(bufsize);
InBlock.gif            
int bytesRead;
InBlock.gif            
// 读取缓冲区
InBlock.gif
            for (; ; )
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                bytesRead 
= socket.Receive(buffer, bufsize, SocketFlags.None);
InBlock.gif                
//读取完毕
InBlock.gif
                if (bytesRead == 0)
InBlock.gif                    
break;
InBlock.gif                buffer[bytesRead] 
= 0;
InBlock.gif
InBlock.gif                
// conver char array to string
InBlock.gif
                message.Append(Encoding.ASCII.GetChars(buffer, 0, bytesRead));
InBlock.gif                
InBlock.gif                
if (buffer[bytesRead - 1== 10)  //=LF #hex:0A #Dec:10
InBlock.gif
                    break;
InBlock.gif            
ExpandedSubBlockEnd.gif            }

InBlock.gif            
string tmp = message.ToString();
InBlock.gif            
if (tmp.StartsWith(OKFlag) == false)  //服务器消息不是以+OK起始的确认消息
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                
throw (new Exception(tmp));
ExpandedSubBlockEnd.gif            }

InBlock.gif            
return tmp;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif

上面的代码完成了监控的基本功能,但是还不够,首先缺少异常处理,比如在socket.connect() 那里,连接失败了怎么办?其次,如果你想把邮件收到本地,那么字符的编码就成了问题,尤其是汉字的处理比较麻烦,详细的情况可以查看 RFC822 .你可以完善基本的代码,实现本地POP3收信了.
现在可以测试一下:
ContractedBlock.gif ExpandedBlockStart.gif Using directives #region Using directives
InBlock.gif
InBlock.gif
using System;
InBlock.gif
using System.Collections.Generic;
InBlock.gif
using System.Text;
InBlock.gif
ExpandedBlockEnd.gif
#endregion

None.gif
None.gif
namespace  Sky.MailMonitor
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
class Program
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
static void Main(string[] args)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            Pop3 pop3 
= new Pop3();
InBlock.gif            
string username="xxxxxx";
InBlock.gif            
string password="xxxxxx";
InBlock.gif            
string hostname="pop3.163.com";
InBlock.gif            
try
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                pop3.Connect(hostname);
InBlock.gif                pop3.Login(username, password);
InBlock.gif                pop3.Stat();
InBlock.gif                pop3.List();
ExpandedSubBlockEnd.gif            }

InBlock.gif            
catch(Exception e)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                Console.WriteLine(e.Message);
ExpandedSubBlockEnd.gif            }

InBlock.gif            
finally
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                pop3.Close();
ExpandedSubBlockEnd.gif            }

InBlock.gif            
InBlock.gif        
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif

转载于:https://www.cnblogs.com/xpoint/archive/2004/08/04/30058.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值