page contents

PHP 下的 Socket 编程--发送邮件

发送邮件使用的是 SMTP 协议 (简单邮件传输协议), 用于邮件服务器和邮件发送方之间。

attachments-2020-04-XyfpRV1P5ea6365ba8c63.jpg


发送邮件使用的是 SMTP 协议 (简单邮件传输协议), 用于邮件服务器和邮件发送方之间。


邮件的发送过程大致如下:

  1. 在邮件发送方和邮件服务器间建立 TCP 连接, 服务器响应 220 表示连接成功;
  2. 发送方通过HELO命令标识自己的身份. 服务器响应 250 表示准备接收邮件;
  3. 发送方通过AUTH LOGIN命令进行登录, 以 163 邮件服务器为例, 登录账号分别是 base64 编码过的邮箱账号和 163 的客户端授权码. 服务器响应 334 表示账号验证通过, 响应 235 表示授权码验证通过;
  4. 发送方通过MAIL FROM命令指定邮件的发送者. 服务器响应 250 表示成功;
  5. 发送方通过RCPT TO命令指定邮件接收地址, 服务器响应 250 表示成功;
  6. 发送方通过DATA命令发送邮件, 邮件内容包括邮件头和邮件正文部分. 服务器响应 250 表示成功;
  7. 发送方通过QUIT命令断开连接.


Windows 下可以通过 telnet 发送邮件。


邮件头的基本格式为:

Date: Feb 7 20:30:39 2007 // 发送日期
From: "发送者" <发送者邮箱>
To: "接受者" <接收者邮箱>
Subject: 邮件标题
Content-Type: text/plain; // 邮件正文类型


邮件头主要配置项:

v2-061ad383110fad5098e237b2cf674136_720w.jpg

邮件内容的具体格式和结构, 可以参考: https://help.aliyun.com/knowled。


attachments-2020-04-36MktCSk5ea6368464d28.jpg


示例一: 发送简单邮件

sendEmail.php

header('Content-type: text/html; charset=utf-8');
$server = 'smtp.163.com';
$errno = null;
$error = null;
// 设置邮件头
$email = 'Date: ' . date('j F G:i:s Y', time()) . "\r\n"; // 发送日期
$email .= 'From: "哪里多" <******@163.com>' . "\r\n"; // 发送者
$email .= "To: \"二柱子\" <******@qq.com>\r\n"; // 接收者
$email .= "Subject: 测试邮件\r\n"; // 邮件标题
$email .= "Content-Type: text/plain; charset=UTF-8\r\n\r\n"; // 邮件内容类型,邮件头和正文间空一行分割
 
$email .= "邮件正文\r\n\r\n\r\n"; // 正文内容结束后空两行
 
 
$email .= ".\r\n"; // 邮件内容结束后以 . 命令标识结束
try
{
// fsockopen()函数: 建立一个网络连接或Unix套接字, 返回一个文件句柄, 可以使用文件操作函数操作返回的资源
$sockHandle = fsockopen($server, 25, $errno, $error, 60);
if($sockHandle === false)
{
exit('无法建立连接');
}
$response = fgets($sockHandle);
if(strpos($response, '220') !== 0)
{
exit('连接邮件服务器失败');
}
fwrite($sockHandle, "HELO test-user\r\n");
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('helo命令执行失败');
}
fwrite($sockHandle, "AUTH LOGIN\r\n");
$response = fgets($sockHandle);
if(strpos($response, '334') !== 0)
{
exit('AUTH LOGIN命令执行失败');
}
fwrite($sockHandle, base64_encode("******@163.com") . "\r\n");
$response = fgets($sockHandle);
if(strpos($response, '334') !== 0)
{
exit('账号验证失败');
}
// 163邮箱的客户端授权码
fwrite($sockHandle, base64_encode("******") . "\r\n");
$response = fgets($sockHandle);
if(strpos($response, '235') !== 0)
{
exit('密码验证失败');
}
fwrite($sockHandle, "MAIL FROM: <******@163.com>\r\n");
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('mail from命令执行失败');
}
fwrite($sockHandle, "RCPT TO: <******@qq.com>\r\n");
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('rcpt to命令执行失败');
}
fwrite($sockHandle, "DATA\r\n");
$response = fgets($sockHandle);
if(strpos($response, '354') !== 0)
{
exit('data命令执行失败');
}
fwrite($sockHandle, $email);
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('发送邮件失败');
}
fwrite($sockHandle, "QUIT\r\n");
echo '发送邮件成功' . PHP_EOL;
fclose($sockHandle);
}catch(Exception $e)
{
var_dump($e->getMessage());
var_dump($e->getTrace());
var_dump($e->getLine());
fclose($sockHandle);
}

通过命令行执行脚本文件:

v2-ac6196e7cd89e99e1ca00c50902fe1d8_720w.pngv2-6c4b480482a9f74321d41cccc53b9b66_720w.jpg


示例二: 发送携带单个附件的邮件

邮件携带附件时, 邮件头的格式类似 HTTP 请求中的上传文件时请求头的格式, 都需要在头部附加说明附件的内容和其他信息.

sendEmailWithAttachment.php

header('Content-type: text/html; charset=utf-8');
$server = 'smtp.163.com';
$errno = null;
$error = null;
// 设置邮件头
$email = 'Date: ' . date('j F G:i:s Y', time()) . "\r\n";
$email .= 'From: "哪里多" <***@163.com>' . "\r\n";
$email .= "To: \"二柱子\" <***@qq.com>\r\n";
$email .= "Subject: 测试邮件\r\n";
$email .= "Content-Type: multipart/mixed; boundary=----delimiter1----\r\n\r\n"; // 与post方式上传附件类似,需设定段体边界。邮件头和正文间空一行分割
 
$email .= "------delimiter1----\r\n"; // 段体开始边界格式:--{$boundary}
$email .= "Content-Type: multipart/alternative; boundary=----delimiter2----\r\n\r\n"; // 邮件正文段体边界
 
$email .= "------delimiter2----\r\n"; // 邮件正文部分段体
$email .= "Content-Type: text/plain; charset=UTF-8\r\n\r\n";
 
$email .= "邮件正文\r\n"; // 正文内容结束后空两行
$email .= "------delimiter2------\r\n\r\n"; // 正文部分结束边界,段体结束边界格式:--{$boundary}--
 
$email .= "------delimiter1----\r\n"; // 邮件附件段体设置,发送多个附件时,重复设置该部分段体
$email .= "Content-Type: application/octet-stream; name=\"1542533630(1).jpg\"\r\n";
$email .= "Content-Transfer-Encoding: base64\r\n";
$email .= "Content-Disposition: attachment; filename=\"test.jpg\"\r\n\r\n"; // 附件下载名称
 
$file = base64_encode(file_get_contents('./1542533630(1).jpg'));
$email .= $file . "\r\n\r\n";
 
$email .= "------delimiter1------\r\n"; // 邮件实体设置结束边界
$email .= ".\r\n"; // 邮件内容结束后以 . 命令表示邮件内容设置完成
try
{
// fsockopen()函数: 建立一个网络连接或Unix套接字, 返回一个文件句柄, 可以使用文件操作函数操作返回的资源
$sockHandle = fsockopen($server, 25, $errno, $error, 60);
if($sockHandle === false)
{
exit('无法建立连接');
}
$response = fgets($sockHandle);
if(strpos($response, '220') !== 0)
{
exit('连接邮件服务器失败');
}
fwrite($sockHandle, "HELO test-user\r\n");
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('helo命令执行失败');
}
fwrite($sockHandle, "AUTH LOGIN\r\n");
$response = fgets($sockHandle);
if(strpos($response, '334') !== 0)
{
exit('AUTH LOGIN命令执行失败');
}
fwrite($sockHandle, base64_encode("***@163.com") . "\r\n");
$response = fgets($sockHandle);
if(strpos($response, '334') !== 0)
{
exit('账号验证失败');
}
// 163邮箱的客户端授权码
fwrite($sockHandle, base64_encode("***") . "\r\n");
$response = fgets($sockHandle);
if(strpos($response, '235') !== 0)
{
exit('密码验证失败');
}
fwrite($sockHandle, "MAIL FROM: <***@163.com>\r\n");
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('mail from命令执行失败');
}
fwrite($sockHandle, "RCPT TO: <***@qq.com>\r\n");
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('rcpt to命令执行失败');
}
fwrite($sockHandle, "DATA\r\n");
$response = fgets($sockHandle);
if(strpos($response, '354') !== 0)
{
exit('data命令执行失败');
}
fwrite($sockHandle, $email);
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('发送邮件失败');
}
fwrite($sockHandle, "QUIT\r\n");
echo '发送邮件成功' . PHP_EOL;
fclose($sockHandle);
}catch(Exception $e)
{
var_dump($e->getMessage());
var_dump($e->getTrace());
var_dump($e->getLine());
fclose($sockHandle);
}


通过命令行运行脚本:

v2-ec7e078621b3b4d019dcc9a220d40bdb_720w.png


邮箱成功接收到邮件:

v2-beb10b40b80a628969966f8f588a9b64_720w.jpg


示例三: 发送携带多个附件的邮件

sendEmailWithMultiAttachment.php

header('Content-type: text/html; charset=utf-8');
$server = 'smtp.163.com';
$errno = null;
$error = null;
// 设置邮件头
$email = 'Date: ' . date('j F G:i:s Y', time()) . "\r\n";
$email .= 'From: "哪里多" <***@163.com>' . "\r\n";
$email .= "To: \"二柱子\" <***@qq.com>\r\n";
$email .= "Subject: 急报\r\n";
$email .= "Content-Type: multipart/mixed; boundary=----delimiter1----\r\n\r\n"; // 邮件头和正文间空一行分割
 
$email .= "------delimiter1----\r\n";
$email .= "Content-Type: multipart/alternative; boundary=----delimiter2----\r\n\r\n";
 
$email .= "------delimiter2----\r\n";
$email .= "Content-Type: text/plain; charset=UTF-8\r\n\r\n";
 
$email .= "见信速回\r\n"; // 正文内容结束后空两行
$email .= "------delimiter2------\r\n\r\n";
 
$email .= "------delimiter1----\r\n"; // 附件1段体
$email .= "Content-Type: application/octet-stream; name=\"baobiao1.jpg\"\r\n";
$email .= "Content-Transfer-Encoding: base64\r\n";
$email .= "Content-Disposition: attachment; filename=\"baobiao1.jpg\"\r\n\r\n"; // 附件下载名称
 
$file = base64_encode(file_get_contents('./1542533630(1).jpg'));
$email .= $file . "\r\n\r\n";
 
$email .= "------delimiter1----\r\n"; // 附件2段体
$email .= "Content-Type: application/octet-stream; name=\"baobiao2.jpg\"\r\n";
$email .= "Content-Transfer-Encoding: base64\r\n";
$email .= "Content-Disposition: attachment; filename=\"baobiao2.jpg\"\r\n\r\n"; // 附件下载名称
 
//$file = base64_encode(file_get_contents('./1542533630(1).jpg'));
$email .= $file . "\r\n\r\n";
 
$email .= "------delimiter1------\r\n";
$email .= ".\r\n"; // 邮件内容结束后以 . 命令标识结束
try
{
// fsockopen()函数: 建立一个网络连接或Unix套接字, 返回一个文件句柄, 可以使用文件操作函数操作返回的资源
$sockHandle = fsockopen($server, 25, $errno, $error, 60);
if($sockHandle === false)
{
exit('无法建立连接');
}
$response = fgets($sockHandle);
if(strpos($response, '220') !== 0)
{
exit('连接邮件服务器失败');
}
fwrite($sockHandle, "HELO test-user\r\n");
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('helo命令执行失败');
}
fwrite($sockHandle, "AUTH LOGIN\r\n");
$response = fgets($sockHandle);
if(strpos($response, '334') !== 0)
{
exit('AUTH LOGIN命令执行失败');
}
fwrite($sockHandle, base64_encode("***@163.com") . "\r\n");
$response = fgets($sockHandle);
if(strpos($response, '334') !== 0)
{
exit('账号验证失败');
}
// 163邮箱的客户端授权码
fwrite($sockHandle, base64_encode("***") . "\r\n");
$response = fgets($sockHandle);
if(strpos($response, '235') !== 0)
{
exit('密码验证失败');
}
fwrite($sockHandle, "MAIL FROM: <***@163.com>\r\n");
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('mail from命令执行失败');
}
fwrite($sockHandle, "RCPT TO: <***@qq.com>\r\n");
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
exit('rcpt to命令执行失败');
}
fwrite($sockHandle, "DATA\r\n");
$response = fgets($sockHandle);
if(strpos($response, '354') !== 0)
{
exit('data命令执行失败');
}
fwrite($sockHandle, $email);
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
echo $response;
exit('发送邮件失败');
}
fwrite($sockHandle, "QUIT\r\n");
echo '发送邮件成功' . PHP_EOL;
fclose($sockHandle);
}catch(Exception $e)
{
var_dump($e->getMessage());
var_dump($e->getTrace());
var_dump($e->getLine());
fclose($sockHandle);
}


通过命令行运行脚本:

v2-926f0d5b6bfbf7647e4ac1e04fa94bd8_720w.png


邮箱接收到邮件:

v2-79c09d5322cc6c6a733aa4cba768c2e2_720w.jpg


如果需要设置抄送项, 在邮件头中配置抄送项Cc即可, 如:

Cc: <抄送人1@qq.com>, <抄送人2@163.com>


然后通过执行命令RCPT TO设置抄送人:

fwrite($sockHandle, "RCPT TO: <抄送人2@163.com>\r\n");
$response = fgets($sockHandle);
if(strpos($response, '250') !== 0)
{
echo $response;
exit('抄送命令执行失败');
}


退信的处理


邮件内容不规范, 或相同内容重复发送时, 可能导致退信, 发送失败.

  1. 如果是重复内容反复发送导致的退信, 更换发送人账号即可.
  2. 也可以通过将发件人添加到收件人解决退信问题. 此时邮件头中To的配置项为:
To: 收件人1 <***@qq.com>, 发件人 <***@163.com>\r\n

 

在通过命令设置发件人时, 通过反复执行RCPT TO命令, 设置多个收件人.

fwrite($sockHandle, "RCPT TO:<收件人1@qq.com>\r\n");
    $response = fgets($sockHandle);
    if(strpos($response, '250') !== 0)
    {
    exit('收件人1命令执行失败');
    }
    fwrite($sockHandle, "RCPT TO: <发件人@163.com>\r\n");
    $response = fgets($sockHandle);
    if(strpos($response, '250') !== 0)
    {
    exit('收件人2命令执行失败');
    }


attachments-2020-04-epj7kUmt5ea636312cde3.jpg

  • 发表于 2020-04-27 09:34
  • 阅读 ( 561 )
  • 分类:PHP开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1135 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1316 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章