php用Socket发送邮件

| 2 Comments | No TrackBacks

Socket编程介绍

向大家申明,本人不是一个TCP/IP编程专家,
故在此只是讲出了我的一点理解和体会。

使用fsockopen函数打开一个Internet连接,函数语法格式:
int fsockopen(string hostname, int port, int [errno], string [errstr], int [
timeout]);
  参数的意思我想不用讲了,这里由于要使用SMTP协议,所以端口号为25。在打开连
接成功后,会返回一个socket句柄,使用它就可以象使用文件句柄一样的。可使用的操
作有fputs(),fgets(),feof(),fclose()等。
  很简单地介绍就到这里吧。
SMTP的基础
  基于TCP/IP的因特网协议一般的命令格式都是通过请求/ 应答方式实现的,采用的
都是文本信息,所以处理起来要容易一些。SMTP是简单邮件传输协议的简称,它可以实
现客户端向服务器发送邮件的功能。所以下面所讲的命令是指客户端向服务器发出请求
指令,而响应则是指服务器返回给客户端的信息。
  SMTP分为命令头和信息体两部分。命令头主要完成客户端与服务器的连接,验证等
。整个过程由多条命令组成。每个命令发到服务器后,由服务器给出响应信息,一般为
3 位数字的响应码和响应文本。不同的服务器返回的响应码是遵守协议的,但是响应正
文本则不必。每个命令及响应的最后都有一个回车符,这样使用fputs()和fgets()就可
以进行命令与响应的处理了。SMTP的命令及响应信息都是单行的。信息体则是邮件的正
文部分,最后的结束行应以单独的"."作为结束行。
  客户端一些常用的SMTP指令为:
HELO hostname: 与服务器打招呼并告知客户端使用的机器名字,可以随便填写
MAIL FROM: sender_id : 告诉服务器发信人的地址
RCPT TO: receiver_id : 告诉服务器收信人的地址
DATA : 下面开始传输信件内容,且最后要以只含有.的特殊行结束
RESET: 取消刚才的指令,从新开始
VERIFY userid: 校验帐号是否存在(此指令为可选指令,服务器可能不支持)
QUIT : 退出连接,结束
  服务器返回的响应信息为(格式为:响应码+空格+解释):
220 服务就绪(在socket连接成功时,会返回此信息)
221 正在处理
250 请求邮件动作正确,完成(HELO,MAIL FROM,RCPT TO,QUIT指令执行成功会返回此信
息)
354 开始发送数据,结束以 .(DATA指令执行成功会返回此信息,客户端应发送信息)
500 语法错误,命令不能识别
550 命令不能执行,邮箱无效
552 中断处理:用户超出文件空间

下面给出一个简单的命令头(这是在打开socket之后做的),
是我向stmp.263.net发邮件的测试结果:
HELO limodou
250 smtp.263.net
MAIL FROM: chatme@263.net
250 Ok
RCPT TO: chatme@263.net
250 Ok
DATA
354 End data with .
To: chatme@263.net
From: chatme@263.net
Subject: test
From: chatme@263.net
test
.
QUIT
250 Ok: queued as C46411C5097E0
  这就是一些SMTP的简单知识。相关内容可以查阅RFC。
RFC 821定义了收/发电子邮件的相关指令。
RFC 822则制定了邮件?容的格式。
RFC 2045-2048制定了多媒体邮件?容的格式,
RFC 1113, 1422-1424则是讨论如何增进电子邮件的保密性。


send_mail类的实现
  现在开始介绍我所编写的发送邮件类。有了上面的预备知识了,下面就是实现了。

类的成员变量
var $lastmessage; //记录最后返回的响应信息
var $lastact; //最后的动作,字符串形式
var $welcome; //用在HELO后面,欢迎用户
var $debug; //是否显示调试信息
var $smtp; //smtp服务器
var $port; //smtp端口号
var $fp; //socket句柄
  其中,$lastmessage和$lastact用于记录最后一次响应信息及执行的命令,当出错
时,用户可以使用它们。为了测试需要,我还定义了$debug变量,当其值为true时,会
在运行过程中显示一些执行信息,否则无任何输出。$fp用于保存打开后的socket句柄。

类的构造
function send_mail($smtp, $welcome="", $debug=false)
{
if(empty($smtp)) die("SMTP cannt be NULL!");
$this->smtp=$smtp;
if(empty($welcome))
{
$this->welcome=gethostbyaddr("localhost");
}
else
$this->welcome=$welcome;
$this->debug=$debug;
$this->lastmessage="";
$this->lastact="";
$this->port="25";
}
  这个构造函数主要完成一些初始值的判定及设置。$welcome用于HELO指令中,告诉
服务器用户的名字。
  HELO指令要求为机器名,但是不用也可以。如果用户没有给出$welcome,则自动查
找本地的机器名。
显示调试信息
1 function show_debug($message, $inout)
2 {
3 if ($this->debug)
4 {
5 if($inout=="in") //响应信息
6 {
7 $m="<<,;
8 }
9 else
10 $m=">> ,;
11 if(!ereg("\n$", $message))
12 $message .= "
";
13 $message=nl2br($message);
14 echo "${m}${message}";
15 }
16 }
  这个函数用来显示调试信息。可以在$inout中指定是上传的指令还是返回的响应,
如果为上传指令,则使用"out";如果为返回的响应则使用"in"。
第3行,判断是否要输出调试信息。
第5行,判断是否为响应信息,如果是,则在第7行将信息的前面加上"<< "来区别信息;
否则在第10行加上 ">> "来区别上传指令。
第11-12行,判断信息串最后是否为换行符,如不是则加上HTML换行标记。第13行将所以
的换行符转成HTML的换行标记。
第14行,输出整条信息,同时将信息颜色置为灰色以示区别。

执行一个命令
1 function do_command($command, $code)
2 {
3 $this->lastact=$command;
4 $this->show_debug($this->lastact, "out");
5 fputs ( $this->fp, $this->lastact );
6 $this->lastmessage = fgets ( $this->fp, 512 );
7 $this->show_debug($this->lastmessage, "in");
8 if(!ereg("^$code", $this->lastmessage))
9 {
10 return false;
11 }
12 else
13 return true;
14 }
  在编写socket处理部分发现,一些命令的处理很相似,如HELO,MAIL FROM,RCPT
TO,QUIT,DATA命令,都要求根据是否显示调试信息将相关内容显示出来,同时对于返
回的响应码,如果是期望的,则应继续处理,如果不是期望的,则应中断出理。所以为
了清晰与简化,专门对这些命令的处理编写了一个通用处理函数。
函数的参数中$code为期望的响应码,如果响应码与之相同则表示处理成功,否则出错。

第3行,记录最后执行命令。
第4行,将上传命令显示出来。
第5行,则使用fputs真正向服务器传换指令。
第6行,从服务器接收响应信息将放在最后响应消息变量中。
第7行,将响应信息显示出来。
第8行,判断响应信息是否期待的,如果是则第13行返回成功(true),否则在第10行返回
失败(false)。
  这样,这个函数一方面完成指令及信息的发送显示功能,别一方面对返回的响应判
断是否成功。
邮件发送处理
  下面是真正的秘密了,可要看仔细了。:)
1 function send( $to,$from,$subject,$message)
2 {
3 //连接服务器
4 $this->lastact="connect";
5 $this->show_debug("Connect to SMTP server : ".$this->smtp, "out");
6 $this->fp = fsockopen ( $this->smtp, $this->port );
7 if ( $this->fp )
8 {
9 set_socket_blocking( $this->fp, true );
10 $this->lastmessage=fgets($this->fp,512);
11 $this->show_debug($this->lastmessage, "in");
12 if (! ereg ( "^220", $this->lastmessage ) )
13 {
14 return false;
15 }
16 else
17 {
18 $this->lastact="HELO " . $this->welcome . "\n";
19 if(!$this->do_command($this->lastact, "250"))
20 {
21 fclose($this->fp);
22 return false;
23 }
24 $this->lastact="MAIL FROM: $from" . "\n";
25 if(!$this->do_command($this->lastact, "250"))
26 {
27 fclose($this->fp);
28 return false;
29 }
30 $this->lastact="RCPT TO: $to" . "\n";
31 if(!$this->do_command($this->lastact, "250"))
32 {
33 fclose($this->fp);
34 return false;
35 }
36 //发送正文
37 $this->lastact="DATA\n";
38 if(!$this->do_command($this->lastact, "354"))
39 {
40 fclose($this->fp);
41 return false;
42 }
43 //处理Subject头
44 $head="Subject: $subject\n";
45 if(!empty($subject) && !ereg($head, $message))
46 {
47 $message = $head.$message;
48 }
49 //处理From头
50 $head="From: $from\n";
51 if(!empty($from) && !ereg($head, $message))
52 {
53 $message = $head.$message;
54 }
55 //处理To头
56 $head="To: $to\n";
57 if(!empty($to) && !ereg($head, $message))
58 {
59 $message = $head.$message;
60 }


61 //加上结束串
62 if(!ereg("\n\.\n", $message))
63 $message .= "\n.\n";
64 $this->show_debug($message, "out");
65 fputs($this->fp, $message);
66
67 $this->lastact="QUIT\n";
68 if(!$this->do_command($this->lastact, "250"))
69 {
70 fclose($this->fp);
71 return false;
72 }
73 }
74 return true;
75 }
76 else
77 {
78 $this->show_debug("Connect failed!", "in");
79 return false;
80 }
81 }

No TrackBacks

TrackBack URL: http://www.wujianrong.com/mt-tb.cgi/100

2 Comments

问一下:

如果要群发多个mail的话,rcpt 应该怎么设置;应该不用把这个函数for循环很多遍把?

需要多次调用,函数本身没有群发功能

Leave a comment

About this Entry

This page contains a single entry by kevinwu published on July 7, 2005 2:43 PM.

php通过smtp发送邮件 was the previous entry in this blog.

诺基亚更新Java API 支持更为方便的移动Web服务 is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.