### PHP 中用 Socket 发送电子邮件(1)

Socket 编程介绍
使用 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 .= "<br>";
13 $message=nl2br($message);
14 echo "<font color=#999999>${m}${message}</font>";
15 }
16 }
--------------------------------------------------------------------------------
这个函数用来显示调试信息。可以在$inout 中指定是上传的指令还是返回的响应，如果为上传指令，则
使用"out"；如果为返回的响应则使用"in"。
第 3 行，判断是否要输出调试信息。
第 5 行，判断是否为响应信息，如果是，则在第 7 行将信息的前面加上"<< "来区别信息；否则在第 10 行加上
">> "来区别上传指令。
第 11-12 行，判断信息串最后是否为换行符，如不是则加上 HTML 换行标记。第 13 行将所以的换行符转成 HTML