21.2 功能设计和实现
本节开始实现用PHP发送邮件的程序。该程序的基本功能是,通过PHP程序,向SMTP发起请求,待SMTP服务器验证通过后,向PHP程序指定的邮箱发送邮件。使用mail函数也可以发送邮件,但其功能比较弱,不便于调试,而且在Windows下需要配置SMTP服务器,所以本章将通过Socket实现发送邮件的PHP程序。本节将设计一个发送邮件的PHP类,通过这个类完成向SMTP服务器发起请求以及发送邮件的功能。
21.2.1 设计一个发送邮件的类
有了21.1节的理论基础,实现发送邮件的类并不是一件困难的事。首先,根据以上介绍,可以确定该类应该有以下成员变量。
❑$lastmessage:改变量记录服务器最后返回的响应信息。
❑$lastact:最近一次要执行的操作,即是HELLO还是MAIL FROM请求等。
❑$welcome:用在HELLO命令后面的字符串
❑$debug:是否显示调试信息。
❑$smtp:smtp服务器。
❑$port:smtp端口号。
❑$fp:socket句柄。
需要实现的函数包括类的构造函数,显示调试信息的函数,处理Socket的函数以及发送邮件的函数。其中发送邮件的函数其实就是用来向Socket发送执行SMTP请求的命令。代码21-1是这个类的完整实现。
代码21-1 发送邮件的PHP类mail.php
01 <?php
02 class smtp_mail
03 {
04 var$lastmessage;//记录最后返回的响应信息
05 var$lastact;//最近一次要执行的操作
06 var$welcome;//用在HELLO命令后面的字符串
07 var$debug;//是否显示调试信息
08 var$smtp;//smtp服务器
09 var$port;//smtp端口号
10 var$fp;//socket句柄
11
12 function smtp_mail($smtp,$welcome="",$debug=false)
13 {
14 if(empty($smtp))die("SMTP cann't be NULL!");
15
16 $this->smtp=$smtp;
17 if(empty($welcome))
18 {
19 $this->welcome=gethostbyaddr("localhost");
20 }
21 else
22 $this->welcome=$welcome;
23 $this->debug=$debug;
24
25 $this->lastmessage="";
26 $this->lastact="";
27 $this->port="25";
28 }
29
30 function show_debug($message,$inout)
31 {
32 $b=false;
33 if($this->debug)
34 {
35 if($inout=="in")//响应信息
36 {
37 $m='<<';
38 $b=true;
39 }
40 else//请求指令
41 $m='>>';
42 if(!ereg("\n$",$message))
43 $message.="";
44 $message=nl2br($message);
45 if($b)
46 echo"<b>${m}${message}</b>";
47 else
48 echo"${m}${message}";
49 }
50 }
51
52 function do_command($command,$code)//处理Socket
53 {
54 $this->lastact=$command;
55 $this->show_debug($this->lastact,"out");
56 fputs($this->fp,$this->lastact);
57
58 $this->lastmessage=fgets($this->fp,512);
59 $this->show_debug($this->lastmessage,"in");
60
61 if(!ereg("^$code",$this->lastmessage))
62 {
63 return false;
64 }
65 else
66 return true;
67 }
68
69 function send($from,$to,$subject,$message)//发送邮件
70 {
71 $this->show_debug("Connect to SMTP server:".$this->smtp."\n","out");
72 $this->fp=fsockopen($this->smtp,$this->port);
73 if($this->fp)
74 {
75 set_socket_blocking($this->fp,true);
76 $this->lastmessage=fgets($this->fp,512);
77 $this->show_debug($this->lastmessage,"in");
78
79 if(!ereg("^220",$this->lastmessage))
80 {
81 return false;
82 }
83 else
84 {
85 $this->lastact="HELLO".$this->welcome."\n";
86 if(!$this->do_command($this->lastact,"250"))
87 {
88 fclose($this->fp);
89 return false;
90 }
91
92 $this->lastact="MAIL FROM:<$from>"."\n";
93 if(!$this->do_command($this->lastact,"250"))
94 {
95 fclose($this->fp);
96 return false;
97 }
98
99 $this->lastact="RCPT TO:<$to>"."\n";
100 if(!$this->do_command($this->lastact,"250"))
101 {
102 fclose($this->fp);
103 return false;
104 }
105
106 $this->lastact="DATA\n";//发送正文
107 if(!$this->do_command($this->lastact,"354"))
108 {
109 fclose($this->fp);
110 return false;
111 }
112
113 $head="Subject:$subject\n";//处理Subject
114 if(!empty($subject)&&!ereg($head,$message))
115 {
116 $message=$head.$message;
117 }
118
119
120 $head="From:$from\n";//处理From
121 if(!empty($from)&&!ereg($head,$message))
122 {
123 $message=$head.$message;
124 }
125
126 $head="To:$to\n";//处理To
127 if(!empty($to)&&!ereg($head,$message))
128 {
129 $message=$head.$message;
130 }
131
132 if(!ereg("\n\.\n",$message))//加上结束串
133 $message.="\n.\n";
134 $this->show_debug($message,"out");
135 fputs($this->fp,$message);
136
137 $this->lastact="QUIT\n";
138 if(!$this->do_command($this->lastact,"250"))
139 {
140 fclose($this->fp);
141 return false;
142 }
143 }
144 return true;
145 }
146 else
147 {
148 $this->show_debug("Connect failed!","in");
149 return false;
150 }
151 }
152 }
153 ?>
【代码解析】
(1)首先介绍构造函数smtp_mail()。构造函数完成一些初始值的判定及设置。参数$welcome用于HELO指令中,告诉服务器用户的名字或者直接填写服务器的名称,表示向服务器打招呼。如果用户没有给出$welcome,则自动查找本地的机器名,如代码第19行所示。
(2)函数show_debug()用来显示调试信息。可以在参数$inout中指定是请求指令还是返回的响应,如果为请求指令,则使用“out”;如果为返回的响应则使用“in”。其中第35行判断是否为响应信息,如果是,将信息的前面加上“<<”来区别响应指令;加上“>>”来区别请求指令。同时将响应信息的字符串字体加粗显示,如代码第46行和48行所示。
(3)函数do_command()用来处理Socket部分。该函数通过Socket处理参数$command所表示的指令,对于HELLO、MAIL FROM、RCPT TO、QUIT和DATA指令,都要求根据是否显示调试信息将相关内容显示出来,并且根据参数$code所表示的服务器响应码,决定是否中断处理。代码第56行使用fputs向服务器发送请求指令。该函数一方面完成指令及信息的发送、显示功能,别一方面对返回的响应判断是否成功。
(4)函数send()是用来发送邮件的函数,它共有4个参数,分别是:$from是发信邮件地址;$to是收信人邮件地址;$subject是邮件的主题;$message是邮件体,也就是邮件正文。如果处理成功,该函数返回true,否则失败返回false。其中的代码$this->fp=fsockopen($this->smtp,$this->port),作用是使用函数fsockopen()打开一个网络链接,其第一个参数是表示此socket链接的标识符,第2个参数标识在哪个端口打开此链接。