PHP实现支持SSL连接的SMTP邮件发送类

这篇文章主要介绍了PHP实现支持SSL连接的SMTP邮件发送类,实例分析了php实现smtp邮件发送类的原理与技巧,以及支持SSL连接的方法,需要的朋友可以参考下

本文实例讲述了PHP实现支持SSL连接的SMTP邮件发送类,分享给大家供大家参考。具体如下:

该实例代码测试过了gmail和QQ邮箱的SMTP,具体代码如下:

  1. <?php
  2. /**
  3. * 邮件发送类
  4. * 支持发送纯文本邮件和HTML格式的邮件,可以多收件人,多抄送,多秘密抄送,带附件(单个或多个附件),支持到服务器的ssl连接
  5. * 需要的php扩展:sockets、Fileinfo和openssl。
  6. * 编码格式是UTF-8,传输编码格式是base64
  7. * @example
  8. * $mail = new MySendMail();
  9. * $mail->setServer("smtp@126.com", "XXXXX@126.com", "XXXXX"); //设置smtp服务器,普通连接方式
  10. * $mail->setServer("smtp.gmail.com", "XXXXX@gmail.com", "XXXXX", 465, true); //设置smtp服务器,到服务器的SSL连接
  11. * $mail->setFrom("XXXXX"); //设置发件人
  12. * $mail->setReceiver("XXXXX"); //设置收件人,多个收件人,调用多次
  13. * $mail->setCc("XXXX"); //设置抄送,多个抄送,调用多次
  14. * $mail->setBcc("XXXXX"); //设置秘密抄送,多个秘密抄送,调用多次
  15. * $mail->addAttachment("XXXX"); //添加附件,多个附件,调用多次
  16. * $mail->setMail("test", "<b>test</b>"); //设置邮件主题、内容
  17. * $mail->sendMail(); //发送
  18. */
  19. class MySendMail {
  20. /**
  21. * @var string 邮件传输代理用户名
  22. * @access protected
  23. */
  24. protected $_userName;
  25. /**
  26. * @var string 邮件传输代理密码
  27. * @access protected
  28. */
  29. protected $_password;
  30. /**
  31. * @var string 邮件传输代理服务器地址
  32. * @access protected
  33. */
  34. protected $_sendServer;
  35. /**
  36. * @var int 邮件传输代理服务器端口
  37. * @access protected
  38. */
  39. protected $_port;
  40. /**
  41. * @var string 发件人
  42. * @access protected
  43. */
  44. protected $_from;
  45. /**
  46. * @var array 收件人
  47. * @access protected
  48. */
  49. protected $_to = array();
  50. /**
  51. * @var array 抄送
  52. * @access protected
  53. */
  54. protected $_cc = array();
  55. /**
  56. * @var array 秘密抄送
  57. * @access protected
  58. */
  59. protected $_bcc = array();
  60. /**
  61. * @var string 主题
  62. * @access protected
  63. */
  64. protected $_subject;
  65. /**
  66. * @var string 邮件正文
  67. * @access protected
  68. */
  69. protected $_body;
  70. /**
  71. * @var array 附件
  72. * @access protected
  73. */
  74. protected $_attachment = array();
  75. /**
  76. * @var reource socket资源
  77. * @access protected
  78. */
  79. protected $_socket;
  80. /**
  81. * @var reource 是否是安全连接
  82. * @access protected
  83. */
  84. protected $_isSecurity;
  85. /**
  86. * @var string 错误信息
  87. * @access protected
  88. */
  89. protected $_errorMessage;
  90. /**
  91. * 设置邮件传输代理,如果是可以匿名发送有邮件的服务器,只需传递代理服务器地址就行
  92. * @access public
  93. * @param string $server 代理服务器的ip或者域名
  94. * @param string $username 认证账号
  95. * @param string $password 认证密码
  96. * @param int $port 代理服务器的端口,smtp默认25号端口
  97. * @param boolean $isSecurity 到服务器的连接是否为安全连接,默认false
  98. * @return boolean
  99. */
  100. public function setServer($server, $username="", $password="", $port=25, $isSecurity=false) {
  101. $this->_sendServer = $server;
  102. $this->_port = $port;
  103. $this->_isSecurity = $isSecurity;
  104. $this->_userName = emptyempty($username) ? "" : base64_encode($username);
  105. $this->_password = emptyempty($password) ? "" : base64_encode($password);
  106. return true;
  107. }
  108. /**
  109. * 设置发件人
  110. * @access public
  111. * @param string $from 发件人地址
  112. * @return boolean
  113. */
  114. public function setFrom($from) {
  115. $this->_from = $from;
  116. return true;
  117. }
  118. /**
  119. * 设置收件人,多个收件人,调用多次.
  120. * @access public
  121. * @param string $to 收件人地址
  122. * @return boolean
  123. */
  124. public function setReceiver($to) {
  125. $this->_to[] = $to;
  126. return true;
  127. }
  128. /**
  129. * 设置抄送,多个抄送,调用多次.
  130. * @access public
  131. * @param string $cc 抄送地址
  132. * @return boolean
  133. */
  134. public function setCc($cc) {
  135. $this->_cc[] = $cc;
  136. return true;
  137. }
  138. /**
  139. * 设置秘密抄送,多个秘密抄送,调用多次
  140. * @access public
  141. * @param string $bcc 秘密抄送地址
  142. * @return boolean
  143. */
  144. public function setBcc($bcc) {
  145. $this->_bcc[] = $bcc;
  146. return true;
  147. }
  148. /**
  149. * 设置邮件附件,多个附件,调用多次
  150. * @access public
  151. * @param string $file 文件地址
  152. * @return boolean
  153. */
  154. public function addAttachment($file) {
  155. if(!file_exists($file)) {
  156. $this->_errorMessage = "file " . $file . " does not exist.";
  157. return false;
  158. }
  159. $this->_attachment[] = $file;
  160. return true;
  161. }
  162. /**
  163. * 设置邮件信息
  164. * @access public
  165. * @param string $body 邮件主题
  166. * @param string $subject 邮件主体内容,可以是纯文本,也可是是HTML文本
  167. * @return boolean
  168. */
  169. public function setMail($subject, $body) {
  170. $this->_subject = base64_encode($subject);
  171. $this->_body = base64_encode($body);
  172. return true;
  173. }
  174. /**
  175. * 发送邮件
  176. * @access public
  177. * @return boolean
  178. */
  179. public function sendMail() {
  180. $command = $this->getCommand();
  181. $this->_isSecurity ? $this->socketSecurity() : $this->socket();
  182. foreach ($command as $value) {
  183. $result = $this->_isSecurity ? $this->sendCommandSecurity($value[0], $value[1]) : $this->sendCommand($value[0], $value[1]);
  184. if($result) {
  185. continue;
  186. }
  187. else{
  188. return false;
  189. }
  190. }
  191. //其实这里也没必要关闭,smtp命令:QUIT发出之后,服务器就关闭了连接,本地的socket资源会自动释放
  192. $this->_isSecurity ? $this->closeSecutity() : $this->close();
  193. return true;
  194. }
  195. /**
  196. * 返回错误信息
  197. * @return string
  198. */
  199. public function error(){
  200. if(!isset($this->_errorMessage)) {
  201. $this->_errorMessage = "";
  202. }
  203. return $this->_errorMessage;
  204. }
  205. /**
  206. * 返回mail命令
  207. * @access protected
  208. * @return array
  209. */
  210. protected function getCommand() {
  211. $separator = "----=_Part_" . md5($this->_from . time()) . uniqid(); //分隔符
  212. $command = array(
  213. array("HELO sendmail\r\n", 250)
  214. );
  215. if(!emptyempty($this->_userName)){
  216. $command[] = array("AUTH LOGIN\r\n", 334);
  217. $command[] = array($this->_userName . "\r\n", 334);
  218. $command[] = array($this->_password . "\r\n", 235);
  219. }
  220. //设置发件人
  221. $command[] = array("MAIL FROM: <" . $this->_from . ">\r\n", 250);
  222. $header = "FROM: <" . $this->_from . ">\r\n";
  223. //设置收件人
  224. if(!emptyempty($this->_to)) {
  225. $count = count($this->_to);
  226. if($count == 1){
  227. $command[] = array("RCPT TO: <" . $this->_to[0] . ">\r\n", 250);
  228. $header .= "TO: <" . $this->_to[0] .">\r\n";
  229. }
  230. else{
  231. for($i=0; $i<$count; $i++){
  232. $command[] = array("RCPT TO: <" . $this->_to[$i] . ">\r\n", 250);
  233. if($i == 0){
  234. $header .= "TO: <" . $this->_to[$i] .">";
  235. }
  236. elseif($i + 1 == $count){
  237. $header .= ",<" . $this->_to[$i] .">\r\n";
  238. }
  239. else{
  240. $header .= ",<" . $this->_to[$i] .">";
  241. }
  242. }
  243. }
  244. }
  245. //设置抄送
  246. if(!emptyempty($this->_cc)) {
  247. $count = count($this->_cc);
  248. if($count == 1){
  249. $command[] = array("RCPT TO: <" . $this->_cc[0] . ">\r\n", 250);
  250. $header .= "CC: <" . $this->_cc[0] .">\r\n";
  251. }
  252. else{
  253. for($i=0; $i<$count; $i++){
  254. $command[] = array("RCPT TO: <" . $this->_cc[$i] . ">\r\n", 250);
  255. if($i == 0){
  256. $header .= "CC: <" . $this->_cc[$i] .">";
  257. }
  258. elseif($i + 1 == $count){
  259. $header .= ",<" . $this->_cc[$i] .">\r\n";
  260. }
  261. else{
  262. $header .= ",<" . $this->_cc[$i] .">";
  263. }
  264. }
  265. }
  266. }
  267. //设置秘密抄送
  268. if(!emptyempty($this->_bcc)) {
  269. $count = count($this->_bcc);
  270. if($count == 1) {
  271. $command[] = array("RCPT TO: <" . $this->_bcc[0] . ">\r\n", 250);
  272. $header .= "BCC: <" . $this->_bcc[0] .">\r\n";
  273. }
  274. else{
  275. for($i=0; $i<$count; $i++){
  276. $command[] = array("RCPT TO: <" . $this->_bcc[$i] . ">\r\n", 250);
  277. if($i == 0){
  278. $header .= "BCC: <" . $this->_bcc[$i] .">";
  279. }
  280. elseif($i + 1 == $count){
  281. $header .= ",<" . $this->_bcc[$i] .">\r\n";
  282. }
  283. else{
  284. $header .= ",<" . $this->_bcc[$i] .">";
  285. }
  286. }
  287. }
  288. }
  289. //主题
  290. $header .= "Subject: =?UTF-8?B?" . $this->_subject ."?=\r\n";
  291. if(isset($this->_attachment)) {
  292. //含有附件的邮件头需要声明成这个
  293. $header .= "Content-Type: multipart/mixed;\r\n";
  294. }
  295. elseif(false){
  296. //邮件体含有图片资源的,且包含的图片在邮件内部时声明成这个,如果是引用的远程图片,就不需要了
  297. $header .= "Content-Type: multipart/related;\r\n";
  298. }
  299. else{
  300. //html或者纯文本的邮件声明成这个
  301. $header .= "Content-Type: multipart/alternative;\r\n";
  302. }
  303. //邮件头分隔符
  304. $header .= "\t" . 'boundary="' . $separator . '"';
  305. $header .= "\r\nMIME-Version: 1.0\r\n";
  306. //这里开始是邮件的body部分,body部分分成几段发送
  307. $header .= "\r\n--" . $separator . "\r\n";
  308. $header .= "Content-Type:text/html; charset=utf-8\r\n";
  309. $header .= "Content-Transfer-Encoding: base64\r\n\r\n";
  310. $header .= $this->_body . "\r\n";
  311. $header .= "--" . $separator . "\r\n";
  312. //加入附件
  313. if(!emptyempty($this->_attachment)){
  314. $count = count($this->_attachment);
  315. for($i=0; $i<$count; $i++){
  316. $header .= "\r\n--" . $separator . "\r\n";
  317. $header .= "Content-Type: " . $this->getMIMEType($this->_attachment[$i]) . '; name="=?UTF-8?B?' . base64_encode( basename($this->_attachment[$i]) ) . '?="' . "\r\n";
  318. $header .= "Content-Transfer-Encoding: base64\r\n";
  319. $header .= 'Content-Disposition: attachment; filename="=?UTF-8?B?' . base64_encode( basename($this->_attachment[$i]) ) . '?="' . "\r\n";
  320. $header .= "\r\n";
  321. $header .= $this->readFile($this->_attachment[$i]);
  322. $header .= "\r\n--" . $separator . "\r\n";
  323. }
  324. }
  325. //结束邮件数据发送
  326. $header .= "\r\n.\r\n";
  327. $command[] = array("DATA\r\n", 354);
  328. $command[] = array($header, 250);
  329. $command[] = array("QUIT\r\n", 221);
  330. return $command;
  331. }
  332. /**
  333. * 发送命令
  334. * @access protected
  335. * @param string $command 发送到服务器的smtp命令
  336. * @param int $code 期望服务器返回的响应吗
  337. * @return boolean
  338. */
  339. protected function sendCommand($command, $code) {
  340. echo 'Send command:' . $command . ',expected code:' . $code . '<br />';
  341. //发送命令给服务器
  342. try{
  343. if(socket_write($this->_socket, $command, strlen($command))){
  344. //当邮件内容分多次发送时,没有$code,服务器没有返回
  345. if(emptyempty($code)) {
  346. return true;
  347. }
  348. //读取服务器返回
  349. $data = trim(socket_read($this->_socket, 1024));
  350. echo 'response:' . $data . '<br /><br />';
  351. if($data) {
  352. $pattern = "/^".$code."+?/";
  353. if(preg_match($pattern, $data)) {
  354. return true;
  355. }
  356. else{
  357. $this->_errorMessage = "Error:" . $data . "|**| command:";
  358. return false;
  359. }
  360. }
  361. else{
  362. $this->_errorMessage = "Error:" . socket_strerror(socket_last_error());
  363. return false;
  364. }
  365. }
  366. else{
  367. $this->_errorMessage = "Error:" . socket_strerror(socket_last_error());
  368. return false;
  369. }
  370. }catch(Exception $e) {
  371. $this->_errorMessage = "Error:" . $e->getMessage();
  372. }
  373. }
  374. /**
  375. * 安全连接发送命令
  376. * @access protected
  377. * @param string $command 发送到服务器的smtp命令
  378. * @param int $code 期望服务器返回的响应吗
  379. * @return boolean
  380. */
  381. protected function sendCommandSecurity($command, $code) {
  382. echo 'Send command:' . $command . ',expected code:' . $code . '<br />';
  383. try {
  384. if(fwrite($this->_socket, $command)){
  385. //当邮件内容分多次发送时,没有$code,服务器没有返回
  386. if(emptyempty($code)) {
  387. return true;
  388. }
  389. //读取服务器返回
  390. $data = trim(fread($this->_socket, 1024));
  391. echo 'response:' . $data . '<br /><br />';
  392. if($data) {
  393. $pattern = "/^".$code."+?/";
  394. if(preg_match($pattern, $data)) {
  395. return true;
  396. }
  397. else{
  398. $this->_errorMessage = "Error:" . $data . "|**| command:";
  399. return false;
  400. }
  401. }
  402. else{
  403. return false;
  404. }
  405. }
  406. else{
  407. $this->_errorMessage = "Error: " . $command . " send failed";
  408. return false;
  409. }
  410. }catch(Exception $e) {
  411. $this->_errorMessage = "Error:" . $e->getMessage();
  412. }
  413. }
  414. /**
  415. * 读取附件文件内容,返回base64编码后的文件内容
  416. * @access protected
  417. * @param string $file 文件
  418. * @return mixed
  419. */
  420. protected function readFile($file) {
  421. if(file_exists($file)) {
  422. $file_obj = file_get_contents($file);
  423. return base64_encode($file_obj);
  424. }
  425. else {
  426. $this->_errorMessage = "file " . $file . " dose not exist";
  427. return false;
  428. }
  429. }
  430. /**
  431. * 获取附件MIME类型
  432. * @access protected
  433. * @param string $file 文件
  434. * @return mixed
  435. */
  436. protected function getMIMEType($file) {
  437. if(file_exists($file)) {
  438. $mime = mime_content_type($file);
  439. /*if(! preg_match("/gif|jpg|png|jpeg/", $mime)){
  440. $mime = "application/octet-stream";
  441. }*/
  442. return $mime;
  443. }
  444. else {
  445. return false;
  446. }
  447. }
  448. /**
  449. * 建立到服务器的网络连接
  450. * @access protected
  451. * @return boolean
  452. */
  453. protected function socket() {
  454. //创建socket资源
  455. $this->_socket = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
  456. if(!$this->_socket) {
  457. $this->_errorMessage = socket_strerror(socket_last_error());
  458. return false;
  459. }
  460. socket_set_block($this->_socket);//设置阻塞模式
  461. //连接服务器
  462. if(!socket_connect($this->_socket, $this->_sendServer, $this->_port)) {
  463. $this->_errorMessage = socket_strerror(socket_last_error());
  464. return false;
  465. }
  466. $str = socket_read($this->_socket, 1024);
  467. if(!preg_match("/220+?/", $str)){
  468. $this->_errorMessage = $str;
  469. return false;
  470. }
  471. return true;
  472. }
  473. /**
  474. * 建立到服务器的SSL网络连接
  475. * @access protected
  476. * @return boolean
  477. */
  478. protected function socketSecurity() {
  479. $remoteAddr = "tcp://" . $this->_sendServer . ":" . $this->_port;
  480. $this->_socket = stream_socket_client($remoteAddr, $errno, $errstr, 30);
  481. if(!$this->_socket){
  482. $this->_errorMessage = $errstr;
  483. return false;
  484. }
  485. //设置加密连接,默认是ssl,如果需要tls连接,可以查看php手册stream_socket_enable_crypto函数的解释
  486. stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
  487. stream_set_blocking($this->_socket, 1); //设置阻塞模式
  488. $str = fread($this->_socket, 1024);
  489. if(!preg_match("/220+?/", $str)){
  490. $this->_errorMessage = $str;
  491. return false;
  492. }
  493. return true;
  494. }
  495. /**
  496. * 关闭socket
  497. * @access protected
  498. * @return boolean
  499. */
  500. protected function close() {
  501. if(isset($this->_socket) && is_object($this->_socket)) {
  502. $this->_socket->close();
  503. return true;
  504. }
  505. $this->_errorMessage = "No resource can to be close";
  506. return false;
  507. }
  508. /**
  509. * 关闭安全socket
  510. * @access protected
  511. * @return boolean
  512. */
  513. protected function closeSecutity() {
  514. if(isset($this->_socket) && is_object($this->_socket)) {
  515. stream_socket_shutdown($this->_socket, STREAM_SHUT_WR);
  516. return true;
  517. }
  518. $this->_errorMessage = "No resource can to be close";
  519. return false;
  520. }
  521. }