Mar26

PHP 中的内存管理【转载】

Author: Tracy Peterson   Click: 10703   Comments: 1 Category: php  Tag: php,内存

PHP V5.2  为 memory_get_usage() 和 memory_get_peak_usage() 提供了一个新参数,这两个函数允许查看内存使用量。说明中提及的新布尔值是 real_size。通过调用函数 memory_get_usage($real);(其中 $real = true),结果将为调用时系统中实际分配的内存大小,包括内存管理器开销。如果不使用标记组,则返回的数据将只包括在运行脚本内使用的内存,减去内存管理器开销。
memory_get_usage() 和 memory_get_peak_usage() 的不同之处在于后者将返回到目前为止调用它的运行进程的最高内存量,而前者只返回执行时的使用量。


  
PHP V5.2:开始

2006 年 11 月发布了 PHP V5.2,它包括许多新增功能和错误修正。它废止了 5.1 版并被推荐给所有 PHP V5 用户进行升级。我最喜欢的实验室环境 —— Windows®、Apache、MySQL、PHP (WAMP) —— 已经被引入了 V5.2 的新软件包中。您将在那里找到在 Windows® XP 或 2003 计算机上安装 PHP V5.2、MySQL 和 Apache 的应用程序。您可以十分轻松地进行安装,它有很多不错的小的管理优点,并且我十分诚恳地推荐使用它。

虽然对于 Windows 用户来说,这是最简单的软件包,但是在 Linux 上配置 PHP 时您需要添加以下代码:--memory-limit-enabled(适用于您服务器的任何其他选项除外)。不过,在 Windows 下,提供了一个解决此问题的函数。

PHP V5.2 中有很多改进之处,并且一个至关重要的领域是内存管理。从 README.ZEND_MM 中准确地引述就是:“新内存管理器(PHP5.2 以及更高版本)的目标是减少内存分配开销并加速内存管理。”

下面是 V5.2 发行说明中的一些关键内容:

删除了不必要的 --disable-zend-memory-manager 配置选项
添加了 --enable-malloc-mm 配置选项,调试构建时此配置选项将被默认启用以允许使用内部和外部内存调试程序
允许使用 ZEND_MM_MEM_TYPE 和 ZEND_MM_SEG_SIZE 环境变量调整内存管理器
为了理解这些新增功能的含义,我们需要深入研究内存管理中的艺术,并考虑为什么分配开销和运行速度是大问题。

 

 


为什么进行内存管理?

计算中开发最快速的一项技术是内存和数据存储,它们是受不断增加速度和存储大小这样持续的需求而驱动的。早期的计算机使用卡作为内存,然后转向了芯片技术。您能想象在只有 1 KB RAM 内存的计算机工作的情景吗?很多早期的计算机程序员就曾使用过。这些先驱者很快就意识到,要在技术限制下工作,他们将必须细心地用琐碎的命令避免系统过载。

身为 PHP 开发人员,与使用 C++ 或其他更严格的语言编码的同事相比,我们所在的环境更方便进行编码。在我们的世界里,我们自己不必担心如何处理系统内存,因为 PHP 将为我们处理这个问题。但是,在其他编程领域里,负责任的编码人员将使用各种函数确保执行的命令不会覆盖其他一些程序数据 —— 因而,破坏了程序的运行。

内存管理通常是由来自编码人员的请求处理的,以分配和释放内存块。分配块 可以保存任何类型的数据,并且此过程将为该数据隔开一定量的内存,并当操作需要访问数据时为应用程序提供访问方法。人们期望程序在完成任何操作后释放分配的内存,并允许系统和其他程序员使用该内存。如果程序没有把内存释放回系统,则称为内存泄露。

泄露是任何运行程序都存在的普遍问题,并且某种程度内通常是可以接受的,尤其是当我们知道运行程序将立即终止并释放默认分配给程序的所有内存。

由于随机运行和终止程序,像几乎所有客户机应用程序一样,这是个问题。期望服务器应用程序不确定地运行而不终止或重新启动,这使得内存管理对于服务器守护程序编程绝对的至关重要。在长时间运行的程序中,即使一个小的泄露最后都将发展为系统衰弱问题,因为内存块已被使用并且永远不被释放。

 

 


长期考虑

正如使用任何语言编写一样,用 PHP 编写的永久性服务器守护程序有很多可能的用途。但是当我们出于这些目的开始使用 PHP 时,我们也必须考虑内存使用情况。

解析大量数据或可能隐藏无限次循环的脚本都趋于消耗大量内存。很明显,一旦内存被耗尽,服务器的性能就降低,因此在执行脚本时我们还必须注意内存的使用情况。虽然我们可以通过启用系统监视器来简单观察内存的使用量,但是它不会告诉我们比整个系统内存状态更有用的任何内容。有时我们不止需要帮助进行故障检修或优化的内容,而有时我们只是需要更多详细信息。

获得脚本执行内容的透明性的一种方法是使用内部或外部调试器。内部调试器 是呈现为执行脚本的相同的进程。从操作系统的角度考虑的独立进程是外部调试器。使用调试器进行内存分析类似于任何一种情况,但是使用了不同的方法访问内存。内部调试器对运行进程所在的内存空间具有直接访问权,而外部调试器将通过套接字访问内存。

有许多方法和可用的调试服务器(外部)和库(内部)可用于辅助开发。为了准备好对 PHP 安装进行调试,可以使用新提供的 --enable-malloc-mm,它在 DEBUG 构建中默认被启用。这使环境变量 USE_ZEND_ALLOC 可用于允许在运行时选择 malloc 或 emalloc 内存分配。使用 malloc-type 内存分配将允许外部调试器观察内存使用情况,而 emalloc 分配将使用 Zend 内存管理器抽象,要求进行内部调试。

 

 


PHP 中的内存管理函数

除了使内存管理器更灵活更透明之外,PHP V5.2 还为 memory_get_usage() 和 memory_get_peak_usage() 提供了一个新参数,这两个函数允许查看内存使用量。说明中提及的新布尔值是 real_size。通过调用函数 memory_get_usage($real);(其中 $real = true),结果将为调用时系统中实际分配的内存大小,包括内存管理器开销。如果不使用标记组,则返回的数据将只包括在运行脚本内使用的内存,减去内存管理器开销。

memory_get_usage() 和 memory_get_peak_usage() 的不同之处在于后者将返回到目前为止调用它的运行进程的最高内存量,而前者只返回执行时的使用量。

对于 memory_get_usage(),php.net 提供了清单 1 中的代码片段。


清单 1. memory_get_usage() 示例
               
 


在这个简单示例中,我们首先回转了直接调用 memory_get_usage() 的结果,代码注释中显示可能在作者的系统中有 36640 字节的常见结果。然后我们使用 4,242 个 “Hello” 副本来装载 $a 并再次运行函数。图 1 中可以看到此简单应用的输出。


图 1. memory_get_usage() 的示例输出
 memory_get_usage() 的示例输出


没有 memory_get_peak_usage() 的示例,因为两者十分相似,语法是相同的。但是,对于清单 1 中的示例代码,将只有一个结果,即当时的最高内存使用量。让我们看一看清单 2。


清单 2. memory_get_peak_usage() 示例
               
 


清单 2 中的代码跟图 1 一样,但是 memory_get_usage() 已经替换为 memory_get_peak_usage()。在我们用 4242 个 “Hello” 副本填充 $a 之前,输出都不会有多大更改。内存跳升至 57960,表示到目前为止的峰值。当检查内存使用量峰值时,得到了目前为止的最高值,因此所有进一步调用都将得到 57960,直至我们处理的操作比处理 $a 使用的内存更多(参见图 2)。


图 2. memory_get_peak_usage() 的示例输出
 memory_get_peak_usage() 的示例输出


限制内存使用

确保托管应用程序的服务器不过载的一种方法是限制 PHP 执行的任何脚本使用的内存量。这根本不是我们应当执行的操作,但由于 PHP 是一种松散类型的语言,并且是在运行时解析的,因此我们有时会获得在释放到生产应用程序中后编写得很差的脚本。这些脚本可能执行循环,也可能打开一张长的文件列表,忘记在打开新文件之前先关闭当前文件。无论在哪一种情况下,编写很差的脚本可能在您知道之前以消耗大量内存告终。

在 PHP.INI 中,您可以使用配制参数 memory_limit 来指定任何脚本能够在系统中运行的最大内存使用量。这不是对于 V5.2 的特定更改,但是内存管理器及其使用的任何讨论都值得至少快速查看一次这个特性。它还精心地引导我使用内存管理器的最后几个新功能:环境变量。

 

 


调整内存管理器

最后,在不能做完美主义者但是又完全符合自己目的的情况下怎样编程?新环境变量 ZEND_MM_MEM_TYPE 和 ZEND_MM_SEG_SIZE 正好可以满足您的需求。

当内存管理器分配大型内存块时,它是安装 ZEND_MM_SEG_SIZE 变量中列出的预定大小执行操作的。这些内存块的默认分区大小为每块 256 KB,但是您可以调整这些分区大小以满足特殊需求。例如,如果您注意到最常用的一个脚本中的操作导致大量的内存浪费,则可以将此大小调整为更接近匹配脚本需求的值,减少分配的内存量但剩下的内存量仍然为零。在正确的条件下,此类谨慎的配制调整可能造成巨大差别。

 

 


在 Windows 中检索内存使用情况

如果具有预构建的 PHP Windows 二进制代码,而没有在构建时使用 --enable-memory-limit 选项,则需要先浏览此部分然后再继续。对于 Linux®,配置 PHP 构建时用 --enable-memory-limit 选项构建 PHP。

要使用 Windows 二进制代码检索内存使用情况,请创建以下函数。


清单 3. 在 Windows 下获得内存使用情况
               
 


将结果保存到名为 function.php 的文件。现在您只能将此文件包含在需要使用它的脚本中。

 

 


 
 


动手实践

让我们来看一看使用这些设置的实际示例给我们带来的好处。可能有很多次您都想知道为什么在脚本的末尾没有正确分配内存。原因是因为一些函数本身导致了内存泄露,尤其是在仅使用内置 PHP 函数的情况下。在这里,您将了解如何发现此类问题。并且为了开始进行内存泄露查找的征战,您将创建一个测试 MySQL 数据库,如清单 4 所示。


清单 4. 创建测试数据库


这将创建一个带有 ID 字段和数据字段的简单表。

在下一张清单中,想象我们坚韧不拔的程序员正在执行一些 MySQL 函数,特别是使用 mysql_query() 将结果应用到变量。当他这样做时,他将注意到即使调用 mysql_free_result(),一些内存也不会被释放,导致内存使用量随着 Apache 进程不断增长(参见清单 5)。


清单 5. 内存泄露检测示例
[code="php"]
 for ( $x=0; $x<300; $x++ ) {  
  $db = mysql_connect("localhost", "root", "test");   
 mysql_select_db("test");   
 $sql  = "SELECT data FROM test";    
$result = mysql_query($sql); // The operation suspected of leaking    mysql_free_result($result);    mysql_close($db);
}[/code]
 


清单 5 是在任何位置都可能使用的简单 MySQL 数据库操作。在运行脚本时,我们注意到一些与内存使用量相关的奇怪行为并需要将其检查出来。为了使用内存管理函数以使我们可以检验发生错误的位置,我们将使用以下代码。


清单 6. 定标查找错误的示例

[code="php"]
if( !function_exists('memory_get_usage') ){
 include('function.php');
}
echo "At the start we're using (in bytes): ".memory_get_usage() ."\n"; $db = mysql_connect("localhost", "user", "password");
mysql_select_db("memory_test");
echo "After connecting, we're using (in bytes): ".memory_get_usage()."\n";
 for ( $x=0; $x<10; $x++ ) {
$sql = "SELECT data FROM leak_test WHERE id='".$x."'";
$result = mysql_query($sql); // The operation
// suspected of leaking.
echo "After query #$x, we're using (in bytes): ".memory_get_usage()."\n";
mysql_free_result($result);
 echo "After freeing result $x, we're using (in bytes): ".memory_get_usage()."\n";
}
mysql_close($db);
echo "After closing the connection, we're using (in bytes):".memory_get_usage()."\n";
echo "Peak memory usage for the script (in bytes):". memory_get_peak_usage();
?>
[/code]

注:按照定义的时间间隔检查当前内存使用量。在下面的输出中,通过显示我们的脚本一直在为函数分配内存,并且在应当释放的时候没有释放内存,从而提供对内存泄露的实际测试,您可以看到每次调用时内存使用量如何增长。


清单 7. 测试脚本输出   At the start we're using (in bytes): 63216After connecting, we're using (in bytes): 64436After query #0, we're using (in bytes): 64760After freeing result 0, we're using (in bytes): 64828After query #1, we're using (in bytes): 65004After freeing result 1, we're using (in bytes): 65080After query #2, we're using (in bytes): 65160After freeing result 2, we're using (in bytes): 65204After query #3, we're using (in bytes): 65284After freeing result 3, we're using (in bytes): 65328After query #4, we're using (in bytes): 65408After freeing result 4, we're using (in bytes): 65452After query #5, we're using (in bytes): 65532After freeing result 5, we're using (in bytes): 65576After query #6, we're using (in bytes): 65656After freeing result 6, we're using (in bytes): 65700After query #7, we're using (in bytes): 65780After freeing result 7, we're using (in bytes): 65824After query #8, we're using (in bytes): 65904After freeing result 8, we're using (in bytes): 65948After query #9, we're using (in bytes): 66028After freeing result 9, we're using (in bytes): 66072After closing the connection, we're using (in bytes): 65108Peak memory usage for the script (in bytes): 88748


我们所做的操作是发现了执行脚本时出现的一些可疑操作,然后调整脚本使其给我们提供一些可理解的反馈。我们再次运行了脚本,在每次迭代期间使用 memory_get_usage() 查看内存使用量的变化。根据分配的内存值的增长情况,暗示了我们用脚本在某个位置建立了一个漏洞。由于 mysql_free_result() 函数不释放内存,因此我们可以认为 mysql_query() 并未正确分配内存。

转载至:http://www.ibm.com/developerworks/cn/opensource/os-php-v521/index.html

 

Mar24

分表处理设计思想和实现

Author: heiyeluren (黑夜路人)  Click: 6345   Comments: 0 Category: 架构  Tag: 分表
一、概述
分表是个目前算是比较炒的比较流行的概念,特别是在大负载的情况下,分表是一个良好分散数据库压力的好方法。
首先要了解为什么要分表,分表的好处是什么。我们先来大概了解以下一个数据库执行SQL的过程:
接收到SQL --> 放入SQL执行队列 --> 使用分析器分解SQL --> 按照分析结果进行数据的提取或者修改 --> 返回处理结果
当然,这个流程图不一定正确,这只是我自己主观意识上这么我认为。那么这个处理过程当中,最容易出现问题的是什么?就是说,如果前一个SQL没有执行完毕的话,后面的SQL是不会执行的,因为为了保证数据的完整性,必须对数据表文件进行锁定,包括共享锁和独享锁两种锁定。共享锁是在锁定的期间,其它线程也可以访问这个数据文件,但是不允许修改操作,相应的,独享锁就是整个文件就是归一个线程所有,其它线程无法访问这个数据文件。一般MySQL中最快的存储引擎MyISAM,它是基于表锁定的,就是说如果一锁定的话,那么整个数据文件外部都无法访问,必须等前一个操作完成后,才能接收下一个操作,那么在这个前一个操作没有执行完成,后一个操作等待在队列里无法执行的情况叫做阻塞,一般我们通俗意义上叫做“锁表”。
锁表直接导致的后果是什么?就是大量的SQL无法立即执行,必须等队列前面的SQL全部执行完毕才能继续执行。这个无法执行的SQL就会导致没有结果,或者延迟严重,影响用户体验。
特别是对于一些使用比较频繁的表,比如SNS系统中的用户信息表、论坛系统中的帖子表等等,都是访问量大很大的表,为了保证数据的快速提取返回给用户,必须使用一些处理方式来解决这个问题,这个就是我今天要聊到的分表技术。
分表技术顾名思义,就是把若干个存储相同类型数据的表分成几个表分表存储,在提取数据的时候,不同的用户访问不同的表,互不冲突,减少锁表的几率。比如,目前保存用户分表有两个表,一个是user_1表,还有一个是 user_2 表,两个表保存了不同的用户信息,user_1 保存了前10万的用户信息,user_2保存了后10万名用户的信息,现在如果同时查询用户 heiyeluren1 和 heiyeluren2 这个两个用户,那么就是分表从不同的表提取出来,减少锁表的可能。
我下面要讲述的两种分表方法我自己都没有实验过,不保证准确能用,只是提供一个设计思路。下面关于分表的例子我假设是在一个贴吧系统的基础上来进行处理和构建的。(如果没有用过贴吧的用户赶紧Google一下)
二、基于基础表的分表处理
这个基于基础表的分表处理方式大致的思想就是:一个主要表,保存了所有的基本信息,如果某个项目需要找到它所存储的表,那么必须从这个基础表中查找出对应的表名等项目,好直接访问这个表。如果觉得这个基础表速度不够快,可以完全把整个基础表保存在缓存或者内存中,方便有效的查询。
我们基于贴吧的情况,构建假设如下的3张表:
1. 贴吧版块表: 保存贴吧中版块的信息
2. 贴吧主题表:保存贴吧中版块中的主题信息,用于浏览
3. 贴吧回复表:保存主题的原始内容和回复内容
“贴吧版块表”包含如下字段:
版块ID       board_id          int(10)
版块名称    board_name      char(50)
子表ID       table_id            smallint(5)
产生时间    created             datetime
“贴吧主题表”包含如下字段:
主题ID          topic_id        int(10)
主题名称        topic_name     char(255)
版块ID          board_id          int(10)
创建时间       created           datetime
“贴吧回复表”的字段如下:
回复ID        reply_id           int(10)
回复内容      reply_text        text
主题ID        topic_id           int(10)
版块ID        board_id         int(10)
创建时间      created            datetime
那么上面保存了我们整个贴吧中的表结构信息,三个表对应的关系是:
版块 --> 多个主题
主题 --> 多个回复
那么就是说,表文件大小的关系是:
版块表文件 < 主题表文件 < 回复表文件
所以基本可以确定需要对主题表和回复表进行分表,已增加我们数据检索查询更改时候的速度和性能。
看了上面的表结构,会明显发现,在“版块表”中保存了一个"table_id"字段,这个字段就是用于保存一个版块对应的主题和回复都是分表保存在什么表里的。
比如我们有一个叫做“PHP”的贴吧,board_id是1,子表ID也是1,那么这条记录就是:
board_id | board_name | table_id | created
1 | PHP | 1 | 2007-01-19 00:30:12
相应的,如果我需要提取“PHP”吧里的所有主题,那么就必须按照表里保存的table_id来组合一个存储了主题的表名称,比如我们主题表的前缀是“topic_”,那么组合出来“PHP”吧对应的主题表应该是:“topic_1”,那么我们执行:
SELECT * FROM topic_1 WHERE board_id = 1 ORDER BY topic_id DESC LIMIT 10
这样就能够获取这个主题下面回复列表,方便我们进行查看,如果需要查看某个主题下面的回复,我们可以继续使用版块表中保存的“table_id”来进行查询。比如我们回复表的前缀是“reply_”,那么就可以组合出“PHP”吧的ID为1的主题的回复:
SELECT * FROM reply_1 WHERE topic_id = 1 ORDER BY reply_id DESC LIMIT 10
这里,我们能够清晰的看到,其实我们这里使用了基础表,基础表就是我们的版块表。那么相应的,肯定会说:基础表的数据量大了以后如何保证它的速度和效率?
当然,我们就必须使得这个基础表保持最好的速度和性能,比如,可以采用MySQL的内存表来存储,或者保存在内存当中,比如Memcache之类的内存缓存等等,可以按照实际情况来进行调整。
一般基于基础表的分表机制在SNS、交友、论坛等Web2.0网站中是个比较不错的解决方案,在这些网站中,完全可以单独使用一个表来来保存基本标识和目标表之间的关系。使用表保存对应关系的好处是以后扩展非常方便,只需要增加一个表记录。
优势】增加删除节点非常方便,为后期升级维护带来很大便利
劣势】需要增加表或者对某一个表进行操作,还是无法离开数据库,会产生瓶颈
三、基于Hash算法的分表处理
我们知道Hash表就是通过某个特殊的Hash算法计算出的一个值,这个值必须是惟一的,并且能够使用这个计算出来的值查找到需要的值,这个叫做哈希表。
我们在分表里的hash算法跟这个思想类似:通过一个原始目标的ID或者名称通过一定的hash算法计算出数据存储表的表名,然后访问相应的表。
继续拿上面的贴吧来说,每个贴吧有版块名称和版块ID,那么这两项值是固定的,并且是惟一的,那么我们就可以考虑通过对这两项值中的一项进行一些运算得出一个目标表的名称。
现在假如我们针对我们这个贴吧系统,假设系统最大允许1亿条数据,考虑每个表保存100万条记录,那么整个系统就不超过100个表就能够容纳。按照这个标准,我们假设在贴吧的版块ID上进行hash,获得一个key值,这个值就是我们的表名,然后访问相应的表。
我们构造一个简单的hash算法:
function get_hash($id){
     $str = bin2hex($id);
     $hash = substr($str, 0, 4);
     if (strlen($hash)<4){
         $hash = str_pad($hash, 4, "0");
     }
     return $hash;
}
算法大致就是传入一个版块ID值,然后函数返回一个4位的字符串,如果字符串长度不够,使用0进行补全。
比如:get_hash(1),输出的结果是“3100”,输入:get_hash(23819),得到的结果是:3233,那么我们经过简单的跟表前缀组合,就能够访问这个表了。那么我们需要访问ID为1的内容时候哦,组合的表将是:topic_3100、reply_3100,那么就可以直接对目标表进行访问了。
当然,使用hash算法后,有部分数据是可能在同一个表的,这一点跟hash表不同,hash表是尽量解决冲突,我们这里不需要,当然同样需要预测和分析表数据可能保存的表名。
如果需要存储的数据更多,同样的,可以对版块的名字进行hash操作,比如也是上面的二进制转换成十六进制,因为汉字比数字和字母要多很多,那么重复几率更小,但是可能组合成的表就更多了,相应就必须考虑一些其它的问题。
归根结底,使用hash方式的话必须选择一个好的hash算法,才能生成更多的表,然数据查询的更迅速。
优点hash算法直接得出目标表名称,效率很高】通过
劣势】扩展性比较差,选择了一个hash算法,定义了多少数据量,以后只能在这个数据量上跑,不能超过过这个数据量,可扩展性稍差
四、其它问题
1. 搜索问题
现在我们已经进行分表了,那么就无法直接对表进行搜索,因为你无法对可能系统中已经存在的几十或者几百个表进行检索,所以搜索必须借助第三方的组件来进行,比如Lucene作为站内搜索引擎是个不错的选择。
2. 表文件问题
我们知道MySQL的MyISAM引擎每个表都会生成三个文件,*.frm、*.MYD、*.MYI 三个文件,分表用来保存表结构、表数据和表索引。Linux下面每个目录下的文件数量最好不要超过1000个,不然检索数据将更慢,那么每个表都会生成三个文件,相应的如果分表超过300个表,那么将检索非常慢,所以这时候就必须再进行分,比如在进行数据库的分离。
使用基础表,我们可以新增加一个字段,用来保存这个表保存在什么数据。使用Hash的方式,我们必须截取hash值中第几位来作为数据库的名字。这样,完好的解决这个问题。
五、总结
在大负载应用当中,数据库一直是个很重要的瓶颈,必须要突破,本文讲解了两种分表的方式,希望对很多人能够有启发的作用。当然,本文代码和设想没有经过任何代码测试,所以无法保证设计的完全准确实用,具体还是需要读者在使用过程当中认真分析实施。
Mar24

近日一报

Author: leeon  Click: 7380   Comments: 1 Category: 生活  Tag: 实习

深圳实习中

Feb28

原创PHP邮件发送类【原创】-支持单附件发送

Author: leeon  Click: 8018   Comments: 0 Category: php  Tag: php,附件
利用PHP自带的mail()函数来发送带有附件的邮件。此代码仅供学习交流使用。转载请注明本站来源。
[code="php"]

class Mail {
private $topic;
private $toaddr;
private $fromaddr;
private $cc;
private $content;
private $attach;
private $header;
private $domain;//邮箱域
private $msg;

private $filename;
private $filemime;
private $filestr;

private $boundary;
private $uniqid;

private $eol; //每行末尾所加的换行符类型


function __construct(){
$this->getEOT();//生成结尾换行符
$this->getUniq_id();
$this->header='';
$this->attach='';
$this->cc='';
$this->msg='';


}


public function getFromaddr() {
return $this->fromaddr;
}

public function setFromaddr($fromaddr) {
$this->fromaddr = $fromaddr;
}

public function getTopic() {
return $this->topic;
}


public function getToaddr() {
return $this->toaddr;
}


public function getCc() {
return $this->cc;
}

public function getContent() {
return $this->content;
}


public function getAttach() {
return $this->attach;
}


public function setTopic($topic) {
$this->topic = mb_convert_encoding(trim($topic),'UTF-8','auto');
}

public function setToaddr($toaddr) {
$this->toaddr = trim($toaddr);
}


public function setCc($cc) {
$this->cc = trim($cc);
}


public function setContent($content) {
$this->content = mb_convert_encoding(trim($content),'UTF-8','auto');
}


public function setAttach($attach) {
$this->attach = trim($attach);
}

public function getDomain() {
return $this->domain;
}

public function setDomain($domain) {
$this->domain = $domain;//输入的值为‘@domain.com’
}


/*
* 根据系统类型设置换行符
*/
private function getEOT() {
if (strtoupper ( substr ( PHP_OS, 0, 3 ) == 'WIN' )) {
$this->eol = "\r\n";
} elseif (strtoupper ( substr ( PHP_OS, 0, 3 ) == 'MAC' )) {
$this->eol= "\r";
} else {
$this->eol = "\n";
}
}


private function getBoundary(){

$this->boundary= '--'.substr(md5(time().rand(1000,2000)),0,16);

}

private function getUniq_id(){

$this->uniqid= md5(microtime().time().rand(1,100));

}

private function outputCommonHeader(){
$this->header .= 'From: '.$this->fromaddr.$this->eol;
//$this->header .= 'To: '.$this->toaddr.$this->eol;
//$this->header .= 'Subject: '.$this->topic.$this->eol;
$this->header .= 'Message-ID: <'.$this->uniqid.$this->domain.'>'.$this->eol;
$this->header .= 'MIME-Version: 1.0'.$this->eol;
$this->header .= 'Reply-To: '.$this->fromaddr.$this->eol;
$this->header .= 'Return-Path: '.$this->fromaddr.$this->eol;
$this->header .= 'X-Mailer: Xmail System'.$this->eol;
$this->header .= 'Content-Disposition: inline'.$this->eol;
}

private function mime_content_type ( $f )
{
$temp = trim ( exec ('file -bi ' . escapeshellarg ( $f ) ) ) ;
$temp = preg_replace('/\s+/',' ',$temp);
$temp = explode(' ',$temp);
return $temp[0];
}//判断文件的mime类型


/*
* 只带有抄送
*/
private function mailWithCC(){
$this->header .= 'Cc: '.$this->cc.$this->eol;
$this->header .= 'Content-type: text/html; charset=UTF-8'.$this->eol;
$this->header .= 'Content-Transfer-Encoding: 8bit'.$this->eol;
$this->msg = $this->content;
if(mail($this->toaddr,$this->topic,$this->msg,$this->header)){

return 1;
}else{
return 0;
}
}
/*
* $filedir需要是绝对地址
*/
private function attachmentToBase64($filedir){
$this->filename = basename($filedir);
@$fopen = fopen($filedir,'r');
$str = fread($fopen,filesize($filedir));
$str = base64_encode($str);
$this->filestr = $str;
}


/*
* 只带有附件
*/
private function mailWithAttach(){
$this->attachmentToBase64($this->attach);
$this->header .= 'Content-type: multipart/mixed; boundary="'.str_replace('--','',$this->boundary).'"'.$this->eol;
$this->msg .= $this->eol.$this->boundary.$this->eol;
$this->msg .= 'Content-Type: text/html; charset=utf-8'.$this->eol;
$this->msg .= 'Content-Disposition: inline'.$this->eol;
$this->msg .= $this->eol.$this->content.$this->eol;
$this->msg .= $this->boundary.$this->eol;
$this->msg .= 'Content-Type: '.$this->mime_content_type($this->attach).$this->eol;
$this->msg .= 'Content-Disposition: attachment; filename="'.$this->filename.'"'.$this->eol;
$this->msg .= 'Content-Transfer-Encoding: base64'.$this->eol;
$this->msg .= $this->eol.$this->filestr.$this->eol;
$this->msg .= $this->eol.$this->boundary.'--';

if(mail($this->toaddr,$this->topic,$this->msg,$this->header)){

return 1;
}else{
return 0;
}
}

/*
* 带有附件和抄送
*/
private function mailAll(){

$this->attachmentToBase64($this->attach);
$this->header .= 'Cc: '.$this->cc.$this->eol;
$this->header .= 'Content-type: multipart/mixed; boundary="'.str_replace('--','',$this->boundary).'"'.$this->eol;
$this->msg .= $this->eol.$this->boundary.$this->eol;
$this->msg .= 'Content-Type: text/html; charset=utf-8'.$this->eol;
$this->msg .= 'Content-Disposition: inline'.$this->eol;
$this->msg .= $this->eol.$this->content.$this->eol;
$this->msg .= $this->boundary.$this->eol;
$this->msg .= 'Content-Type: '.$this->mime_content_type($this->attach).$this->eol;
$this->msg .= 'Content-Disposition: attachment; filename="'.$this->filename.'"'.$this->eol;
$this->msg .= 'Content-Transfer-Encoding: base64'.$this->eol;
$this->msg .= $this->eol.$this->filestr.$this->eol;
$this->msg .= $this->eol.$this->boundary.'--';

if(mail($this->toaddr,$this->topic,$this->msg,$this->header)){

return 1;
}else{
return 0;
}

}
/*
* 不带抄送和附件
*/
private function mailSimple(){
$this->header .= 'Content-type: text/html; charset=UTF-8'.$this->eol;
$this->header .= 'Content-Transfer-Encoding: 8bit'.$this->eol;
$this->msg = $this->content;
if(mail($this->toaddr,$this->topic,$this->msg,$this->header)){

return 1;
}else{
return 0;
}
}

public function send(){

if(empty($this->attach)&&empty($this->cc)){
$this->outputCommonHeader();
return $this->mailSimple();

}else if(empty($this->attach)){
$this->outputCommonHeader();
return $this->mailWithCC();

}else if(empty($this->cc)){
$this->outputCommonHeader();
$this->getBoundary(); //有附件就生成boundary
return $this->mailWithAttach();

}else if(!empty($this->toaddr)&&!empty($this->topic)&&!empty($this->cc)&&!empty($this->content)&&!empty($this->attach)){
$this->outputCommonHeader();
$this->getBoundary(); //有附件就生成boundary
return $this->mailAll();
}
}
}
?>
[/code]

示例代码,有些变量需要上下文环境:
[code="php"]
$m = new Mail();
$m->setToaddr($this->temp['receipt_address']);
$m->setTopic($this->temp['mail_title']);
$m->setContent($this->temp['mail_content']);
$m->setFromaddr($_SESSION['user']['name'].' <'.$_SESSION['user']['name'].'@'.SystemDomain.'>');
$m->setDomain('@'.SystemDomain);
$m->setCc($this->temp['cc_address']);
$m->setAttach(PATH.'/temp/'.$this->temp['attachment_file']);
$m->send();
[/code]

分类

标签

归档

最新评论

Abyss在00:04:28评论了
Linux中ramdisk,tmpfs,ramfs的介绍与性能测试
shallwe99在10:21:17评论了
【原创】如何在微信小程序开发中正确的使用vant ui组件
默一在09:04:53评论了
Berkeley DB 由浅入深【转自架构师杨建】
Memory在14:09:22评论了
【原创】最佳PHP框架选择(phalcon,yaf,laravel,thinkphp,yii)
leo在17:57:04评论了
shell中使用while循环ssh的注意事项

我看过的书

链接

其他

访问本站种子 本站平均热度:9360 c° 本站链接数:1 个 本站标签数:464 个 本站被评论次数:94 次