0

0

SMTP简介与PHP简单实现

php中文网

php中文网

发布时间:2016-06-20 12:27:18

|

1111人浏览过

|

来源于php中文网

原创

0.smtp工作过程简述

SMTP是客户和服务模型,之间用简单的命令,通过NVT ASCII通信。

以下 用 [S] 代表服务器,[C] 代表客户端。

先来看看我用QQ邮箱发送邮件后的一些信息(密码之类的被我修改了):

[S]220 smtp.qq.com Esmtp QQ Mail Server[C]EHLO localhost [S]250-smtp.qq.com 250-PIPELINING 250-SIZE 73400320 250-AUTH LOGIN PLAIN 250-AUTH=LOGIN 250-MAILCOMPRESS 250 8BITMIME[C]AUTH LOGIN [S]334 ABCDEFGHI[C]username [S]334 ABCDEFGHI[C]password [S]235 Authentication successful[C]MAIL FROM: [S]250 Ok[C]RCPT TO:  [S]250 Ok[C]RCPT TO:  [S]250 Ok[C]RCPT TO:  [S]250 Ok[C]DATA [S]354 End data with .[C]FROM:  TO:  CC:  BCC   Subject: Test mail Subject MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="[BOUNDARY:4f78098b1b3fb4f42ac473f8c86cbebe]>>>" --[BOUNDARY:4f78098b1b3fb4f42ac473f8c86cbebe]>>> Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: base64 BASE64编码的正文 --[BOUNDARY:4f78098b1b3fb4f42ac473f8c86cbebe]>>> Content-Type: image/x-icon Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="favicon.ico" BASE64编码的附件 --[BOUNDARY:4f78098b1b3fb4f42ac473f8c86cbebe]>>>-- . [S]250 Ok: queued as[C]QUIT [S]221 Bye

基本上就是有[S]先响应连接发出220开头的ASCII信息,对,每次[S]的回复都以一个三位码开头。然后[C]传递命令过去,等待[S]回复。

立即学习PHP免费学习笔记(深入)”;

这里需要注意的几点是

1.换行是用 CRLF也就是\r\n。

2.MIME用到来隔开正文和多个附件之间会插入一个用户定义的boundary分隔符。每部分以--boundary开头。只有文件结束时以--boundary--结尾。

3.邮件DATA结尾要用到 CRLF.CRLF 结尾,可以看到QQ的服务器也提示了这点。

最后有兴趣的可以去看下这些书,有命令的详解,我就是参考了这些:

1.《深入理解计算机网络》第11章 11.5节 电子邮件服务

伤心森林订单留言系统
伤心森林订单留言系统

功能简介:1.用户留言功能2.用户定货功能3.定制货货功能4.定制网页样式和其实设置(比如主页)5.强大的管理功能(现在的程序都是管理功能大于应用功能:)6.管理功能支持查看订货单,留言,分页,删除等功能管理页面:login.asp管理密码:admin

下载

2.《TCP/IP详解 卷1:协议》第28章 SMTP:简单邮件传送协议

以及在网上参考了一些网友的代码。

这里我还有一点疑惑,就是 EHLO或HELO后面跟的 究竟是什么,书上说“必须是完全合格的客户主机名”。可是我看有的网友传的是sendmail,而localhost感觉对于服务器也意义不大。不过我试后都通过了。

1. PHP简单地实现SMTP

首先定义一个Mail类,来处理邮件的一些信息。

    class Mail {        private $from;        private $to;        private $cc;        private $bcc;        private $type;        private $subject;        private $content;        private $related;        private $attachment;        /**        * @param from 发件人        * @param to 收件人 或 收件人数组        * @param subject 主题        * @param content 内容        * @param type 内容类型 html 或 plain,默认plain        * @param related 内容是否引用外部链接 默认FALSE        */        function __construct($from,$to,$subject,                            $content,$type='plain',$related=FALSE){            $this->from = $from;            $this->to = is_array($to) ? $to : [$to];            $this->cc = [];            $this->bcc = [];            $this->type = $type;            $this->subject = $subject;            $this->content = $content;            $this->related = $related;            $this->attachment = [];        }        /**        * @param to 收件人 或 收件人数组        */        function addTO($to){            if(is_array($to))                $this->to = array_merge($this->to,$to);            else array_push($this->to,$to);        }        /**        * @param cc 抄送人 或 抄送人数组        */        function addCC($cc){            if(is_array($cc))                $this->cc = array_merge($this->cc,$cc);            else array_push($this->cc,$cc);        }        /**        * @param bcc 秘密抄送人 或 秘密抄送人数组        */        function addBCC($bcc){            if(is_array($bcc))                $this->bcc = array_merge($this->bcc,$bcc);            else array_push($this->bcc,$bcc);        }        /**        * @param path 附件地址 或 附件地址数组        */        function addAttachment($path){            if(is_array($path))                $this->attachment = array_merge($this->attachment,$path);            else array_push($this->attachment,$path);        }        /**        * @param name 成员变量名        * @return 非数组成员变量值        */        function __get($name){            if(isset($this->$name) && !is_array($this->$name))                return $this->$name;            else user_error('Invalid Property: '.__CLASS__.'::'.$name);        }        /**        * @param name 数组型成员变量名        * @param visitor 遍历整个数组并调用之        */        function expose($name, $visitor){            if(isset($this->$name) && is_array($this->$name))                foreach($this->$name as $i)$visitor($i);            else user_error('Invalid Property: '.__CLASS__.'::'.$name);        }        /**        * @param name 数组型成员变量名        * @param caller 作用于数组的调用        * @return 返回调用后的返回值        */        function affect($name, $caller){            if(isset($this->$name) && is_array($this->$name))                return $caller($this->$name);            else user_error('Invalid Property: '.__CLASS__.'::'.$name);        }        /**        * @param name 数组型成员名        * @return 数组成员长度        */        function count($name){            if(isset($this->$name) && is_array($this->$name))                return count($this->$name);            else user_error('Invalid Property: '.__CLASS__.'::'.$name);        }    }

接着就是SMTPSender这个用于发送邮件的类:

    class SMTPSender {        private $host;        private $port;        private $username;        private $password;        private $security;        /**        * @param host 服务器地址        * @param port 服务器端口        * @param username 邮箱账户        * @param password 邮箱密码        * @param security 安全层 SSL SSL2 SSL3 TLS        */        function __construct($host,$port,                            $username,$password,                            $security=NULL){            $this->host = $host;            $this->port = $port;            $this->username = $username;            $this->password = $password;            $this->security = $security;        }        /**        * @param mail Mail对象        * @param timeout 连接超时,单位秒,默认10秒        * @return 错误信息,无错误返回NULL        */        function send($mail,$timeout=10){            $address = 'tcp://'.$this->host.':'.$this->port;            $socket = stream_socket_client($address,$errno,$errstr,$timeout);            if(!$socket)return $errno.' error:'.$errstr;            try {                //设置安全套接字                if(isset($this->security))                    if(!self::setSecurity($socket, $this->security))                        return 'set security failed';                //阻塞模式                if(!stream_set_blocking($socket,TRUE))                    return 'set stream blocking failed';                //获取服务器响应                $message = trim(fread($socket,1024));                if(substr($message,0,3) != '220')                    return 'Invalid Server: '.$message;                //发送命令给服务器                $command = self::makeCommand($this,$mail);                foreach($command as $i){                    $error = self::command($socket,$i[0],$i[1]);                    if($error != NULL)return $error;                }                return NULL;//成功            }catch(Exception $e){                return '[SMTP]Exception:'.$e->getMessage();            }finally{                stream_socket_shutdown($socket,STREAM_SHUT_WR);            }        }        /**        * @param socket 套接字        * @param command SMTP命令        * @param code 期待的SMTP返回码        * @return 错误信息,无错误返回NULL        */        private static function command($socket,$command,$code){            if(fwrite($socket,$command)){                $data = trim(fread($socket,1024));                if(!$data)return '[SMTP Server not tip]';                if(substr($data,0,3) == $code)return NULL;//成功                else return '[SMTP]Error: '.$data;            }else return '[SMTP] send command failed';        }        /**        * @param server SMTP服务器信息        * @param related 邮件是否引用外部链接        * @return 错误信息,无错误返回NULL        */        private static function makeCommand($info,$mail){            $command = [                ["EHLO localhost\r\n",'250'],                ["AUTH LOGIN\r\n",'334'],                [base64_encode($info->username)."\r\n",'334'],                [base64_encode($info->password)."\r\n",'235'],                ['MAIL FROM:<'.$mail->from.">\r\n",'250']            ];            $addRCPTTO = function($i)use(&$command){                array_push($command,['RCPT TO: <'.$i.">\r\n",'250']);            };            $mail->expose('to',$addRCPTTO);//收件人            $mail->expose('cc',$addRCPTTO);//抄送人            $mail->expose('bcc',$addRCPTTO);//秘密抄送人            array_push($command,["DATA\r\n",'354']);            array_push($command,[self::makeData($mail),'250']);            array_push($command,["QUIT\r\n",'221']);            return $command;        }        /**        * @param related 邮件是否引用外部链接        * @return 返回生成的DATA报文        */        private static function makeData($mail){            //邮件基本信息            $data = 'FROM: <'.$mail->from.">\r\n";//发件人            $merge = function($m){ return implode('>,<',$m); };            $data .= 'TO: <'.$mail->affect('to',$merge).">\r\n";//收件人组            if($mail->count('cc') != 0)//抄送人组                $data .= 'CC: <'.$mail->affect('cc',$merge).">\r\n";            if($mail->count('bcc') != 0)//秘密抄送人组                $data .= 'BCC: <'.$mail->affect('bcc',$merge).">\r\n";            $data .= "Subject: ".$mail->subject."\r\n";//主题            //设置MIME 块            $data .= "MIME-Version: 1.0\r\n";            $data .= 'Content-Type: multipart/';            $hasAttachment = $mail->count('attachment') != 0;            if($hasAttachment)$data .= "mixed;\r\n";            else if($mail->related)$data .= "related;\r\n";            else $data .= "alternative;\r\n";            $boundary = '[BOUNDARY:'.md5(uniqid()).']>>>';            $data .= "\tboundary=\"".$boundary."\"\r\n\r\n";            //正文内容            $data .= '--'.$boundary."\r\n";            $data .= 'Content-Type: text/'.$mail->type."; charset=utf-8\r\n";            $data .= "Content-Transfer-Encoding: base64\r\n\r\n";            $data .= base64_encode($mail->content)."\r\n\r\n";            //附件            if($hasAttachment)$mail->expose('attachment',function($i)use(&$data,$boundary){                if(!is_file($i))return;                $type = mime_content_type($i);                $name = basename($i);                $file = base64_encode(file_get_contents($i));                $data .= '--'.$boundary."\r\n";                $data .= 'Content-Type: '.$type."\r\n";                $data .= "Content-Transfer-Encoding: base64\r\n";                $data .= 'Content-Disposition: attachment; filename="'.$name."\"\r\n\r\n";                $data .= $file."\r\n\r\n";            });            //结束块 和 结束邮件            $data .= "--".$boundary."--\r\n\r\n.\r\n";            return $data;        }        /**        * @param socket 套接字        * @param type   安全层类型 SSL SSL2 SSL3 TLS        * @return 设置是否成功的BOOL值        */        private static function setSecurity($socket, $type){            $method = NULL;            if($type == 'SSL')$method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT;            else if($type == 'SSL2')$method = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;            else if($type == 'SSL3')$method = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;            else if($type == 'TLS')$method = STREAM_CRYPTO_METHOD_TLS_CLIENT;            if($method == NULL) return FALSE;            stream_socket_enable_crypto($socket,TRUE,$method);            return TRUE;        }    }

SMTPSender只有send这个成员函数是公开的。

下面我给出一个使用这两个类的例子,假设参数从$_POST传入:

$mail = new Mail(    $_POST['from'],    explode(';',$_POST['to']),    $_POST['subject'],    'adfdsgsgsdfsdfdsafsd!!!!!@@@@文本内容123456789');if(isset($_POST['cc']))$mail->addCC(explode(';',$_POST['cc']));if(isset($_POST['bcc']))$mail->addBCC(explode(';',$_POST['bcc']));$mail->addAttachment('./demo/favicon.ico');$sender = new SMTPSender(    $_POST['host'],$_POST['port'],    $_POST['username'],    $_POST['password'],    $_POST['security']);$error = $sender->send($mail);

希望这些对SMTP感兴趣的朋友有帮助。

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法
pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法

本专题系统整理pixiv网页版官网入口及登录访问方式,涵盖官网登录页面直达路径、在线阅读入口及快速进入方法说明,帮助用户高效找到pixiv官方网站,实现便捷、安全的网页端浏览与账号登录体验。

463

2026.02.13

微博网页版主页入口与登录指南_官方网页端快速访问方法
微博网页版主页入口与登录指南_官方网页端快速访问方法

本专题系统整理微博网页版官方入口及网页端登录方式,涵盖首页直达地址、账号登录流程与常见访问问题说明,帮助用户快速找到微博官网主页,实现便捷、安全的网页端登录与内容浏览体验。

135

2026.02.13

Flutter跨平台开发与状态管理实战
Flutter跨平台开发与状态管理实战

本专题围绕Flutter框架展开,系统讲解跨平台UI构建原理与状态管理方案。内容涵盖Widget生命周期、路由管理、Provider与Bloc状态管理模式、网络请求封装及性能优化技巧。通过实战项目演示,帮助开发者构建流畅、可维护的跨平台移动应用。

64

2026.02.13

TypeScript工程化开发与Vite构建优化实践
TypeScript工程化开发与Vite构建优化实践

本专题面向前端开发者,深入讲解 TypeScript 类型系统与大型项目结构设计方法,并结合 Vite 构建工具优化前端工程化流程。内容包括模块化设计、类型声明管理、代码分割、热更新原理以及构建性能调优。通过完整项目示例,帮助开发者提升代码可维护性与开发效率。

20

2026.02.13

Redis高可用架构与分布式缓存实战
Redis高可用架构与分布式缓存实战

本专题围绕 Redis 在高并发系统中的应用展开,系统讲解主从复制、哨兵机制、Cluster 集群模式及数据分片原理。内容涵盖缓存穿透与雪崩解决方案、分布式锁实现、热点数据优化及持久化策略。通过真实业务场景演示,帮助开发者构建高可用、可扩展的分布式缓存系统。

26

2026.02.13

c语言 数据类型
c语言 数据类型

本专题整合了c语言数据类型相关内容,阅读专题下面的文章了解更多详细内容。

29

2026.02.12

雨课堂网页版登录入口与使用指南_官方在线教学平台访问方法
雨课堂网页版登录入口与使用指南_官方在线教学平台访问方法

本专题系统整理雨课堂网页版官方入口及在线登录方式,涵盖账号登录流程、官方直连入口及平台访问方法说明,帮助师生用户快速进入雨课堂在线教学平台,实现便捷、高效的课程学习与教学管理体验。

14

2026.02.12

豆包AI网页版入口与智能创作指南_官方在线写作与图片生成使用方法
豆包AI网页版入口与智能创作指南_官方在线写作与图片生成使用方法

本专题汇总豆包AI官方网页版入口及在线使用方式,涵盖智能写作工具、图片生成体验入口和官网登录方法,帮助用户快速直达豆包AI平台,高效完成文本创作与AI生图任务,实现便捷智能创作体验。

524

2026.02.12

PostgreSQL性能优化与索引调优实战
PostgreSQL性能优化与索引调优实战

本专题面向后端开发与数据库工程师,深入讲解 PostgreSQL 查询优化原理与索引机制。内容包括执行计划分析、常见索引类型对比、慢查询优化策略、事务隔离级别以及高并发场景下的性能调优技巧。通过实战案例解析,帮助开发者提升数据库响应速度与系统稳定性。

53

2026.02.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
c语言项目php解释器源码分析探索
c语言项目php解释器源码分析探索

共7课时 | 0.4万人学习

nginx浅谈
nginx浅谈

共15课时 | 0.9万人学习

第二十四期_PHP8编程
第二十四期_PHP8编程

共86课时 | 3.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号