我们知道,PHP发送邮件有两种方式,一个是mail()函数,另一个就是socket发送邮件。对于第一种方式,我不多做介绍,因为它的用法相对简单,只是说可能需要配置的东西比较多。详细可参加PHP文档。这里,我主要谈谈第二种方式。
讲到第二种方式,我首先要谈谈发送邮件的原理。
发送邮件是基于客户/服务器模式的,也就是说,客户端首先需要向服务器端发送连接请求,当双方连接后,再进行数据的传输,与此同时,我们又不能随便的进行传输,而是要遵守一定的规则,这里所说的规则我们称之为协议。
在邮件系统中,主要涉及三种协议,SMTP,POP3,MIME,我们这里谈前两种;
SMTP协议:简单文件栓出协议,发送邮件使用的协议;
POP3:接收邮件协议。
上面的实体图正是发送邮件的原理示意图。通常我们发送邮件给另一个好友,并非直接把邮件发送到对方的客户机上。它实际上是先发送到了发送方的邮件服务器,然后发送方的邮件服务器将邮件发送到接收方的邮件服务器,收件人再从接收方的邮件服务器中获得所需邮件。举个例子来说,我们有一个163邮箱,邮件地址是hl@163.com;现在我要向145642@qq.com的邮箱中发一封邮件。实际的发送流程为,我们在在客户端使用客户端代理(如outlook)编辑好邮件,然后点击发送,这时邮件便会发送到163的邮件服务器,163邮件服务器在合适的时间会将邮件发送到QQ的邮件服务器,QQ邮件服务器接收到邮件后,根据收件人地址,会通知收件人,这时,收件人便可以从QQ邮件服务器获得邮件。大致的原理就是这样,当然,内部的工作原理还是很复杂的。
再来谈谈发送邮件的过程中所涉及的协议。我们从客户代理将邮件发送到发送放邮件服务器时,使用的是SMTP协议,发送方邮件服务器讲邮件发送到接收方邮件服务器时,使用的也是SMTP协议。当接收方代理从接收方邮件服务器获得邮件时,使用的是POP3协议,总之一句话,发送使用SMTP协议,接收使用POP3协议。
而我们这里要讨论的socket发送邮件,实质上就是自己编写代码取代发送方邮件代理的功能,自己将邮件发送到发送方邮件服务器中,讲到这里,大家有点明白了吧。
下面,讨论具体实现:
由于我们只是关心如何发送邮件,而不需要关心接收方如何获得邮件,因此,我们在编写的代码就是如何实现SMTP。
SMTP分为命令头和信息体两部分。命令头主要完成客户端和服务器的连接,验证等。整个过程由多条命令组成。每个命令发送到服务器后,由服务器给出相应信息,一般为3位数字的响应码和响应文本。每个命令及响应的最后都有一个回车符,这样使用fputs和fgets就可以进行响应的处理了。
客户端向邮件服务器发出的一些常用的SMTP指令为:
HELO hostname 与服务器打招呼并且告知服务器客户端使用的机器名,这里可以随便填写;
AUTH LOGIN:user,pass 邮箱帐号,密码
MAIL FROM:sender 告诉邮件服务器发信人的地址;
RCPT TO:receiver 告诉邮件服务器收信人的地址;
DATA 开始发送邮件内容,并且最后只能含有"."为结尾字符的行结束。
QUIT 传输结束,退出连接;
客户端每发一条命令,服务器便会返回相应的相应信息,信息格式为(响应码+空格+解释)
一些常见的相应码为:
220 服务就绪(socket返回)
221 正在处理
250 请求邮件动作成功(HELO MAIL FROM,RCPT TO,QUIT指令成功后会返回此状态码)
354 开始发送数据,以“.”结束(DATA 指令执行成功后会返回此信息);
500 语法错误,命令不能识别
550 命令不能执行,邮箱无效
而我们使用socket发送邮件是指上就是通过客户端向邮件服务器发送以上的命令,从而实现SMTP协议来达到发送邮件的目的。实质上outbllook之类的客户端邮件代理发送邮件原理和我们要实现的代码是一样的,只是它们将这些操作都封装好,然后提供了一个友好的界面供我们操作。
下面是我实现的代码(有详细注释):网上有很多封装好了的比较完整的发送邮件类,我只是粗略实现了它的功能,还有很多没有实现,因此,这里只供参考:
- <?php
- class smtp {
- var $smtp_port;//邮件服务器的端口,邮件服务器的为25
- var $host_name;//客户端主机名
- var $relay_host;//邮件服务器地址
- var $user;//用户名
- var $pass;//密码
- var $lastmessage;//服务器返回的信息
- private $socket;//socket句柄,调用fsockopen后返回的,具体用法参阅PHP文档
- //构造函数,初始化必要信息
- function __construct($relay_host="",$smtp_port=25,$auth=false,$user,$pass) {
- $this->smtp_port=$smtp_port;
- $this->replay_host=$relay_host;
- $this->auth=$auth;
- $this->user=$user;
- $this->pass=$pass;
- $this->host_name="127.0.0.1";
- }
- //执行指令的函数,相关函数用法请参阅PHP文档
- function smtp_putcmd($cmd) {
- fputs($this->socket,$cmd."/r/n");
- $this->lastmessage=str_replace("/r/n", "", fgets($this->socket, 512));
- if((preg_match("/^[23]/",$this->lastmessage))<=0) {//如果邮件服务器返回的状态码不是2或者3开头,则表明出现错误,具体状态码意义请参阅上文
- Return false;
- }
- else {
- Return true;
- }
- }
- //发送邮件所用的函数,the most important
- //$to表示收件人地址,$from表示发件人地址,$subject邮件主题,$body表示邮件主体
- function sendmail($to,$from,$subject="",$body="") {
- $this->socket=fsockopen($this->replay_host,$this->smtp_port);//打开一个socket
- stream_set_blocking($this->socket,1);
- $this->lastmessage=fgets($this->socket,512);
- if((preg_match("/^220/",$this->lastmessage))<=0) {
- Return false;
- }
- else {
- //这里客户端开始向邮件服务器发送上文中讨论过的命令
- if(!$this->smtp_putcmd("HELO ".$this->host_name)) {
- die('ERROR:sending HELO command/n');
- }
- if($this->auth) {
- if(!$this->smtp_putcmd("AUTH LOGIN ".base64_encode($this->user))) {
- die('ERROR:sending HELO command/n');
- }
- if(!$this->smtp_putcmd(base64_encode($this->pass))) {
- echo("falil");
- die('ERROR:sending HELO command/n');
- }
- }
- if(!$this->smtp_putcmd("MAIL FROM:<".$from.">")) {
- die("ERROR:sending MAIL FROM command");
- }
- if(!$this->smtp_putcmd("RCPT TO:<".$to.">")) {
- die("ERROR:sending RCPT TO command");
- }
- if(!$this->smtp_putcmd("DATA")) {
- die("ERROR:sending DATA command");
- }
- $head="To: ".$to."/r/n";
- $head.="From: $from<".$from.">/r/n";
- $head.="Subject: ".$subject."/r/n";
- //$body = preg_replace("/(^|(/r/n))(/.)/", "//1.//3", $body);
- $body.="/r/n./r/n";
- fputs($this->socket,$head."/r/n".$body);
- if(!$this->smtp_putcmd("QUIT")) {
- die("Error:sending QUIT command");
- }
- //关闭socket连接
- fclose($this->socket);
- }
- }
- }
- ?>
下面,我们就可以发送邮件了:
- <?php
- include('send_mail.php');
- $sm=new smtp("smtp.163.com",25,true,"这里是您的用户名","这里是您的密码");
- $sm->sendmail("这里填写您要发送的邮箱","这里是您的邮箱","邮件主题","邮件主体");
- ?>