一、我们可以且应该优化什么? 
  硬件
  
  操作系统/软件库
  
  SQL服务器(设置和查询)
  
  应用编程接口(API)
  
  应用程序
  
  二、优化硬件 
  如果你需要庞大的数据库表(>2G),你应该考虑使用64位的硬件结构,像Alpha、Sparc或即将推出的IA64。因为MySQL内部使用大量64位的整数,64位的CPU将提供更好的性能。
  
  对大数据库,优化的次序一般是RAM、快速硬盘、CPU能力。
  
  更多的内存通过将最常用的键码页面存放在内存中可以加速键码的更新。
  
  如果不使用事务安全(transaction-safe)的表或有大表并且想避免长文件检查,一台UPS就能够在电源故障时让系统安全关闭。
  
  对于数据库存放在一个专用服务器的系统,应该考虑1G的以太网。延迟与吞吐量同样重要。
  
  三、优化磁盘 
  为系统、程序和临时文件配备一个专用磁盘,如果确是进行很多修改工作,将更新日志和事务日志放在专用磁盘上。
  低寻道时间对数据库磁盘非常重要。对与大表,你可以估计你将需要log(行数)/log(索引块长度/3*2/(键码长度 + 数据指针长度))+1次寻到才能找到一行。对于有500000行的表,索引Mediun int类型的列,需要log(500000) / log(1024/3*2/(3 + 2))+1=4次寻道。上述索引需要500000*7*3/2=5.2M的空间。实际上,大多数块将被缓存,所以大概只需要1-2次寻道。
  然而对于写入(如上),你将需要4次寻道请求来找到在哪里存放新键码,而且一般要2次寻道来更新索引并写入一行。
  
  对于非常大的数据库,你的应用将受到磁盘寻道速度的限制,随着数据量的增加呈N log N数据级递增。
  
  将数据库和表分在不同的磁盘上。在MySQL中,你可以为此而使用符号链接。
  条列磁盘(RAID 0)将提高读和写的吞吐量。
  带镜像的条列(RAID 0+1)将更安全并提高读取的吞吐量。写入的吞吐量将有所降低。
  不要对临时文件或可以很容易地重建的数据所在的磁盘使用镜像或RAID(除了RAID 0)。
  在Linux上,在引导时对磁盘使用命令hdparm -m16 -d1以启用同时读写多个扇区和DMA功能。这可以将响应时间提高5~50%。
  在Linux上,用async (默认)和noatime挂载磁盘(mount)。
  对于某些特定应用,可以对某些特定表使用内存磁盘,但通常不需要。
  
  四、优化操作系统 
  不要交换区。如果内存不足,增加更多的内存或配置你的系统使用较少内存。
  不要使用NFS磁盘(会有NFS锁定的问题)。
  增加系统和MySQL服务器的打开文件数量。(在safe_mysqld脚本中加入ulimit -n #)。
  增加系统的进程和线程数量。
  如果你有相对较少的大表,告诉文件系统不要将文件打碎在不同的磁道上(Solaris)。
  使用支持大文件的文件系统(Solaris)。
  选择使用哪种文件系统。在Linux上的Reiserfs对于打开、读写都非常快。文件检查只需几秒种。
  
  五、操作系统移植
  PERL
  可在不同的操作系统和数据库之间移植。
  适宜快速原型。
  应该使用DBI/DBD接口。
  PHP
  比PERL易学。
  使用比PERL少的资源。
  通过升级到PHP4可以获得更快的速度。
  C
  MySQL的原生接口。
  较快并赋予更多的控制。
  低层,所以必须付出更多。
  C++
  较高层次,给你更多的时间来编写应用。
  仍在开发中
  ODBC
  运行在Windows和Unix上。
  几乎可在不同的SQL服务器间移植。
  较慢。MyODBC只是简单的直通驱动程序,比用原生接口慢19%。
  有很多方法做同样的事。很难像很多ODBC驱动程序那样运行,在不同的领域还有不同的错误。
  问题成堆。Microsoft偶尔还会改变接口。
  不明朗的未来。(Microsoft更推崇OLE而非ODBC)
  ODBC
  运行在Windows和Unix上。
  几乎可在不同的SQL服务器间移植。
  较慢。MyODBC只是简单的直通驱动程序,比用原生接口慢19%。
  有很多方法做同样的事。很难像很多ODBC驱动程序那样运行,在不同的领域还有不同的错误。
  问题成堆。Microsoft偶尔还会改变接口。
  不明朗的未来。(Microsoft更推崇OLE而非ODBC)
  JDBC
  理论上可在不同的操作系统何时据库间移植。
  可以运行在web客户端。
  Python和其他
  可能不错,可我们不用它们。
六、优化应用 
  应该集中精力解决问题。
  在编写应用时,应该决定什么是最重要的:
  速度
  操作系统间的可移植性
  SQL服务器间的可移植性
  使用持续的连接。.
  缓存应用中的数据以减少SQL服务器的负载。
  不要查询应用中不需要的列。
  不要使用SELECT * FROM table_name...
  测试应用的所有部分,但将大部分精力放在在可能最坏的合理的负载下的测试整体应用。通过以一种模块化的方式进行,你应该能用一个快速“哑模块”替代找到的瓶颈,然后很容易地标出下一个瓶颈。
  如果在一个批处理中进行大量修改,使用LOCK TABLES。例如将多个UPDATES或DELETES集中在一起。
  
  七、应该使用可移植的应用 
  Perl DBI/DBD
  ODBC
  JDBC
  Python(或其他有普遍SQL接口的语言)
  你应该只使用存在于所有目的SQL服务器中或可以很容易地用其他构造模拟的SQL构造。www.mysql.com上的Crash-me页可以帮助你。
  为操作系统/SQL服务器编写包装程序来提供缺少的功能。
  
  八、如果你需要更快的速度,你应该:
  找出瓶颈(CPU、磁盘、内存、SQL服务器、操作系统、API或应用)并集中全力解决。
  使用给予你更快速度/灵活性的扩展。
  逐渐了解SQL服务器以便能为你的问题使用可能最快的SQL构造并避免瓶颈。
  优化表布局和查询。
  使用复制以获得更快的选择(select)速度。
  如果你有一个慢速的网络连接数据库,使用压缩客户/服务器协议。
  不要害怕时应用的第一个版本不能完美地移植,在你解决问题时,你总是可以在以后优化它。
  
  九、优化MySQL 
  挑选编译器和编译选项。
  位你的系统寻找最好的启动选项。
  通读MySQL参考手册并阅读Paul DuBios的《MySQL》一书。(已有中文版-译注)
  多用EXPLAIN SELECT、SHOW VARIABLES、SHOW STATUS和SHOW PROCESSLIST。
  了解查询优化器的工作原理。
  优化表的格式。
  维护你的表(myisamchk、CHECK TABLE、 OPTIMIZE TABLE)
  使用MySQL的扩展功能以让一切快速完成。
  如果你注意到了你将在很多场合需要某些函数,编写MySQL UDF函数。
  不要使用表级或列级的GRANT,除非你确实需要。
  购买MySQL技术支持以帮助你解决问题:)
  
  十、编译和安装MySQL 
  通过位你的系统挑选可能最好的编译器,你通常可以获得10-30%的性能提高。
  在Linux/Intel平台上,用pgcc(gcc的奔腾芯片优化版)编译MySQL。然而,二进制代码将只能运行在Intel奔腾CPU上。
  对于一种特定的平台,使用MySQL参考手册上推荐的优化选项。
  一般地,对特定CPU的原生编译器(如Sparc的Sun Workshop)应该比gcc提供更好的性能,但不总是这样。
  用你将使用的字符集编译MySQL。
  静态编译生成mysqld的执行文件(用--with-mysqld-ldflags=all-static)并用strip sql/mysqld整理最终的执行文件。
  注意,既然MySQL不使用C++扩展,不带扩展支持编译MySQL将赢得巨大的性能提高。
  如果操作系统支持原生线程,使用原生线程(而不用mit-pthreads)。
  用MySQL基准测试来测试最终的二进制代码。
  
  十一、维护 
  如果可能,偶尔运行一下OPTIMIZE table,这对大量更新的变长行非常重要。
  偶尔用myisamchk -a更新一下表中的键码分布统计。记住在做之前关掉MySQL。
  如果有碎片文件,可能值得将所有文件复制到另一个磁盘上,清除原来的磁盘并拷回文件。
  如果遇到问题,用myisamchk或CHECK table检查表。
  用mysqladmin -i10 precesslist extended-status监控MySQL的状态。
  用MySQL GUI客户程序,你可以在不同的窗口内监控进程列表和状态。
  使用mysqladmin debug获得有关锁定和性能的信息。
  
  十二、优化SQL 
  扬SQL之长,其它事情交由应用去做。使用SQL服务器来做:
  
  找出基于WHERE子句的行。
  JOIN表
  GROUP BY
  ORDER BY
  DISTINCT
  不要使用SQL来做:
  
  检验数据(如日期)
  成为一只计算器
  技巧:
  
  明智地使用键码。
  键码适合搜索,但不适合索引列的插入/更新。
  保持数据为数据库第三范式,但不要担心冗余信息或这如果你需要更快的速度,创建总结表。
  在大表上不做GROUP BY,相反创建大表的总结表并查询它。
  UPDATE table set count=count+1 where key_column=constant非常快。
  对于大表,或许最好偶尔生成总结表而不是一直保持总结表。
  充分利用INSERT的默认值。
  
  十三、不同SQL服务器的速度差别(以秒计)
  通过键码读取2000000行: NT Linux
  mysql 367 249
  mysql_odbc 464
  db2_odbc 1206
  informix_odbc 121126
  ms-sql_odbc 1634
  oracle_odbc 20800
  solid_odbc 877
  sybase_odbc 17614
  
  插入350768行: NT Linux
  mysql 381 206
  mysql_odbc 619
  db2_odbc 3460
  informix_odbc 2692
  ms-sql_odbc 4012
  oracle_odbc 11291
  solid_odbc 1801
  sybase_odbc 4802
  
  在上述测试中,MySQL配置8M高速缓存运行,其他数据库以默认安装运行。
 十四、重要的MySQL启动选项 
  back_log 如果需要大量新连接,修改它。
  thread_cache_size 如果需要大量新连接,修改它。
  key_buffer_size 索引页池,可以设成很大。
  bdb_cache_size BDB表使用的记录和键吗高速缓存。
  table_cache 如果有很多的表和并发连接,修改它。
  delay_key_write 如果需要缓存所有键码写入,设置它。
  log_slow_queries 找出需花大量时间的查询。
  max_heap_table_size 用于GROUP BY
  sort_buffer 用于ORDER BY和GROUP BY
  myisam_sort_buffer_size 用于REPAIR TABLE
  join_buffer_size 在进行无键吗的联结时使用。
  
  十五、优化表 
  MySQL拥有一套丰富的类型。你应该对每一列尝试使用最有效的类型。
  ANALYSE过程可以帮助你找到表的最优类型:SELECT * FROM table_name PROCEDURE ANALYSE()。
  对于不保存NULL值的列使用NOT NULL,这对你想索引的列尤其重要。
  将ISAM类型的表改为MyISAM。
  如果可能,用固定的表格式创建表。
  不要索引你不想用的东西。
  利用MySQL能按一个索引的前缀进行查询的事实。如果你有索引INDEX(a,b),你不需要在a上的索引。
  不在长CHAR/VARCHAR列上创建索引,而只索引列的一个前缀以节省存储空间。CREATE TABLE table_name (hostname CHAR(255) not null, index(hostname(10)))
  对每个表使用最有效的表格式。
  在不同表中保存相同信息的列应该有同样的定义并具有相同的列名。
  
  十六、MySQL如何次存储数据 
  数据库以目录存储。
  表以文件存储。
  列以变长或定长格式存储在文件中。对BDB表,数据以页面形式存储。
  支持基于内存的表。
  数据库和表可在不同的磁盘上用符号连接起来。
  在Windows上,MySQL支持用.sym文件内部符号连接数据库。
  
  十七、MySQL表类型 
  HEAP表:固定行长的表,只存储在内存中并用HASH索引进行索引。
  ISAM表:MySQL 3.22中的早期B-tree表格式。
  MyIASM:IASM表的新版本,有如下扩展:
  二进制层次的可移植性。
  NULL列索引。
  对变长行比ISAM表有更少的碎片。
  支持大文件。
  更好的索引压缩。
  更好的键吗统计分布。
  更好和更快的auto_increment处理。
  来自Sleepcat的Berkeley DB(BDB)表:事务安全(有BEGIN WORK/COMMIT|ROLLBACK)。
  
  十八、MySQL行类型(专指IASM/MyIASM表)
  如果所有列是定长格式(没有VARCHAR、BLOB或TEXT),MySQL将以定长表格式创建表,否则表以动态长度格式创建。
  定长格式比动态长度格式快很多并更安全。
  动态长度行格式一般占用较少的存储空间,但如果表频繁更新,会产生碎片。
  在某些情况下,不值得将所有VARCHAR、BLOB和TEXT列转移到另一个表中,只是获得主表上的更快速度。
  利用myiasmchk(对ISAM,pack_iasm),可以创建只读压缩表,这使磁盘使用率最小,但使用慢速磁盘时,这非常不错。压缩表充分地利用将不再更新的日志表
  
  十九、MySQL高速缓存(所有线程共享,一次性分配) 
  键码缓存:key_buffer_size,默认8M。
  表缓存:table_cache,默认64。
  线程缓存:thread_cache_size,默认0。
  主机名缓存:可在编译时修改,默认128。
  内存映射表:目前仅用于压缩表。
  注意:MySQL没有行高速缓存,而让操作系统处理。
  
  二十、MySQL缓存区变量(非共享,按需分配) 
  sort_buffer:ORDER BY/GROUP BY
  record_buffer:扫描表。
  join_buffer_size:无键联结
  myisam_sort_buffer_size:REPAIR TABLE
  net_buffer_length:对于读SQL语句并缓存结果。
  tmp_table_size:临时结果的HEAP表大小。
  
  二十一、MySQL表高速缓存工作原理 
  每个MyISAM表的打开实例(instance)使用一个索引文件和一个数据文件。如果表被两个线程使用或在同一条查询中使用两次,MyIASM将共享索引文件而是打开数据文件的另一个实例。
  如果所有在高速缓存中的表都在使用,缓存将临时增加到比表缓存尺寸大些。如果是这样,下一个被释放的表将被关闭。
  你可以通过检查mysqld的Opened_tables变量以检查表缓存是否太小。如果该值太高,你应该增大表高速缓存。
  
  二十二、MySQL扩展/优化-提供更快的速度 
  使用优化的表类型(HEAP、MyIASM或BDB表)。
  对数据使用优化的列。
  如果可能使用定长行。
  使用不同的锁定类型(SELECT HIGH_PRIORITY,INSERT LOW_PRIORITY)
  Auto_increment
  REPLACE (REPLACE INTO table_name VALUES (...))
  INSERT DELAYED
  LOAD DATA INFILE / LOAD_FILE()
  使用多行INSERT一次插入多行。
  SELECT INTO OUTFILE
  LEFT JOIN, STRAIGHT JOIN
  LEFT JOIN ,结合IS NULL
  ORDER BY可在某些情况下使用键码。
  如果只查询在一个索引中的列,将只使用索引树解决查询。
  联结一般比子查询快(对大多数SQL服务器亦如此)。
  LIMIT
  SELECT * from table1 WHERE a > 10 LIMIT 10,20
  DELETE * from table1 WHERE a > 10 LIMIT 10
  foo IN (常数列表) 高度优化。
  GET_LOCK()/RELEASE_LOCK()
  LOCK TABLES
  INSERT和SELECT可同时运行。
  UDF函数可装载进一个正在运行的服务器。
  压缩只读表。
  CREATE TEMPORARY TABLE
  CREATE TABLE .. SELECT
  带RAID选项的MyIASM表将文件分割成很多文件以突破某些文件系统的2G限制。
  Delay_keys
  复制功能
  
  二十二、MySQL何时使用索引 
  对一个键码使用>, >=, =,  1 and key_part1 < 90
  如果使用HEAP表且不用=搜索所有键码部分。
  在HEAP表上使用ORDER BY。
  如果不是用键码第一部分
  
  SELECT * FROM table_name WHERE key_part2=1
  如果使用以一个通配符开始的LIKE
  
  SELECT * FROM table_name WHERE key_part1 LIKE '%jani%'
  搜索一个索引而在另一个索引上做ORDER BY
  
  SELECT * from table_name WHERE key_part1 = # ORDER BY key2
  
  
  二十四、学会使用EXPLAIN 
  对于每一条你认为太慢的查询使用EXPLAIN!
  
  mysql> explain select t3.DateOfAction, t1.TransactionID
  -> from t1 join t2 join t3
  -> where t2.ID = t1.TransactionID and t3.ID = t2.GroupID
  -> order by t3.DateOfAction, t1.TransactionID;
  
  ALL和范围类型提示一个潜在的问题。
  
  二十五、学会使用SHOW PROCESSLIST 
  使用SHOW processlist来发现正在做什么:
  
  在mysql或mysqladmin中用KILL来杀死溜掉的线程。
  
  二十六、如何知晓MySQL解决一条查询 
  运行项列命令并试图弄明白其输出:
  SHOW VARIABLES;
  SHOW COLUMNS FROM ...G
  EXPLAIN SELECT ...G
  FLUSH STATUS;
  SELECT ...;
  SHOW STATUS;
  
  二十七、MySQL非常不错 
  日志
  在进行很多连接时,连接非常快。
  同时使用SELECT和INSERT的场合。
  在不把更新与耗时太长的选择结合时。
  在大多数选择/更新使用唯一键码时。
  在使用没有长时间冲突锁定的多个表时。
  在用大表时(MySQL使用一个非常紧凑的表格式)。
  
  二十八、MySQL应避免的事情 
  用删掉的行更新或插入表,结合要耗时长的SELECT。
  在能放在WHERE子句中的列上用HAVING。
  不使用键码或键码不够唯一而进行JOIN。
  在不同列类型的列上JOIN。
  在不使用=匹配整个键码时使用HEAP表。
  在MySQL监控程序中忘记在UPDATE或DELETE中使用一条WHERE子句。如果想这样做,使用mysql客户程序的--i-am-a-dummy选项。
  
  二十九、MySQL各种锁定 
  内部表锁定
  LOCK TABLES(所有表类型适用)
  GET LOCK()/RELEASE LOCK()
  页面锁定(对BDB表)
  ALTER TABLE也在BDB表上进行表锁定
  LOCK TABLES允许一个表有多个读者和一个写者。
  一般WHERE锁定具有比READ锁定高的优先级以避免让写入方干等。对于不重要的写入方,可以使用LOW_PRIORITY关键字让锁定处理器优选读取方。
  UPDATE LOW_PRIORITY SET value=10 WHERE id=10;
  
  三十、给MySQL更多信息以更好地解决问题的技巧 
  注意你总能去掉(加注释)MySQL功能以使查询可移植:
  
  SELECT /*! SQL_BUFFER_RESULTS */ ...
  SELECT SQL_BUFFER_RESULTS ...
  将强制MySQL生成一个临时结果集。只要所有临时结果集生成后,所有表上的锁定均被释放。这能在遇到表锁定问题时或要花很长时间将结果传给客户端时有所帮助。
  SELECT SQL_SMALL_RESULT ... GROUP BY ...
  告诉优化器结果集将只包含很少的行。
  SELECT SQL_BIG_RESULT ... GROUP BY ...
  告诉优化器结果集将包含很多行。
  SELECT STRAIGHT_JOIN ...
  强制优化器以出现在FROM子句中的次序联结表。
  SELECT ... FROM table_name [USE INDEX (index_list) | IGNORE INDEX (index_list)] table_name2
  强制MySQL使用/忽略列出的索引。
  
  三十一、事务的例子 
  MyIASM表如何进行事务处理:
  mysql> LOCK TABLES trans READ, customer WRITE;
  mysql> select sum(value) from trans where customer_id=some_id;
  mysql> update customer set total_value=sum_from_previous_statement
  where customer_id=some_id;
  mysql> UNLOCK TABLES;
  
  BDB表如何进行事务:
  mysql> BEGIN WORK;
  mysql> select sum(value) from trans where customer_id=some_id;
  mysql> update customer set total_value=sum_from_previous_statement
  where customer_id=some_id;
  mysql> COMMIT;
  
  注意你可以通过下列语句回避事务:
  UPDATE customer SET value=value+new_value WHERE customer_id=some_id;
  
  三十二、使用REPLACE的例子
  REPLACE的功能极像INSERT,除了如果一条老记录在一个唯一索引上具有与新纪录相同的值,那么老记录在新纪录插入前则被删除。不使用
  
  SELECT 1 FROM t1 WHERE key=#
  IF found-row
  LOCK TABLES t1
  DELETE FROM t1 WHERE key1=#
  INSERT INTO t1 VALUES (...)
  UNLOCK TABLES t1;
  ENDIF
  
  而用
  REPLACE INTO t1 VALUES (...)
  
  三十三、一般技巧 
  使用短主键。联结表时使用数字而非字符串。
  当使用多部分键码时,第一部分应该时最常用的部分。
  有疑问时,首先使用更多重复的列以获得更好地键码压缩。
  如果在同一台机器上运行MySQL客户和服务器,那么在连接MySQL时则使用套接字而不是TCP/IP(这可以提高性能7.5%)。可在连接MySQL服务器时不指定主机名或主机名为localhost来做到。
  如果可能,使用--skip-locking(在某些OS上为默认),这将关闭外部锁定并将提高性能。
  使用应用层哈希值而非长键码:
  SELECT * FROM table_name WHERE hash=MD5(concat(col1,col2)) AND
  col_1='constant' AND col_2='constant'
  
  在文件中保存需要以文件形式访问的BLOB,在数据库中只保存文件名。
  删除所有行比删除一大部分行要快。
  如果SQL不够快,研究一下访问数据的较底层接口。
  
  三十四、使用MySQL 3.23的好处 
  MyISAM:可移植的大表格式
  HEAP:内存中的表
  Berkeley DB:支持事务的表。
  众多提高的限制
  动态字符集
  更多的STATUS变量
  CHECK和REPAIR表
  更快的GROUP BY和DISTINCT
  LEFT JOIN ... IF NULL的优化
  CREATE TABLE ... SELECT
  CREATE TEMPORARY table_name (...)
  临时HEAP表到MyISAM表的自动转换
  复制
  mysqlhotcopy脚本
  
  三十五、正在积极开发的重要功能 
  改进事务处理
  失败安全的复制
  正文搜索
  多个表的删除(之后完成多个表的更新)
  更好的键码缓存
  原子RENAME (RENAME TABLE foo as foo_old, foo_new as foo)
  查询高速缓存
  MERGE TABLES
  一个更好的GUI客户程序
posted @ 2009-03-02 17:59 小马歌 阅读(132) | 评论 (0)编辑 收藏
 

PHP中的CURL函数库(Client URL Library Function)

curl_close — 关闭一个curl会话
curl_copy_handle — 拷贝一个curl连接资源的所有内容和参数
curl_errno — 返回一个包含当前会话错误信息的数字编号
curl_error — 返回一个包含当前会话错误信息的字符串
curl_exec — 执行一个curl会话
curl_getinfo — 获取一个curl连接资源句柄的信息
curl_init — 初始化一个curl会话
curl_multi_add_handle — 向curl批处理会话中添加单独的curl句柄资源
curl_multi_close — 关闭一个批处理句柄资源
curl_multi_exec — 解析一个curl批处理句柄
curl_multi_getcontent — 返回获取的输出的文本流
curl_multi_info_read — 获取当前解析的curl的相关传输信息
curl_multi_init — 初始化一个curl批处理句柄资源
curl_multi_remove_handle — 移除curl批处理句柄资源中的某个句柄资源
curl_multi_select — Get all the sockets associated with the cURL extension, which can then be "selected"
curl_setopt_array — 以数组的形式为一个curl设置会话参数
curl_setopt — 为一个curl设置会话参数
curl_version — 获取curl相关的版本信息

curl_init()函数的作用初始化一个curl会话,curl_init()函数唯一的一个参数是可选的,表示一个url地址。
curl_exec()函数的作用是执行一个curl会话,唯一的参数是curl_init()函数返回的句柄。
curl_close()函数的作用是关闭一个curl会话,唯一的参数是curl_init()函数返回的句柄。

<?php
$ch = curl_init("http://www.baidu.com/");
curl_exec($ch);
curl_close($ch);
?>

curl_version()函数的作用是获取curl相关的版本信息,curl_version()函数有一个参数,不清楚是做什么的

<?php
print_r(curl_version())
?>

curl_getinfo()函数的作用是获取一个curl连接资源句柄的信息,curl_getinfo()函数有两个参数,第一个参数是curl的资源句柄,第二个参数是下面一些常量:

<?php
$ch = curl_init("http://www.baidu.com/");
print_r(curl_getinfo($ch));
?>

可选的常量包括:

CURLINFO_EFFECTIVE_URL
最后一个有效的url地址

CURLINFO_HTTP_CODE
最后一个收到的HTTP代码

CURLINFO_FILETIME
远程获取文档的时间,如果无法获取,则返回值为“-1”

CURLINFO_TOTAL_TIME
最后一次传输所消耗的时间

CURLINFO_NAMELOOKUP_TIME
名称解析所消耗的时间

CURLINFO_CONNECT_TIME
建立连接所消耗的时间

CURLINFO_PRETRANSFER_TIME
从建立连接到准备传输所使用的时间

CURLINFO_STARTTRANSFER_TIME
从建立连接到传输开始所使用的时间

CURLINFO_REDIRECT_TIME
在事务传输开始前重定向所使用的时间

CURLINFO_SIZE_UPLOAD
上传数据量的总值

CURLINFO_SIZE_DOWNLOAD
下载数据量的总值

CURLINFO_SPEED_DOWNLOAD
平均下载速度

CURLINFO_SPEED_UPLOAD
平均上传速度

CURLINFO_HEADER_SIZE
header部分的大小

CURLINFO_HEADER_OUT
发送请求的字符串

CURLINFO_REQUEST_SIZE
在HTTP请求中有问题的请求的大小

CURLINFO_SSL_VERIFYRESULT
Result of SSL certification verification requested by setting CURLOPT_SSL_VERIFYPEER

CURLINFO_CONTENT_LENGTH_DOWNLOAD
从Content-Length: field中读取的下载内容长度

CURLINFO_CONTENT_LENGTH_UPLOAD
上传内容大小的说明

CURLINFO_CONTENT_TYPE
下载内容的“Content-type”值,NULL表示服务器没有发送有效的“Content-Type: header”

curl_setopt()函数的作用是为一个curl设置会话参数。curl_setopt_array()函数的作用是以数组的形式为一个curl设置会话参数。

<?php
$ch = curl_init();
$fp = fopen("example_homepage.txt", "w");
curl_setopt($ch, CURLOPT_FILE, $fp);
$options = array(
 
CURLOPT_URL => 'http://www.baidu.com/',
 
CURLOPT_HEADER => false
 
);
curl_setopt_array($ch, $options);
curl_exec($ch);
curl_close($ch);
fclose($fp);
?>

可设置的参数有:

CURLOPT_AUTOREFERER
自动设置header中的referer信息

CURLOPT_BINARYTRANSFER
在启用CURLOPT_RETURNTRANSFER时候将获取数据返回

CURLOPT_COOKIESESSION
启用时curl会仅仅传递一个session cookie,忽略其他的cookie,默认状况下curl会将所有的cookie返回给服务端。session cookie是指那些用来判断服务器端的session是否有效而存在的cookie。

CURLOPT_CRLF
启用时将Unix的换行符转换成回车换行符。

CURLOPT_DNS_USE_GLOBAL_CACHE
启用时会启用一个全局的DNS缓存,此项为线程安全的,并且默认为true。

CURLOPT_FAILONERROR
显示HTTP状态码,默认行为是忽略编号小于等于400的HTTP信息

CURLOPT_FILETIME
启用时会尝试修改远程文档中的信息。结果信息会通过curl_getinfo()函数的CURLINFO_FILETIME选项返回。

CURLOPT_FOLLOWLOCATION
启用时会将服务器服务器返回的“Location:”放在header中递归的返回给服务器,使用CURLOPT_MAXREDIRS可以限定递归返回的数量。

CURLOPT_FORBID_REUSE
在完成交互以后强迫断开连接,不能重用。

CURLOPT_FRESH_CONNECT
强制获取一个新的连接,替代缓存中的连接。

CURLOPT_FTP_USE_EPRT
TRUE to use EPRT (and LPRT) when doing active FTP downloads. Use FALSE to disable EPRT and LPRT and use PORT only.
Added in PHP 5.0.0.

CURLOPT_FTP_USE_EPSV
TRUE to first try an EPSV command for FTP transfers before reverting back to PASV. Set to FALSE to disable EPSV.

CURLOPT_FTPAPPEND
TRUE to append to the remote file instead of overwriting it.

CURLOPT_FTPASCII
An alias of CURLOPT_TRANSFERTEXT. Use that instead.

CURLOPT_FTPLISTONLY
TRUE to only list the names of an FTP directory.

CURLOPT_HEADER
启用时会将头文件的信息作为数据流输出。

CURLOPT_HTTPGET
启用时会设置HTTP的method为GET,因为GET是默认是,所以只在被修改的情况下使用。

CURLOPT_HTTPPROXYTUNNEL
启用时会通过HTTP代理来传输。

CURLOPT_MUTE
讲curl函数中所有修改过的参数恢复默认值。

CURLOPT_NETRC
在连接建立以后,访问~/.netrc文件获取用户名和密码信息连接远程站点。

CURLOPT_NOBODY
启用时将不对HTML中的body部分进行输出。

CURLOPT_NOPROGRESS
启用时关闭curl传输的进度条,此项的默认设置为true

CURLOPT_NOSIGNAL
启用时忽略所有的curl传递给php进行的信号。在SAPI多线程传输时此项被默认打开。

CURLOPT_POST
启用时会发送一个常规的POST请求,类型为:application/x-www-form-urlencoded,就像表单提交的一样。

CURLOPT_PUT
启用时允许HTTP发送文件,必须同时设置CURLOPT_INFILE和CURLOPT_INFILESIZE

CURLOPT_RETURNTRANSFER
讲curl_exec()获取的信息以文件流的形式返回,而不是直接输出。

CURLOPT_SSL_VERIFYPEER
FALSE to stop cURL from verifying the peer's certificate. Alternate certificates to verify against can be specified with the CURLOPT_CAINFO option or a certificate directory can be specified with the CURLOPT_CAPATH option. CURLOPT_SSL_VERIFYHOST may also need to be TRUE or FALSE if CURLOPT_SSL_VERIFYPEER is disabled (it defaults to 2). TRUE by default as of cURL 7.10. Default bundle installed as of cURL 7.10.

CURLOPT_TRANSFERTEXT
TRUE to use ASCII mode for FTP transfers. For LDAP, it retrieves data in plain text instead of HTML. On Windows systems, it will not set STDOUT to binary mode.

CURLOPT_UNRESTRICTED_AUTH
在使用CURLOPT_FOLLOWLOCATION产生的header中的多个locations中持续追加用户名和密码信息,即使域名已发生改变。

CURLOPT_UPLOAD
启用时允许文件传输

CURLOPT_VERBOSE
启用时会汇报所有的信息,存放在STDERR或指定的CURLOPT_STDERR中

CURLOPT_BUFFERSIZE
每次获取的数据中读入缓存的大小,这个值每次都会被填满。

CURLOPT_CLOSEPOLICY
不是CURLCLOSEPOLICY_LEAST_RECENTLY_USED就是CURLCLOSEPOLICY_OLDEST,还存在另外三个,但是curl暂时还不支持。.

CURLOPT_CONNECTTIMEOUT
在发起连接前等待的时间,如果设置为0,则不等待。

CURLOPT_DNS_CACHE_TIMEOUT
设置在内存中保存DNS信息的时间,默认为120秒。

CURLOPT_FTPSSLAUTH
The FTP authentication method (when is activated): CURLFTPAUTH_SSL (try SSL first), CURLFTPAUTH_TLS (try TLS first), or CURLFTPAUTH_DEFAULT (let cURL decide).

CURLOPT_HTTP_VERSION
设置curl使用的HTTP协议,CURL_HTTP_VERSION_NONE(让curl自己判断),CURL_HTTP_VERSION_1_0(HTTP/1.0),CURL_HTTP_VERSION_1_1(HTTP/1.1)

CURLOPT_HTTPAUTH
使用的HTTP验证方法,可选的值有:CURLAUTH_BASIC,CURLAUTH_DIGEST,CURLAUTH_GSSNEGOTIATE,CURLAUTH_NTLM,CURLAUTH_ANY,CURLAUTH_ANYSAFE,可以使用“|”操作符分隔多个值,curl让服务器选择一个支持最好的值,CURLAUTH_ANY等价于CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_GSSNEGOTIATE | CURLAUTH_NTLM,CURLAUTH_ANYSAFE等价于CURLAUTH_DIGEST | CURLAUTH_GSSNEGOTIATE | CURLAUTH_NTLM

CURLOPT_INFILESIZE
设定上传文件的大小

CURLOPT_LOW_SPEED_LIMIT
当传输速度小于CURLOPT_LOW_SPEED_LIMIT时,PHP会根据CURLOPT_LOW_SPEED_TIME来判断是否因太慢而取消传输。

CURLOPT_LOW_SPEED_TIME
The number of seconds the transfer should be below CURLOPT_LOW_SPEED_LIMIT for PHP to consider the transfer too slow and abort.
当传输速度小于CURLOPT_LOW_SPEED_LIMIT时,PHP会根据CURLOPT_LOW_SPEED_TIME来判断是否因太慢而取消传输。

CURLOPT_MAXCONNECTS
允许的最大连接数量,超过是会通过CURLOPT_CLOSEPOLICY决定应该停止哪些连接

CURLOPT_MAXREDIRS
指定最多的HTTP重定向的数量,这个选项是和CURLOPT_FOLLOWLOCATION一起使用的。

CURLOPT_PORT
一个可选的用来指定连接端口的量

CURLOPT_PROXYAUTH
The HTTP authentication method(s) to use for the proxy connection. Use the same bitmasks as described in CURLOPT_HTTPAUTH. For proxy authentication, only CURLAUTH_BASIC and CURLAUTH_NTLM are currently supported.

CURLOPT_PROXYPORT
The port number of the proxy to connect to. This port number can also be set in CURLOPT_PROXY.

CURLOPT_PROXYTYPE
Either CURLPROXY_HTTP (default) or CURLPROXY_SOCKS5.

CURLOPT_RESUME_FROM
在恢复传输时传递一个字节偏移量(用来断点续传)

CURLOPT_SSL_VERIFYHOST
1 to check the existence of a common name in the SSL peer certificate.
2 to check the existence of a common name and also verify that it matches the hostname provided.

CURLOPT_SSLVERSION
The SSL version (2 or 3) to use. By default PHP will try to determine this itself, although in some cases this must be set manually.

CURLOPT_TIMECONDITION
如果在CURLOPT_TIMEVALUE指定的某个时间以后被编辑过,则使用CURL_TIMECOND_IFMODSINCE返回页面,如果没有被修改过,并且CURLOPT_HEADER为true,则返回一个"304 Not Modified"的header,CURLOPT_HEADER为false,则使用CURL_TIMECOND_ISUNMODSINCE,默认值为CURL_TIMECOND_IFMODSINCE

CURLOPT_TIMEOUT
设置curl允许执行的最长秒数

CURLOPT_TIMEVALUE
设置一个CURLOPT_TIMECONDITION使用的时间戳,在默认状态下使用的是CURL_TIMECOND_IFMODSINCE

CURLOPT_CAINFO
The name of a file holding one or more certificates to verify the peer with. This only makes sense when used in combination with CURLOPT_SSL_VERIFYPEER.

CURLOPT_CAPATH
A directory that holds multiple CA certificates. Use this option alongside CURLOPT_SSL_VERIFYPEER.

CURLOPT_COOKIE
设定HTTP请求中“Set-Cookie:”部分的内容。

CURLOPT_COOKIEFILE
包含cookie信息的文件名称,这个cookie文件可以是Netscape格式或者HTTP风格的header信息。

CURLOPT_COOKIEJAR
连接关闭以后,存放cookie信息的文件名称

CURLOPT_CUSTOMREQUEST
A custom request method to use instead of "GET" or "HEAD" when doing a HTTP request. This is useful for doing "DELETE" or other, more obscure HTTP requests. Valid values are things like "GET", "POST", "CONNECT" and so on; i.e. Do not enter a whole HTTP request line here. For instance, entering "GET /index.html HTTP/1.0\r\n\r\n" would be incorrect.
Note: Don't do this without making sure the server supports the custom request method first.

CURLOPT_EGBSOCKET
Like CURLOPT_RANDOM_FILE, except a filename to an Entropy Gathering Daemon socket.

CURLOPT_ENCODING
header中“Accept-Encoding: ”部分的内容,支持的编码格式为:"identity","deflate","gzip"。如果设置为空字符串,则表示支持所有的编码格式

CURLOPT_FTPPORT
The value which will be used to get the IP address to use for the FTP "POST" instruction. The "POST" instruction tells the remote server to connect to our specified IP address. The string may be a plain IP address, a hostname, a network interface name (under Unix), or just a plain '-' to use the systems default IP address.

CURLOPT_INTERFACE
在外部网络接口中使用的名称,可以是一个接口名,IP或者主机名。

CURLOPT_KRB4LEVEL
KRB4(Kerberos 4)安全级别的设置,可以是一下几个值之一:"clear","safe","confidential","private"。默认的值为"private",设置为null的时候表示禁用KRB4,现在KRB4安全仅能在FTP传输中使用。

CURLOPT_POSTFIELDS
在HTTP中的“POST”操作。如果要传送一个文件,需要一个@开头的文件名

CURLOPT_PROXY
设置通过的HTTP代理服务器

CURLOPT_PROXYUSERPWD
连接到代理服务器的,格式为“[username]:[password]”的用户名和密码。

CURLOPT_RANDOM_FILE
设定存放SSL用到的随机数种子的文件名称

CURLOPT_RANGE
设置HTTP传输范围,可以用“X-Y”的形式设置一个传输区间,如果有多个HTTP传输,则使用逗号分隔多个值,形如:"X-Y,N-M"。

CURLOPT_REFERER
设置header中"Referer: " 部分的值。

CURLOPT_SSL_CIPHER_LIST
A list of ciphers to use for SSL. For example, RC4-SHA and TLSv1 are valid cipher lists.

CURLOPT_SSLCERT
传递一个包含PEM格式证书的字符串。

CURLOPT_SSLCERTPASSWD
传递一个包含使用CURLOPT_SSLCERT证书必需的密码。

CURLOPT_SSLCERTTYPE
The format of the certificate. Supported formats are "PEM" (default), "DER", and "ENG".

CURLOPT_SSLENGINE
The identifier for the crypto engine of the private SSL key specified in CURLOPT_SSLKEY.

CURLOPT_SSLENGINE_DEFAULT
The identifier for the crypto engine used for asymmetric crypto operations.

CURLOPT_SSLKEY
The name of a file containing a private SSL key.

CURLOPT_SSLKEYPASSWD
The secret password needed to use the private SSL key specified in CURLOPT_SSLKEY.
Note: Since this option contains a sensitive password, remember to keep the PHP script it is contained within safe.

CURLOPT_SSLKEYTYPE
The key type of the private SSL key specified in CURLOPT_SSLKEY. Supported key types are "PEM" (default), "DER", and "ENG".

CURLOPT_URL
需要获取的URL地址,也可以在PHP的curl_init()函数中设置。

CURLOPT_USERAGENT
在HTTP请求中包含一个”user-agent”头的字符串。

CURLOPT_USERPWD
传递一个连接中需要的用户名和密码,格式为:“[username]:[password]”。

CURLOPT_HTTP200ALIASES
设置不再以error的形式来处理HTTP 200的响应,格式为一个数组。

CURLOPT_HTTPHEADER
设置一个header中传输内容的数组。

CURLOPT_POSTQUOTE
An array of FTP commands to execute on the server after the FTP request has been performed.

CURLOPT_QUOTE
An array of FTP commands to execute on the server prior to the FTP request.

CURLOPT_FILE
设置输出文件的位置,值是一个资源类型,默认为STDOUT (浏览器)。

CURLOPT_INFILE
在上传文件的时候需要读取的文件地址,值是一个资源类型。

CURLOPT_STDERR
设置一个错误输出地址,值是一个资源类型,取代默认的STDERR。

CURLOPT_WRITEHEADER
设置header部分内容的写入的文件地址,值是一个资源类型。

CURLOPT_HEADERFUNCTION
设置一个回调函数,这个函数有两个参数,第一个是curl的资源句柄,第二个是输出的header数据。header数据的输出必须依赖这个函数,返回已写入的数据大小。

CURLOPT_PASSWDFUNCTION
设置一个回调函数,有三个参数,第一个是curl的资源句柄,第二个是一个密码提示符,第三个参数是密码长度允许的最大值。返回密码的值。

CURLOPT_READFUNCTION
设置一个回调函数,有两个参数,第一个是curl的资源句柄,第二个是读取到的数据。数据读取必须依赖这个函数。返回读取数据的大小,比如0或者EOF。

CURLOPT_WRITEFUNCTION
设置一个回调函数,有两个参数,第一个是curl的资源句柄,第二个是写入的数据。数据写入必须依赖这个函数。返回精确的已写入数据的大小

curl_copy_handle()函数的作用是拷贝一个curl连接资源的所有内容和参数

<?php
$ch = curl_init("http://www.baidu.com/");
$another = curl_copy_handle($ch);
curl_exec($another);
curl_close($another);
?>

curl_error()函数的作用是返回一个包含当前会话错误信息的字符串。
curl_errno()函数的作用是返回一个包含当前会话错误信息的数字编号。

curl_multi_init()函数的作用是初始化一个curl批处理句柄资源。
curl_multi_add_handle()函数的作用是向curl批处理会话中添加单独的curl句柄资源。curl_multi_add_handle()函数有两个参数,第一个参数表示一个curl批处理句柄资源,第二个参数表示一个单独的curl句柄资源。
curl_multi_exec()函数的作用是解析一个curl批处理句柄,curl_multi_exec()函数有两个参数,第一个参数表示一个批处理句柄资源,第二个参数是一个引用值的参数,表示剩余需要处理的单个的curl句柄资源数量。
curl_multi_remove_handle()函数表示移除curl批处理句柄资源中的某个句柄资源,curl_multi_remove_handle()函数有两个参数,第一个参数表示一个curl批处理句柄资源,第二个参数表示一个单独的curl句柄资源。
curl_multi_close()函数的作用是关闭一个批处理句柄资源。

<?php
$ch1 = curl_init();
$ch2 = curl_init();
curl_setopt($ch1, CURLOPT_URL, "http://www.baidu.com/");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://www.google.com/");
curl_setopt($ch2, CURLOPT_HEADER, 0);
$mh = curl_multi_init();
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);
do {
 
curl_multi_exec($mh,$flag);
} while ($flag > 0);
curl_multi_remove_handle($mh,$ch1);
curl_multi_remove_handle($mh,$ch2);
curl_multi_close($mh);
?>

curl_multi_getcontent()函数的作用是在设置了CURLOPT_RETURNTRANSFER的情况下,返回获取的输出的文本流。

curl_multi_info_read()函数的作用是获取当前解析的curl的相关传输信息。

curl_multi_select()
Get all the sockets associated with the cURL extension, which can then be "selected"

posted @ 2009-03-02 17:59 小马歌 阅读(827) | 评论 (2)编辑 收藏
 
     摘要: <?php /* $Id: config.inc.php,v 1.204.2.1 2003/10/10 14:24:24 nijel Exp $ */ // vim: expandtab sw=4 ts=4 sts=4: /** * phpMyAdmin Configuration File * * All directives are explained in Docume...  阅读全文
posted @ 2009-03-02 17:58 小马歌 阅读(5935) | 评论 (1)编辑 收藏
 

 

版权声明:如有转载请求,请注明出处:http://blog.csdn.net/yzhz  杨争  

        web项目指基于web的开发项目,由于web开发的一些特点,使得web开发的项目管理与以往的软件开发项目管理有很大的不同,具体表现在
1、web项目周期短。
        一般的web项目的周期为1~3月,而一般的软件开发的周期都在半年以上,象vista微软花费了五年的时间才开发出来。
2、web项目要求上线快。
        互联网公司推出的产品,讲究快字当头,谁先推出产品占领市场,谁就取得先机,所以web的项目往往要求上线快,对于比较大的项目通常我们会先把产品先launch上线,然后第二期第三期再来完善。
      “快”应该是web开发和通常的软件开发的最大区别,web产品的维护是在服务器端,这就使得这种快成为可能,我们可以很容易地随时升级产品,而通常的软件由于是部署在用户的机器上,升级的频率和幅度没办法与web产品比拟。
        也正由于这个“快”,使得web项目的需求变更成为了web项目管理中最需解决的问题。
 
        web项目经理手册分为若干主题,每个专题从项目管理的某个方面介绍项目经理在这方面要做的事情,专题会陆续推出。
       本手册为本人在项目管理中的经验总结,所以手册的内容也会不断完善中。

       本手册的原则:
1、指导性强。
2、实用性强。
        我一直崇尚这么一句话:把问题复杂化是为了帮助我们更好地理解这个问题,而把问题简单化是为了让我们更好地执行。所以本手册把简单可行作为标准。一个再好的流程如果不简单可行,最终也没法在实际工作中推广起来。当然简单的含义不是要少做事情,而是所做的事情让执行的人觉得就该怎么做,不这么做,质量就没法保证,并且执行起来很自然。

对阅读者的要求:
1、本手册来源与本人平时项目管理的经验,不同公司有不同的特点,项目本身也有差别,本手册虽然阐述的是具有普遍性的问题,但是遇到一些具体特殊问题,大家还是要以实际情况为准,本手册可以起到参考作用。

web项目经理手册-版本控制流程

        大家在项目过程中是否会经常发生以下问题:
1、测试人员在测试阶段更新测试环境时,发现编译不通过,或者应用出现异常,无法进行测试。后来发现的根源是测试和开发共用一个分支。
2、有一天某个人群发了一条邮件通知,“我们的项目代码已经发到主干,这段时间大家不要修改主干信息”,这样影响其他项目的正常发布。
3、项目进行了比较长的时间,等最后发布,需要与主干进行合并的时候,出现大量的冲突,几乎没法处理。而且冲突处理完后我们还需要重新再做测试,以保证我们的冲突处理没有问题,这样又会需要花费大量的时间。

 版本控制流程目标:
1、保证各个环境(开发、测试、主干)的独立,避免相互影响。
2、减少最终发布时合并主干出现冲突的概率。
3、降低冲突处理的难度。

原则:
多个版本(开发版本,测试版本,发布版本);多次合并。

流程:
1、项目开发编码前从当前主干建立一条开发分支,供项目开发人员使用;
2、开发结束,提交测试的时候,从当前主干建立一条测试分支,将开发分支合并到测试分支上,供测试人员进行测试。这样开发人员对开发分支的修改不会影响测试环境;
3、bug fix的时候我们定时将开发分支的修改合并到测试环境中。
3、回归测试的时候,从当前主干建议一条发布分支,将测试分支合并到该发布分支上,在发布分支上进行回归测试。
4、发布前,将发布分支合并到当前主干。

好处:
1、多个版本相互独立,互不影响
2、通过多次与主干的合并,这样发布时候和主干做最后一次合并的冲突会大大减少,并且在与主干多次合并过程中的冲突解决都在测试阶段中得到了测试。

建议:
如果项目的周期比较长,和主干进行合并的次数也应该加大,以降低处理冲突的难度。

 

web项目经理手册-开发时间估算

 

        项目经理制定项目时间表的时候,需要估算每个任务所需的时间,其中开发任务中模块的分配和时间估算是其中最主要的部分。本篇专门就这部分作一个阐述。

一、在分配模块和估算开发时间时,我们需要把握的原则和目标:
1、保证项目整体的进度。
2、有助于确保开发编码的质量。
3、有助于提高开发编码的速度。


二、每个公司都拥有自己的技术框架,开发人员主要的工作通常投入在具体的商业逻辑上。
通常每个模块所需的开发时间取决于以下三个因素:
1、该模块的商业逻辑的复杂程度。
2、开发人员的技术水平和对项目所在应用的熟悉程度(包括对框架和应用的熟悉程度)。
3、该模块技术实现上是否有技术难点。这里我把技术难点定义为:在现有系统中还未实现的有一定技术难点的问题。对于这样的难题,开发者没有相关的代码可以参考,需要投入一些时间研究解决。

三、模块分配和开发时间估算的步骤:
1、作为项目经理划分好模块后,我会自己先估算一下每个模块所需要的开发时间。

2、召集所有开发人员,讨论模块分配和开发时间估算。
      项目经理将划分好的模块,让开发人员从中挑选他们感兴趣的模块。这样做可以提高开发人员的主动性和参与性。
      项目经理在分配模块的时候还需从以下几方面考虑,以确保开发的速度和质量。
 (1)相同类似的模块由同一人负责开发,比如文章的增删改由同一开发者负责。这样做的好处就是开发者对相关逻辑会更加熟悉,同时接口的定义也会比较明确,沟通的成本比较低。
 (2)技术难度比较大的模块由技术水平比较高的人负责。
 (3)业务逻辑比较复杂的由对这块逻辑比较了解的人负责。


 3、模块分配完后,开发人员评估自己负责开发的模块所需要的时间。在此过程中我们会比较详细的讨论每个模块的技术实现,以便使时间的估算更加准确。
 
 4、项目经理对开发人员估算的时间进行确认。
        在确认过程中作为项目经理我会参考以上提到的三个因素,同时将自己估算的时间和开发人员估算的时间进行比较。这其中的差异当然会存在的。对于那些差异比较大的,我会和技术人员探讨其中的缘由。
        对于时间周期比较长的任务,我通常会再细分一下,争取每个任务的最长时间不超过3天。时间周期越长的任务,不确定性越高,风险也越高,越有可能成为项目的瓶颈。
 
 
建议:
1、项目总结的时候,对项目中的一些数据做好统计比如单位UC所花的开发时间、测试时间等,这些数据统计可以作为以后开发的参考。
2、对技术难点,在项目开始前做好技术准备,提前安排人员研究。这样会节省以后项目时间,降低技术风险。

 

web项目经理手册-Code Review

 

     Code Review是保证项目中代码质量非常重要的一个环节,其主要工作是:
1、发现代码中的bug;
2、从代码的易维护性、可扩展性角度考察代码的质量,提出修改建议。

1、代码中的bug主要会出现在下列两个地方:
(1) 与商业逻辑无关的bug。
        比如,系统中打开的流/文件/连接等没有及时关闭;或是存在thread safe问题,或是存在性能低下问题等,这类问题对有经验的开发人员是比较容易发现的。

2、与商业逻辑相关的bug。
        这类bug是非常隐蔽的,如果有对产品不熟悉的人参与该产品的项目开发,容易出现这类的bug。为了避免这类bug的出现,我们除了在Use Case和Test Case中详细描述以正确指导开发人员并在测试时能及时发现它之外,Code Review也是不可缺少的保证环节。
        我们希望代码的审核者对产品非常熟悉。

3、什么样的人承担代码审核者Code Reviewer?
(1)、比较熟悉相关商业逻辑。
(2)、有丰富的编程经验。
两者缺一不可。

4、代码Code Review的步骤,这些是我在平时工作中的经验总结,目前也是按照这个步骤在做。
(1)、代码编写者和代码审核者坐在一起,由代码编写者按照UC依次讲解自己负责的代码和相关逻辑,从Web层->DAO层;
(2)、代码审核者在此过程中可以随时提出自己的疑问,同时积极发现隐藏的bug;对这些bug记录在案。
(3)、代码讲解完毕后,代码审核者给自己安排几个小时再对代码审核一遍。
        代码需要一行一行静下心看。同时代码又要全面的看,以确保代码整体上设计优良。

(4)、代码审核者根据审核的结果编写“代码审核报告”,“审核报告”中记录发现的问题及修改建议,然后把“审核报告”发送给相关人员。

(5)、代码编写者根据“代码审核报告”给出的修改意见,修改好代码,有不清楚的地方可积极向代码审核者提出。

(6)、代码编写者 bug fix完毕之后给出反馈。

(7)、代码审核者把Code Review中发现的有价值的问题更新到"代码审核规范"的文档中,对于特别值得提醒的问题可群发email给所有技术人员。

5、责任:
        代码编写者,代码审核者共同对代码的质量承担责任。这样才能保证Code Review不是走过场,其中代码编写者承担主要责任,代码审核者承担次要责任。

6、Code Review必备的文档:
      “代码审核规范”文档:记录代码应该遵循的标准。代码审核者根据这些标准来Code Review代码,同时在Code Review过程中不断完善该文档。

web项目经理手册-需求变更管理

 

  需求变更管理是web项目管理中最重要的一个环节,需求变更管理的有效性直接影响项目的成功与否。

  对待变更的态度:
1、变更是不可避免的。
2、变更必须被管理。
3、积极发现引起变更的因素,促使变更尽可能早的出现,减低变更带来的风险。

需求变更管理的目标:
1、相关的干系人必须清楚地了解发生的变更。
2、变更处于有效的管理中。
3、尽量降低变更带来的风险。

  通过制定需求变更的流程,确保项目中的需求变更有效地进行,实现上述的目标。

  需求变更流程:
1、确定需求的基准线。
 通常我们会以User Case作为需求基准线,在User Case确认之后的任何需求改变,都需要走需求变更流程。没有走需求变更流程的需求将不被认可。

2、首先项目经理接收到需求变更的要求。
  需求变更的提出者可以是项目中的任何人包括产品经理、客服、开发人员、测试人员等。
 
3、项目经理评估该需求变更。
  项目经理可以召集相关人员讨论该需求变更的合理性、可行性,实施的代价以及对项目的影响。
 项目经理作为项目的负责人,对项目的成功负有主要的责任。所以需求变更的决策者应该由项目经理承担。
 
4、需求变更确认后由专人将需求变更记录下来(格式如下),通知给项目中所有成员。其中以下人员对需求的变更是紧密相关的,他们必须知晓并认可此需求变更。包括(客户方代表,需求分析师,测试人员,相关开发人员)。
需求变更表的格式:

序号

变更提出时间

变更描述

变更类型(是对原有需求的修改还是新增需求)

原因

变更提出者

开发人员

对进度的影响(工作量)

5、相关人员接收到确认的需求变更后,做以下事情。
需求分析人员修改需求说明书和User Case的相关内容。
测试人员修改测试用例的相关内容。
开发人员修改代码中的相关部分。

6、需求冻结
 项目越到后期,需求变更对项目的影响就越大,所以在一定时候我们会进入需求冻结阶段,不再接收需求的变更。

 

web项目经理手册-项目经理的工作内容

 

一、项目经理的目标
1、满足项目利害关系者的不同需求。
清晰明确地了解每一个项目利害关系者的需求和期望,投其所好。
项目利害关系者包括:项目团队成员和项目团队外成员(比如各部门的部门经理,客服等)。
2、保证开发项目按时保质的完成。

二、项目经理的职责
1、建立有效的流程保证项目的顺利进行。
2、制定详细周密的项目计划。
3、跟踪,推动项目按计划进行。
4、积极解决项目过程中出现的问题和冲突。
5、调动开发团队的积极性,创造力,推动团队成员在项目过程中不断成长。

三、项目经理的具体工作

1、项目前期阶段
  . 技术可行性分析,对项目进行技术评估、成本评估以及风险评估。
 . 与需求方代表进行需求讨论,明确项目的目标、价值;确定项目范围、功能及优先级。
  . 组建项目团队,特别要搞清楚项目的key person(对产品有决定权的人)。
  . 项目启动会议。

    通常项目成员包括以下人员:项目经理、架构师、技术经理、产品经理、开发工程师、DBA、测试工程师、需求分析工程师、UI、文案、SQA、SCM。
   
阶段输出物:确认后的最终需求文档
       
2、分析设计阶段
. 制定项目进度计划,工作任务分解(WBS)。
. 资源申请-项目涉及到的开发资源、测试资源、设计资源。
. 数据库设计。
. 系统设计。
. 文档(包括UC、Demo、TC等)评审会议

阶段输出物:
(1) User Case
(2) DEMO
(3) 系统设计文档
(4) 数据库设计文档
(5) User Case等文档评审
 
3、执行阶段(开发、测试)
. 准备开发环境、测试环境。
. 跟踪,推动项目按计划进行。
. 通报项目的进展情况,通常以周报的形式。
. 对项目的阶段成果进行评估,以确保该阶段完成的质量,包括代码审核、SQL审核等。
. 对需求变更进行控制管理。
. 对项目风险进行管理。
. 测试阶段客户验收、收集反馈意见。

4、发布阶段
. 制定项目发布计划。
. 用户培训。
. 发布上线。

5、上线后监控
. 数据监控(日志、服务器状态)。
. BUG FIX及改进。

5、结束阶段
. 项目总结会。
. 产品交付。

 

web项目经理手册-项目经理需要铭记在心的话

 

1、项目经理不是来管人的,而是来支持人的。
        解析:不光是项目经理,任何经理的职位都是如此。但现实中很多人并不是那么做,这也是为什么他们没能把项目做成功的原因。作为项目经理首先要端正态度,认识到这份工作职责的本质。

2、好的开始是成功的一半。
        解析:一个好项目的失败,往往是由于前期的准备不足、计划不周密。所以在项目初期要舍得花时间做前期的需求收集、讨论、技术准备等工作。尽管前期的工作看起来并没有直接产生效益,但这块工作做好了,后面的工作往往会事半功倍。否则前期准备不足,很可能导致项目出现各种各样的问题(比如大量的需求变更等)。

3、什么样的项目最可能成功?答案是:项目越小成功的可能性越大。
       解析:项目经理和相关人员要仔细评估项目中feature的成本/价值比,尽可能缩小产品的规模。
      有时候项目经理可能改变不了整个项目规模,但是项目经理可以采用各种手段来“缩小”项目,比如分期进行、迭代开发等。
 
4、任何对项目的改善无关的工作都是浪费时间。
       解析:在项目过程中项目经理不要做表面工作,或者对项目本身无意义的工作。比如无休止的会议;要求编写详细而最终没有用处的文档。
 
5、使用者的参与是项目成功的重要保证。
      解析:使用者可以是:产品经理、需求方代表、或者客户。
      在项目的各个阶段,项目经理要积极要求使用者参与到项目过程中。通过这种与使用者不断的沟通、反馈,使得最终做出来的产品是客户真正想要的。

6、不要认为把任务交给团队成员,期间你可以不闻不问,到了完成的时间他自然会把任务交上来。这种想法是非常错误。
       解析:这样做无疑会增加项目的风险,很容易出现该完成的任务没有按时完成,有些延误,这样项目后续的工作都会收到牵制。
      正确的做法是:当把任务安排下去后,你要定期和成员沟通完成的情况,询问是否需要支持,这样我们才能保证任务能按时保质的完成。

7、沟通要诀:项目过程中与相关人员沟通时,不要总认为对方的出发点都是从项目利益考虑,他/她一定先考虑个人利益或部门利益,所以项目经理要做的是:如何把对方的个人利益(部门利益)引导到和项目利益一致。

8、“加班”是一个危险的信号,表明一定是某个地方出现了问题,要找出进度落后的原因。

9、项目开始前,项目经理一定要找出项目的决策者是谁,谁对项目的产品有最终的发言权。

10、我们交付的不是程序,而是产品和服务。 

 

web项目经理手册-风险管理

 

         风险管理是web项目中项目经理最重要的工作之一。风险管理是一个持续的过程,贯穿于整个项目过程中,风险管理包括风险识别、风险估计、风险解决以及风险管理策略。
   
        在实际web项目中,项目风险主要表现为以下情况。了解这些有助于项目经理在项目初期就识别出这些风险,并采取措施避免或者减少它们的发生。

一、web项目风险列表:
1:需求变更风险:需求已经打上了基线,但此后仍然有变更发生,对项目造成影响。
       如何减少此类风险的发生?
(1) 前期的需求讨论要详细、充分。需求文档中需求的范围要明确、功能描述要清楚。
(2) 需求文档中要有demo。对于web项目,图片比文字更能说明问题。
(3) 找出项目中需求的决策者(通常会是产品经理、相关职能主管、客服),所有的需求要经过他们的认可。
(4) 客户在项目过程中的全程参与有助于降低此类风险。需求讨论、需求确认、User Case确认、测试阶段的客户验收等环节,都要要求客户参与。
(5) 发生需求变更时,严格按照需求变更流程执行。

2、技术风险:开发过程中遇到技术难题,导致开发时间延迟或者需求不得不发生变更。
     如何减少此类风险的发生?
     在项目开始前的技术评估阶段,明确技术难点,提前安排人员进行攻克。如果在可预期的时间内无法解决,可以要求需求方变更需求。

3、质量风险:对于web项目而言,质量风险主要指开发代码的质量。
       如何提高开发人员开发的质量?
(1)、制定项目计划时,对开发时间的评估要尽可能的合适。合理的开发时间对开发质量的影响很大。开发时间评估可参考【web项目经理手册-开发时间估算】。
(2)、有一套严格可行的代码规范,编码时严格遵守,code review时严格考核。
(3)、在编码前,开发人员要对框架熟练掌握。
(4)、一份好的系统设计文档对指导开发非常重要。

4、资源风险:项目所需人力资源无法按时到位,导致资源风险。
如何减少此类风险的发生?
这个就需要在项目计划制定的时候提前申请确认资源,并在项目过程中不断沟通协调。

二、项目风险管理的要点:
1、上述我们所说的风险管理都是指可以预期将要发生的风险,那些不可预期将要发生的风险不属于风险管理的范畴。这也说明项目经理的经验和知识对能否管理好风险至关重要。
2、详细明确的项目计划、以及项目执行过程中每个要点的质量保证是降低项目风险的必要条件。
3、风险报告是项目团队以及领导了解项目风险的一个有效手段。
风险报告的格式通常是:

  序号 风险简介  对项目的影响   解决方案(对策) 

web项目经理手册-跨部门合作项目

 

       web项目中有很多项目涉及到跨部门、跨公司的合作。这类项目往往比其他项目更有挑战。对于项目经理如何做好这些项目呢?
       首先让我们看看这类项目都有哪些共同的特点。
1、合作双方工作在不同地方,对项目沟通造成一定影响。
2、合作双方隶属于不同的公司或者部门,双方的项目开发流程可能完全不同,在项目执行过程中需要考虑到这个因素。
2、合作项目需要双方共同完成,如果一方的工作进度出现延误,那么整个项目的进度都会收到影响。

      本人根据平时这类项目的实施经验,总结一下这类项目要想成功,需要把握的原则。
1、合作双方的领导层必须都非常重视这个项目。剃头挑子一头热的项目成功的可能性不会高。
只有这样,项目的优先级才有保证,这样在以后项目过程中一些资源(包括人力、硬件、时间投入)更有保证,配合起来也会更加顺畅。

2、合作双方确定好各自的接口人。双方的沟通都通过接口人进行,这样可以降低成本,提高沟通的效率。
接口人可以分为两类:一类是商业上的接口人,一类是技术上的接口人。

3、完备的文档(接口文档、数据库文档)必不可少。
web项目双方的合作在技术方面通常采用API接口方式交互。所以项目前期详细准确的接口说明文档非常重要,双方开发人员之后的开发都是严格按照接口进行。
同时接口的相对稳定也是非常重要的,所以需要前期设计的时候认真全面地考虑接口规范。
 
4、便利的沟通工具。
 对于跨地区的合作,便利的沟通工具是非常重要的。当然工具最好是免费,比如使用IM。从沟通方式的效果来看,我觉得面对面的沟通>电话沟通>EMAIL(or IM)。

5、接口变更的及时通知。
这一点很重要,接口变更应该有流程来保证,特别是对于这种成员分散在不同地方的团队尤为重要。
 
6、前期技术方案的沟通。
前期技术方案的讨论以及接口的定义,最好能当面沟通,这样效果最好。所以前期最好去一趟对方公司商谈这些要点。

7、各自开发环境的可访问问题。解决双方开发环境的相互调用问题。
合作双方联调的时候通常需要访问对方的接口。由于双方都在各自环境进行开发,所以需要解决这种问题。
最好的情况是:可以访问对方的环境(外网)。
最大的风险是:没有可以联调的环境,等到发布到正式环境上再测试,这时候时间上就有点晚了,可能会遇到一些之前预想不到的问题。所以联调的时间越提前,问题就能越快暴露出来,整个项目的风险就越小。
联调环境的稳定也非常重要。有一次我们发现我们的功能有问题,代码跟踪调试,结果发现原来对方的环境有问题,浪费了我们很多时间。

8、由于项目的各个点是互相依赖的,所以在一些关键点上要能按时提交,否则会影响对方的进度。
在项目计划中要详细定义各个重要的里程碑,并严格控制执行。

9、项目进度报告。
定时相互通告项目进度,重点关注项目风险。

10、熟悉对方项目开发的流程。
不同公司项目的流程、角色分工不一定相同。只有熟悉了对方项目的流程,在与对方沟通时候才能做正确的事情。所谓知己知彼,才能百战百胜。
千万不要自己闷头开发,完全不顾对方的做事方式,然后自己想当然他们应该和我们一样。

 

 web项目经理手册-你会沟通吗?

 

         我们常说做好项目的关键之一就是做好“沟通”,但很多人只知道“沟通”的重要性,却不知道怎么做好“沟通”,所以仍然会有很多项目由于沟通未做好而导致项目失败或者有些遗憾。
       “沟通”不仅仅是说话,不是说的越多沟通就越好。要做好“沟通”关键是清楚以下两点:我们要和谁沟通,和他(她)沟通什么,怎么和他(她)沟通。
沟通的最终目标是:让被沟通的人明白你要传递的内容,并自觉执行好你希望他做的事情。
 
要解决好沟通问题,我们需要把握以下两个原则:
一、利益原则
         利益原则解决的是“和谁沟通”的问题。
项目开始阶段我们要识别出与项目有利益的人(即项目干系人),确定他们需求和期望,然后采用合适的沟通策略。
  项目的干系人是指参与项目,或其利益在项目执行中或成功后受到积极或消极影响的个
人和组织。这些人是项目过程中需要着重关注的人群,很多项目出了问题都是由于忽视了(或者是忘了)其中某些人。
 
   项目干系人通常包括:
Ø       项目发起人、出资方。(项目决策者)
Ø       部门职能经理。(资源提供方)
Ø       项目团队成员。(项目执行者)
Ø       产品运营。(产品的运营者、使用者)
Ø       客服人员。(客户接口)
 
 为了更好地把握这一原则,我推荐项目经理在项目开始阶段使用以下表格。
 

序号
项目干系人
其对项目的主要期望
在本项目中的利益程度
(H, M, L)
对项目的影响程度
(H, M, L)
与其沟通的策略
1
 
 
 
 
 
2
 
 
 
 
 
3
 
 
 
 
 
4
 
 
 
 
 
5
 
 
 
 
 

 
 
二、闭环原则
很多项目经理在实际沟通中常常会是这样的:某某某这个事情你做一下,或者发个邮件给某某,期间也不闻不问,期望到时候那个人就会按时提交任务。这种情况往往会发生问题。
 
正确的沟通环节应该是一个闭环。具体的过程应该是这样的:
1、项目经理和项目干系人沟通事情,征询他们的意见。(双向沟通)
2、达成一致意见,确认action列表。(责任、任务落实到具体的人)。
3、执行过程中要跟踪执行情况,确认执行人是否需要协助,同时有助于识别是否存在潜在的风险发生。
4、执行结果的检查。
    沟通结束前要注意总结、回顾,以及action,以确保沟通的效果
 
 
三、 良好的沟通技巧会有助于沟通。
1、当你不知道怎么给出建议,或者如何回答的时候,建议你采用提问式的回答,比如“你觉得怎么做会好呢?”等等开放式的问题,这样有助于发挥大家的积极性,创造性,最终获得良好的效果。
2、沟通过程中尽可能少的打断,不要匆忙下结论,不要立刻针锋相对地驳斥对方。
3、要适当使用幽默。
3、积极地给予反馈。
4、了解沟通者的风格,以便更有效的沟通。
 
四、沟通的表现形式实际上是很多的,绝不要局限在面对面对话,像会议、email等都是沟通的具体表现。所以上面所说的原则和技巧都可以这些环节中采用。
 
 
       在项目中如果把握好上面所说的原则,再加上自身沟通的技巧,一定会对项目的成功起到非常大的帮助。记住和正确的人正确地做正确的事情
 
 
Web项目经理手册-组织会议
 

组织会议是项目经理日常工作中一项非常重要的工作任务,项目过程中很多重要的决定都是在会议中做出的,也有很多由于不成功的会议而对项目本身造成了不好的影响。

 

首先我们看看不成功的会议常常表现为哪些形式:

1、会议氛围不好,参与者发言不踊跃;

2、会议讨论常常偏离主题;

3、会议没有取得预期的结果;

4、会议时间常常一拖再拖。

这些不成功的会议最终的结果就是:既浪费了大家的宝贵时间又没有达到会议的目的,很多人都经历过这样的会议,对此也是深恶痛绝。

 

以下是根据我个人的经验,列出了会议组织者应该注意的问题,也可看作组织会议的最佳实践。

 

在列出最佳实践之前有三点我们必须要清楚:

1、会议是否会取得成功很大程度上取决于会议的组织者。只有组织得有力,会议才有可能取得成功,这是会议成功的充分条件。

2、会议的组织者和参与者的想法通常是不一致的,有时候甚至会大相径庭。所以不要希望会议的参与者和你一样,对会议有着如此的期待,对大多数参与者而言,在会议中他只是一个发表想法的人, 他不用对会议的成功承担责任。

3、以下十一条最佳实践是形式上的约定,具体的实施可以根据实际情况来做。

 

     组织会议的十一条最佳实践

1、只有需要开会时才开会。有时候两三个人单独小范围沟通会更加有效。

2、提前发出会议议程,以便会议参与者知道他们来做什么。

3、请对人很重要,不要把非必要的人召来开会,当然也不要漏掉那些关键人物。在确保必要人物都在的情况下一次会议参与者越少效果越好。

4、提前预约参与者的时间,以确保他们能按时到场。

5、会议的开场很重要。会议组织者要在开始前做好几件事情。通常我建议有几点要在开场时说:

  (1) 再一次强调会议的目标,我们来做什么。

  (2) 强调一下会议的基调。比如:本次会议是一个需求确认会,而非需求讨论会,主要是讨论做还是不做以及告知大家我们要做什么,而不要把太多的精力放在讨论如何做上面。

  (3) 说明一下会议的规则。如要发言,请举手;不要有小圈子讨论;不要打断别人的讲话,等别人说完你再说等等。

6、会议过程中时刻注意引导和控制会议,以确保会议按照目标进行。一次会议的氛围是否良好,讨论是否充分,好的引导至关重要。比如多提一些开放式的问题。

7、会议记录很重要,把一些结论和有价值的内容记录下来,这些是本次会议的重要成果之一。

8、会议要有结论。我们常在会议上听到有人说:"大家讨论了这么半天,结论呢?"。没有结论的会议是没有意义的。

9、会议后别忘发会议纪要,以及一些Action,什么人什么时候做什么。

10、会议后的action执行情况的反馈很重要。反馈是对会议参与者的尊重,同时也告知了会议的效果。否则会让大家感觉到这是一个可无可无的会议,大家以后参与的积极性也会降低。很多会议往往都不注意这一点。

11、按时结束的会议会受到所有人的欢迎。

 

      这十一条最佳实践,我认为适用于项目过程中的大多数会议,其他类型的会议也可以采用。

posted @ 2009-03-02 17:57 小马歌 阅读(472) | 评论 (0)编辑 收藏
 

apache DSO模式详解原理

那么DSO究竟是什么?事实上DSO是Dynamic SharedObjects(动态共享目标)的缩写,它是现代Unix派生出来的操作系统都存在着的一种动态连接机制。它提供了一种在运行时将特殊格式的代码,在程序运行需要时,将需要的部分从外存调入内存执行的方法。Apache在1.3以后的版本后开始支持它。因为Apache早就使用一个模块概念来扩展它的功能并且在内部使用一个基于调度的列表来链接扩展模块到Apache核心模块.所以, Apache早就注定要使用DSO来在运行时加载它的模块。
在mod_ssl和mod_rewrite的作者Ralf S. Engelschall(在我看来他是真正的Apache大师和主要开发者之一,他的个人主页上 的名篇“Apache 1.3 Dynamic Shared Object (DSO) Support”
中对DSO模式的原理有着比较详尽的叙述(本文基本基于此)。


让我们先来看一下Apache本身的程序结构:这是一个很复杂的四层结构–每一层构建在下一层之上。
第四层是用Apache模块开发的第三方库–比如open ssl一般来说在Apache的官方发行版中这层是空的,但是在实际的Apache结构中这些库构成的层结构肯定是存在的。
第三层是一些可选的附加功能模块–如mod_ssl,mod_perl。这一层的每个模块通常实现的是Apache的一个独立的分离的功能而事实上这些模块没有一个是必须的,运行一个最小的Apache不需要任何一个此层的模块。
第二层是Apache的基本功能库-这也是Apache的核心本质层–这层包括Apache内核,http_core(Apache的核心模块),它们实现了基本HTTP功能(比如资源处理(通过文件描述符和内存段等等),保持预生成(pre-forked)子进程模型,监听已配置的虚拟服务器的TCP/IP套接字,传输HTTP请求流到处理进程,处理HTTP协议状态,读写缓冲,此外还有附加的许多功能比如URL和MIME头的解析及DSO的装载等),也提供了Apache的应用程序接口(API)(其实Apache的真正功能还是包含在内部模块中的,为了允许这些模块完全控制Apache进程,内核必须提供API接口),这层也包括了一般性的可用代码库(libap)和实现正则表达式匹配的库(libregex)还有就是一个小的操作系统的抽象库(libos)。
最低层是与OS相关的平台性应用函数,这些OS可以是不同现代UNIX的变种,win32,os/2,MacOS甚至只要是一个POSIX子系统。
在这个复杂的程序结构中有趣的部分是—事实上第三层和第四层与第二层之间是松散的连接,而另一方面第三层的模块间是互相依赖的–因这种结构造成的显著影响就是第三层和第四层的代码不能静态地连接到最低层平台级的代码上。因此DSO模式就成了解决它的一种手段。结合DSO功能,这个结构就变得很灵活了,可以让Apache内核(从技术上说应该是mod_so模块而不是内核)在启动时(而不是安装时)装载必要的部分以实现第三层和第四层的功能。
DSO在程序运行时将需要的部分从外存调入内存执行存取通常会有两种途径:一种是ld由系统在程序开始时自动载入,这种载入可以由两种途径实现:一种是自动由系统在可执行程序开始时调用ld.so来执行。另一种是手动,是通过在执行程序里程序系统界面到Unix装载程序通过系统调用tdlopen()/dlsym()来执行的.
那么具体来说Apache是怎么实现DSO功能的呢?DSO支持调用特别的Apache模块是基于一个名叫mod_so.c的模块,这个模块必须静态的编译Apache的核心。这是除了http_core.c以外仅有的模块不可以被放到DSO自己(bootstrapping!)。实际上所有的别的发布的Apache模块都可以被放置到DSO,通过个别的通过设置被DSO建立允许–允许可能的-共享设置(看顶层的INSTALL文件)或者通过改变 AddModule命令在你的src/Configuration到SharedModule命令(看src/INSTALL文件)。
在编译一个模块到一个名字叫mod_foo.so的DSO以后,你能够使用mod_so的LoadModule命令在你的httpd.conf文件里在服务程序开始时或重新启动以后调用这个模块。

为了简化这种为了Apache模块而创建DSO文件的方法(尤其是第三方模块),一个新的名叫apxs的支持程序(APacheeXtenSion)被使用.它可以用来建立基于DSO的模块,模块位于Apache源码树以外。这个思路很简单:当安装Apache时,设置使安装程序安装Apache C头文件并且放置平台支持的编译器和链接程序标志,用来建立DSO文件到apxs程序。通过这种方法,用户可以使用apxs来编译它的Apache模块源代码而不用Apache发布源代码树并且不用手动的添加平台支持的编译程序和谅解程序的标志来获得DSO支持。
为了放置编译好的apache核心程序到一个DSO库(仅仅在一些支持的平台上需要强迫链接程序输出Apache核心的地址码–一个DSO模块化的先决条件)规定的SHARED_CORE必须能够通过设置–允许-规定=SHARED_CORE设置(看顶层的INSTALL文件)或者通过改变Rule命令,在你的Configuration 文件里规定SHARED_CORE=yes(看src/INSTALL文件).Apache核心代码接着被放置到一个名叫libhttpd.so的DSO库。因为静态库不能够在所有的平台上被链接成为一个DSO,一个附加的名叫 libhttpd.ep的可执行程序被创建,这个程序不但包含这个静态代码而且有提供main()剩余部分的功能.最后,httpd程序自己被用bootstrapping代码替换,后者自动确定Unix调度程序能够装载并且开始libhttpd.ep,它通过提供LD_LIBRARY_PATH到libhttpd.so的方法实现.
最后我们再来说说ApacheDSO模式支持的平台和它的优缺点–应该说目前DSO模式基本可运行在任何unix平台上–除了ultrix(因为它没有动态(库)装载器即:Nodlopen-style interface under thisplatform).它的优点是服务器包能够在运行时更加灵活并且服务器包能够简单的用第三方模块来扩展,那怕是在安装之后。但同时DSO模式也有一些缺点如:

1、不能工作在所有平台下比如刚才说的不支持动态连接的ultrix。
2、Apache会在启动时慢大约20%,因为要做一些前置作业,而地址码的解决现在需要UNIX调度程序来做。服务器在某些平台在执行时会慢5%,因为相对地址代码(PIC)有时在不必要时也会需要重新编译相对寻址,所以没有绝对地址快.因此有时DSO不会提高速度。
3、因为DSO模块会在个别平台上与别的基于DSO的库(ld -lfoo)发生冲突(例如a.out-based 平台经常不支持这个功能,但是ELF-based平台支持)你不能为所有类型的模块使用DSO机制(即不是所有的DSO模块都能被加载)。或者换一句话说,模块作为DSO文件编译是受限制,只能使用APACHE核心地址码,APACHE核心使用的C库(libc)和所有的别的动态和静态的库,或者静态库档案(libfoo.a)包含的独立的代码。唯一使用别的代码的办法是或者确定APACHE核心自己早就包含自己的参考,通过dlopen()调用代码自己,或者在建立APACHE时允许 SHARED_CHAIN规则(当你的平台支持不用DSO库链接DSO文件)。
在一些平台下(许多SVR4系统)在链接Apache httpd可执行程序时没有办法强迫链接程序输出所有全部的DSO所用的地址.但是没有可见的Apache核心地址码就没有标准的Apache模块能够作为DSO使用.唯一的办法是使用SHARED_CORE特性,因为这种方法使全部的地址码都被强制输出。作为结果,Apache src/Configure script自动强制SHARED_CORE在这些平台上,当DSO特性被用在Configuration文件或者在configure命令行.

posted @ 2009-03-02 17:55 小马歌 阅读(202) | 评论 (0)编辑 收藏
 

章:   PHP And Socket
书名: 《PHP Game Programming》
作者:   Matt Rutledget
翻译:   heiyeluren <heiyeluren_gmail_com>

◇ Socket基础
◇ 产生一个服务器
◇  产生一个客户端

在这一章里你将了解到迷人而又让人容易糊涂的套接字(Sockets)。Sockets在PHP中是没有充分利用的功能。今天你将看到产生一个能使用客户端连接的服务器,并在客户端使用socket进行连接,服务器端将详细的处理信息发送给客户端。
当你看到完整的socket过程,那么你将会在以后的程序开发中使用它。这个服务器是一个能让你连接的HTTP服务器,客户端是一个Web浏览器,这是一个单一的客户端/服务器 的关系。

◆ Socket 基础


PHP使用Berkley的socket库来创建它的连接。你可以知道socket只不过是一个数据结构。你使用这个socket数据结构去开始一个客户端和服务器之间的会话。这个服务器是一直在监听准备产生一个新的会话。当一个客户端连接服务器,它就打开服务器正在进行监听的一个端口进行会话。这时,服务器端接受客户端的连接请求,那么就进行一次循环。现在这个客户端就能够发送信息到服务器,服务器也能发送信息给客户端。
产生一个Socket,你需要三个变量:一个协议、一个socket类型和一个公共协议类型。产生一个socket有三种协议供选择,继续看下面的内容来获取详细的协议内容。
定义一个公共的协议类型是进行连接一个必不可少的元素。下面的表我们看看有那些公共的协议类型。

表一:协议
名字/常量     描述
AF_INET  这是大多数用来产生socket的协议,使用TCP或UDP来传输,用在IPv4的地址
AF_INET6     与上面类似,不过是来用在IPv6的地址
AF_UNIX  本地协议,使用在Unix和Linux系统上,它很少使用,一般都是当客户端和服务器在同一台及其上的时候使用
表二:Socket类型
名字/常量     描述
SOCK_STREAM  这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
SOCK_DGRAM  这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
SOCK_SEQPACKET  这个协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
SOCK_RAW  这个socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
SOCK_RDM  这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序

表三:公共协议
名字/常量     描述
ICMP  互联网控制消息协议,主要使用在网关和主机上,用来检查网络状况和报告错误信息
UDP      用户数据报文协议,它是一个无连接,不可靠的传输协议
TCP 传输控制协议,这是一个使用最多的可靠的公共协议,它能保证数据包能够到达接受者那儿,如果在传输过程中发生错误,那么它将重新发送出错数据包。

现在你知道了产生一个socket的三个元素,那么我们就在php中使用socket_create()函数来产生一个socket。这个 socket_create()函数需要三个参数:一个协议、一个socket类型、一个公共协议。socket_create()函数运行成功返回一个包含socket的资源类型,如果没有成功则返回false。
Resourece socket_create(int protocol, int socketType, int commonProtocol);

现在你产生一个socket,然后呢?php提供了几个操纵socket的函数。你能够绑定socket到一个IP,监听一个socket的通信,接受一个socket;现在我们来看一个例子,了解函数是如何产生、接受和监听一个socket。

<?php
$commonProtocol = getprotobyname(“tcp”);
$socket = socket_create(AF_INET, SOCK_STREAM, $commonProtocol);
socket_bind($socket, ‘localhost’, 1337);
socket_listen($socket);
// More socket functionality to come
?>

上面这个例子产生一个你自己的服务器端。例子第一行,
$commonProtocol = getprotobyname(“tcp”);
使用公共协议名字来获取一个协议类型。在这里使用的是TCP公共协议,如果你想使用UDP或者ICMP协议,那么你应该把getprotobyname() 函数的参数改为“udp”或“icmp”。还有一个可选的办法是不使用getprotobyname()函数而是指定SOL_TCP或SOL_UDP在 socket_create()函数中。
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
例子的第二行是产生一个socket并且返回一个socket资源的实例。在你有了一个socket资源的实例以后,你就必须把socket绑定到一个IP地址和某一个端口上。
socket_bind($socket, ‘localhost’, 1337);
在这里你绑定socket到本地计算机(127.0.0.1)和绑定socket到你的1337端口。然后你就需要监听所有进来的socket连接。
socket_listen($socket);
在第四行以后,你就需要了解所有的socket函数和他们的使用。

表四:Socket函数
函数名      描述
socket_accept()    接受一个Socket连接
socket_bind()     把socket绑定在一个IP地址和端口上
socket_clear_error()   清除socket的错误或者最后的错误代码
socket_close()     关闭一个socket资源
socket_connect()    开始一个socket连接
socket_create_listen()   在指定端口打开一个socket监听
socket_create_pair()   产生一对没有区别的socket到一个数组里
socket_create()    产生一个socket,相当于产生一个socket的数据结构
socket_get_option()    获取socket选项
socket_getpeername()   获取远程类似主机的ip地址
socket_getsockname()   获取本地socket的ip地址
socket_iovec_add()    添加一个新的向量到一个分散/聚合的数组
socket_iovec_alloc()   这个函数创建一个能够发送接收读写的iovec数据结构
socket_iovec_delete()   删除一个已经分配的iovec
socket_iovec_fetch()   返回指定的iovec资源的数据
socket_iovec_free()    释放一个iovec资源
socket_iovec_set()    设置iovec的数据新值
socket_last_error()    获取当前socket的最后错误代码
socket_listen()     监听由指定socket的所有连接
socket_read()     读取指定长度的数据
socket_readv()     读取从分散/聚合数组过来的数据
socket_recv()     从socket里结束数据到缓存
socket_recvfrom()    接受数据从指定的socket,如果没有指定则默认当前socket
socket_recvmsg()    从iovec里接受消息
socket_select()     多路选择
socket_send()     这个函数发送数据到已连接的socket
socket_sendmsg()    发送消息到socket
socket_sendto()    发送消息到指定地址的socket
socket_set_block()    在socket里设置为块模式
socket_set_nonblock()   socket里设置为非块模式
socket_set_option()    设置socket选项
socket_shutdown()    这个函数允许你关闭读、写、或者指定的socket
socket_strerror()    返回指定错误号的详细错误
socket_write()     写数据到socket缓存
socket_writev()    写数据到分散/聚合数组

(注: 函数介绍删减了部分原文内容,函数详细使用建议参考英文原文,或者参考PHP手册)


以上所有的函数都是PHP中关于socket的,使用这些函数,你必须把你的socket打开,如果你没有打开,请编辑你的php.ini文件,去掉下面这行前面的注释:
extension=php_sockets.dll
如果你无法去掉注释,那么请使用下面的代码来加载扩展库:
<?php
if(!extension_loaded(‘sockets’))
{
if(strtoupper(substr(PHP_OS, 3)) == “WIN”)
{
dl(‘php_sockets.dll’);
}
else
{
dl(‘sockets.so’);
}
}
?>

如果你不知道你的socket是否打开,那么你可以使用phpinfo()函数来确定socket是否打开。你通过查看phpinfo信息了解socket是否打开。如下图:

查看phpinfo()关于socket的信息


◆ 产生一个服务器


现在我们把第一个例子进行完善。你需要监听一个指定的socket并且处理用户的连接。

<?php
$commonProtocol = getprotobyname("tcp");
$socket = socket_create(AF_INET, SOCK_STREAM, $commonProtocol);
socket_bind($socket, 'localhost', 1337);
socket_listen($socket);
// Accept any incoming connections to the server
$connection = socket_accept($socket);
if($connection)
{
socket_write($connection, "You have connected to the socket...\n\r");
}
?>

你应该使用你的命令提示符来运行这个例子。理由是因为这里将产生一个服务器,而不是一个Web页面。如果你尝试使用Web浏览器来运行这个脚本,那么很有可能它会超过30秒的限时。你可以使用下面的代码来设置一个无限的运行时间,但是还是建议使用命令提示符来运行。
set_time_limit(0);
在你的命令提示符中对这个脚本进行简单测试:
Php.exe example01_server.php
如果你没有在系统的环境变量中设置php解释器的路径,那么你将需要给php.exe指定详细的路径。当你运行这个服务器端的时候,你能够通过远程登陆(telnet)的方式连接到端口1337来测试这个服务器。如下图:



上面的服务器端有三个问题:1. 它不能接受多个连接。2. 它只完成唯一的一个命令。3. 你不能通过Web浏览器连接这个服务器。
这个第一个问题比较容易解决,你可以使用一个应用程序去每次都连接到服务器。但是后面的问题是你需要使用一个Web页面去连接这个服务器,这个比较困难。你可以让你的服务器接受连接,然后些数据到客户端(如果它一定要写的话),关闭连接并且等待下一个连接。
在上一个代码的基础上再改进,产生下面的代码来做你的新服务器端:

<?php
// Set up our socket
$commonProtocol = getprotobyname("tcp");
$socket = socket_create(AF_INET, SOCK_STREAM, $commonProtocol);
socket_bind($socket, 'localhost', 1337);
socket_listen($socket);
// Initialize the buffer
$buffer = "NO DATA";
while(true)
{
// Accept any connections coming in on this socket

$connection = socket_accept($socket);
printf("Socket connected\r\n");
// Check to see if there is anything in the buffer
if($buffer != "")
{
  printf("Something is in the buffer...sending data...\r\n");
  socket_write($connection, $buffer . "\r\n");
  printf("Wrote to socket\r\n");
}
else
{
  printf("No Data in the buffer\r\n");
}
// Get the input
while($data = socket_read($connection, 1024, PHP_NORMAL_READ))
{
  $buffer = $data;
  socket_write($connection, "Information Received\r\n");
  printf("Buffer: " . $buffer . "\r\n");
}
socket_close($connection);
printf("Closed the socket\r\n\r\n");
}
?>

这个服务器端要做什么呢?它初始化一个socket并且打开一个缓存收发数据。它等待连接,一旦产生一个连接,它将打印“Socket connected”在服务器端的屏幕上。这个服务器检查缓冲区,如果缓冲区里有数据,它将把数据发送到连接过来的计算机。然后它发送这个数据的接受信息,一旦它接受了信息,就把信息保存到数据里,并且让连接的计算机知道这些信息,最后关闭连接。当连接关闭后,服务器又开始处理下一次连接。(翻译的烂,附上原文)
This is what the server does. It initializes the socket and the buffer that you use to receive
and send data. Then it waits for a connection. Once a connection is created it prints
“Socket connected” to the screen the server is running on. The server then checks to see if
there is anything in the buffer; if there is, it sends the data to the connected computer.
After it sends the data it waits to receive information. Once it receives information it stores
it in the data, lets the connected computer know that it has received the information, and
then closes the connection. After the connection is closed, the server starts the whole
process again.


◆ 产生一个客户端

处理第二个问题是很容易的。你需要产生一个php页连接一个socket,发送一些数据进它的缓存并处理它。然后你又个处理后的数据在还顿,你能够发送你的数据到服务器。在另外一台客户端连接,它将处理那些数据。
To solve the second problem is very easy. You need to create a PHP page that connects to
a socket, receive any data that is in the buffer, and process it. After you have processed the
data in the buffer you can send your data to the server. When another client connects, it
will process the data you sent and the client will send more data back to the server.


下面的例子示范了使用socket:

<?php
// Create the socket and connect
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$connection = socket_connect($socket,’localhost’, 1337);
while($buffer = socket_read($socket, 1024, PHP_NORMAL_READ))
{
if($buffer == “NO DATA”)
{
echo(“<p>NO DATA</p>”);
break;
}
else
{
  // Do something with the data in the buffer
  echo(“<p>Buffer Data: “ . $buffer . “</p>”);
}
}
echo(“<p>Writing to Socket</p>”);
// Write some test data to our socket
if(!socket_write($socket, “SOME DATA\r\n”))
{
echo(“<p>Write failed</p>”);
}
// Read any response from the socket
while($buffer = socket_read($socket, 1024, PHP_NORMAL_READ))
{
echo(“<p>Data sent was: SOME DATA<br> Response was:” . $buffer . “</p>”);
}
echo(“<p>Done Reading from Socket</p>”);
?>

这个例子的代码演示了客户端连接到服务器。客户端读取数据。如果这是第一时间到达这个循环的首次连接,这个服务器将发送“NO DATA”返回给客户端。如果情况发生了,这个客户端在连接之上。客户端发送它的数据到服务器,数据发送给服务器,客户端等待响应。一旦接受到响应,那么它将把响应写到屏幕上。

posted @ 2009-03-02 17:55 小马歌 阅读(144) | 评论 (0)编辑 收藏
 

1、用file_get_contents或者fopen、file、readfile等函数读取url的时候,会创建一个名为$http_response_header的变量来保存http响应的报头,使用fopen等函数打开的数据流信息可以用stream_get_meta_data来获取。
2、php5中新增的参数context使这些函数更加灵活,通过它我们可以定制http请求,甚至post数据。


示例代码1:

  1. <?php
  2. $html = file_get_contents('http://www.example.com/');
  3. print_r($http_response_header);
  4. // or
  5. $fp = fopen('http://www.example.com/', 'r');
  6. print_r(stream_get_meta_data($fp));
  7. fclose($fp);
  8. ?>

示例代码2:

  1. <?php
  2. $data = array ('foo' => 'bar');
  3. $data = http_build_query($data);
  4. $opts = array (
  5.     'http' => array (
  6.         'method' => 'POST',
  7.         'header'=> "Content-type: application/x-www-form-urlencoded\r\n" .
  8.                    "Content-Length: " . strlen($data) . "\r\n",
  9.         'content' => $data
  10.     ),
  11. );
  12. $context = stream_context_create($opts);
  13. $html = file_get_contents('http://www.example.com', false, $context);
  14. echo $html;
  15. ?>

参考:
http://cn.php.net/manual/zh/function.file-get-contents.php
http://cn.php.net/manual/en/function.stream-context-create.php
http://cn.php.net/manual/zh/wrappers.http.php

posted @ 2009-03-02 17:54 小马歌 阅读(101) | 评论 (0)编辑 收藏
 
一、代理服务器概述
 
1.1什么是代理服务器 
在TCP/IP网络中,传统的通信过程是这样的:客户端向服务器请求数据,服务器响应该请求,将数据传送给客户端。在引入了代理服务器以后,这一过程变成了这样:客户端向服务器发起请求,该请求被送到代理服务器;代理服务器分析该请求,先查看自己缓存中是否有请求数据,如果有就直接传送给客户端,如果没有就代替客户端向该服务器发出请求。服务器响应以后,代理服务器将响应的数据传送给客户端,同时在自己的缓存中保留一份该数据的拷贝。这样,再有客户端请求相同的数据时,代理服务器就可以直接将数据传送给客户端,而不需要再向该服务器发起请求。
 
1.2 代理服务器的功能 
一般说来,代理服务器具有以下的功能: 
1.通过缓存增加访问速度 
随着Internet的迅猛发展,网络带宽变得越来越珍贵。所以为了提高访问速度,好多ISP都提供代理服务器,通过代理服务器的缓存功能来加快网络的访问速度。一般说来,大多数的代理服务器都支持HTTP缓存,但是,有的代理服务器也支持FTP缓存。在选择代理服务器时,对于大多数的组织,只需要HTTP缓存功能就足够了。 
通常,缓存有主动缓存被动缓存之分。所谓被动缓存,指的是代理服务器只在客户端请求数据时才将服务器返回的数据进行缓存,如果数据过期了,又有客户端请求相同数据时,代理服务器又必须重新发起新的数据请求,在将响应数据传送给客户端时又进行新的缓存。所谓主动缓存,就是代理服务器不断地检查缓存中的数据,一旦有数据过期,则代理服务器主动发起新的数据请求来更新数据。这样,当有客户端请求该数据时就会大大缩短响应时间。还需要说明的是,对于数据中的认证信息,大多数的代理服务器都不会进行缓存的。 
2.提供用私有IP访问Internet的方法 
IP地址是不可再生的宝贵资源,假如你只有有限的IP地址,但是需要提供整个组织的Internet访问能力,那么,你可以通过使用代理服务器来实现这一点。 
3.提高网络的安全性 
这一点是很明显的,如果内部用户访问Internet都是通过代理服务器,那么,代理服务器就成为进入Internet的唯一通道;反过来说,代理服务器也是Internet访问内部网的唯一通道,如果你没有做反向代理,则对于Internet上的主机来说,你的整个内部网只有代理服务器是可见的,从而大大增强了网络的安全性。 

1.3 代理服务器的分类及特点 
通常的代理服务器分类方法,是从实现的机理分为线路层代理、应用层代理、智能线路层代理等等。在这里,我想从另外一个角度出发,把代理服务器分为传统代理服务器和透明代理服务器。 
我认为有必要好好搞清楚两者的区别,只有真正明白了内在地机理,才能在遇到问题时,有章可循,才不会一头雾水,不知从何解决问题。因此,下面我们就通过具体的实例来说明。本章的写作思路来源于Paul Russell所写的IPCHAINS-HOWTO。下面所举的例子也来源于该文章,我觉得我读该文的最大收获在于对内部网访问外部网以及外部网访问内部网的实现手段有了一个清晰的认识。当然,这里所谓的内部网是指使用私有IP的内部网络。 
我们的例子都基于以下假设: 
你的域名为sample.com,你的内部网(192.168.1.*)用户通过proxy.sample.com(外部接口 eth0:1.2.3.4;内部接口 eth1:192.168.1.1)的代理服务器访问Internet,换句话说,该代理服务器是唯一一台直接与Internet和内部网相连的机器。并假该设代理服务器上运行着某种代理服务器软件(如squid)。假设内部网中某一客户机为client.sample.com(192.168.1.100)。 
+-------------------+ 
|内部网(192.168.1.*)| eth1+--------+eth0 DDN 
| +------------| proxy |<===============>Internet 
|client198.168.1.100| +--------+ 
+-------------------+ 
eth0: 1.2.3.4 
eth1: 198.168.1.1 

1.3.1传统代理 
在以上基础上我们做以下工作: 
1.代理服务软件被绑定到代理服务器的8080端口。 
2.客户端浏览器被配置使用代理服务器的8080端口。 
3.客户端不需要配置DNS。 
4.代理服务器上需要配置代理服务器。 
5.客户端不需要配置缺省路由。 
当我们在客户端浏览器中打开一个web请求,比如“http://www.linuxaid.com.cn”,这时将陆续发生以下事件: 
1.客户端使用某一端口(比如1025)连接代理服务器8080端口,请求web页面“http://www.linuxaid.com.cn” 
2.代理服务器向DNS请求“www.linuxaid.com.cn”,得到相应的IP地址202.99.11.120。然后,代理服务器使用某一端口(比如1037)向该IP地址的80端口发起web连接请求,请求web页面。 
3.收到响应的web页面后,代理服务器把该数据传送给客户端。 
4.客户端浏览器显示该页面。 
从www.linuxaid.com.cn的角度看来,连接是在1.2.3.4地1037端口和202.99.11.120的80端口之间建立的。从client的角度看来,连接是在192.168.1.100的1025端口和1.2.3.4的8080端口之间建立的。
 
1.3.2 透明代理 
透明代理的意思是客户端根本不需要知道有代理服务器的存在。 
在以上基础上我们做以下工作: 
1.配置透明代理服务器软件运行在代理服务器的8080端口。 
2.配置代理服务器将所有对80端口的连接重定向到8080端口。 
3.配置客户端浏览器直接连解到Internet。 
4.在客户端配置好DNS. 
5.配置客户端的缺省网关为192.168.1.1. 
当我们在客户端浏览器中打开一个web请求,比如“http://www.linuxaid.com.cn”,这时将陆续发生以下事件: 
1.客户端向DNS请求“www.linuxaid.com.cn”,得到相应的IP地址202.99.11.120。然后,客户端使用某一端口(比如1066)向该IP地址的80端口发起web连接请求,请求web页面。 
2.当该请求包通过透明代理服务器时,被重定向到代理服务器的绑定端口8080。于是,透明代理服务器用某一端口(比如1088)向202.99.11.120的80端口发起web连接请求,请求web页面。 
3.收到响应的web页面后,代理服务器把该数据传送给客户端。 
4.客户端浏览器显示该页面。 
从www.linuxaid.com.cn的角度看来,连接是在1.2.3.4地1088端口和202.99.11.120的80端口之间建立的。从client的角度看来,连接是在192.168.1.100的1066端口和202.99.11.120的80端口之间建立的。 
以上就是传统代理服务器和透明代理服务器的区别所在。 

二、各种代理服务器的比较 
linux下的代理服务器软件很多,我从www.freshmeat.com(一个著名的linux软件站点)查看了一下,足有六十多个。但是被广泛应用的只有Apache、socks、squid等几个实践证明是高性能的代理软件。下面我们分别来比较一下这几个软件: 

2.1 Apache 
Apache是世界上用的最广泛的HTTP服务器,之所以用的最广泛,是因为它强大的功能、高效率、安全性和速度。从1.1.x版本开始,Apache开始包含了一个代理模块。用Apache作代理服务器的性能优势并不明显,不建议使用。 

2.2 Socks 
Socks是一种网络代理协议,该协议可以让客户机通过Socks服务器获得对Internet的完全访问能力。Scoks在服务器和客户端之间建立一个安全的代理数据通道,从客户的角度看来,Scoks是透明的;从服务器的角度看来,Socks就是客户端。客户端不需要具有对Internet的直接访问能力(也就是说,可以使用私有IP地址),因为Socks服务器能够把来自于客户端的连接请求重定向到Internet。此外,Socks服务器可以对用户连接请求进行认证,允许合法用户建立代理连接。同理,Socks也能防止非授权的Internet用户访问及的内部网络。所以常常把Socks当作防火墙来使用。 
常见的浏览器如netscape、IE等可以直接使用Socks, 并且我们也可以使用socsk5的所带的client来使那些不直接支持socks的internet软件使用Socks。 
更多的资料可以参考Socks官方站点http://www.socks.nec.com。 

2.3 Squid 
对于web用户来说,Squid是一个高性能的代理缓存服务器,Squid支持FTP、gopher和HTTP协议。和一般的代理缓存软件不同,Squid用一个单独的、非模块化的、I/O驱动的进程来处理所有的客户端请求。 
Squid将数据元缓存在内存中,同时也缓存DNS查询的结果,除此之外,它还支持非模块化的DNS查询,对失败的请求进行消极缓存。Squid支持SSL,支持访问控制。由于使用了ICP(轻量Internet缓存协议),Squid能够实现层叠的代理阵列,从而最大限度地节约带宽。 
Squid由一个主要的服务程序squid,一个DNS查询程序dnsserver,几个重写请求和执行认证的程序,以及几个管理工具组成。当Squid启动以后,它可以派生出预先指定数目的dnsserver进程,而每一个dnsserver进程都可以执行单独的DNS查询,这样一来就大大减少了服务器等待DNS查询的时间。 

2.4 选择 
从上面的比较可以看出,Apache主要功能是web服务器,代理功能只不过是其一个模块而已,Socks虽然强大,但有欠灵活,因此我们着重推荐你使用Squid。下面的章节我们就一起来学习Squid激动人心的特性及相关的安装与配置。

三、安装Squid Proxy Server 

3.1获取软件 
你可以通过以下途径获取该软件: 
1.从Squid的官方站点http://www.squid-cache.org下载该软件; 
2.从你的linux发行版本中获取该软件; 
通常,Squid软件包有两种:一种是源代码,下载后需要自己重新编译;可执行文件,下载后只需解压就可以使用;另一种是就是RedHat所使用的rpm包。下面我们分别讲讲这两种软件包的安装方法。 

3.2安装软件 
我们以目前最新的稳定版本squid-2.3.STABLEX为例。 

3.2.1rpm包的安装 
1.进入/mnt/cdrom/RedHat/RPMS 
2.执行rpm -ivh squid-2.2.STABLE4-8.i386.rpm。 
当然,我们也可以在开始安装系统的过程中安装该软件。 

3.2.2 源代码包的安装 
1.从http://www.squid-cache.org下载squid-2.3.STABLE2-src.tar.gz。 
2.将该文件拷贝到/usr/local目录。 
3.解开该文件 tar xvzf squid-2.3.STABLE2-src.tar.gz。 
4.解开后,在/usr/local生成一个新的目录squid-2.3.STABLE2,为了方便用mv命令将 该目录重命名为squid mv squid-2.3.STABLE2 squid; 
5.进入squid cd squid 
6.执行./configure 可以用./confgure --prefix=/directory/you/want指定安装目录 
系统缺省安装目录为/usr/local/squid。 
7.执行 make all 
8.执行 make install 
9.安装结束后,squid的可执行文件在安装目录的bin子目录下,配置文件在etc子目录下。 

四、配置squid基础篇——让代理服务器跑起来 
由于RedHat各方面的优势(包括易用性,稳定性等等),全世界范围内使用该发行版的用户比较多,所以,我们下面的说明都是以RedHat6.1环境下squid-2.2.STABLE4-8版本为主。从我的使用经验看来,该版本的squid要比其他版本稳定的多,以前的1.1.22版本也比较稳定,但是在功能及灵活性方面有所欠缺。 
squid有一个主要的配置文件squid.conf,在RedHat环境下所有squid的配置文件位于/etc/squid子目录下。 

4.1常用的配置选项 
因为缺省的配置文件有问题,所以我们必须首先修改该配置文件的有关内容,以便让squid跑起来。 
下面我们来看一看squid.conf文件的结构以及一些常用的选项: 
squid.conf配置文件的可以分为十三个部分,这十三个部分分别是: 
1.NETWORK OPTIONS (有关的网络选项) 
2.OPTIONS WHICH AFFECT THE NEIGHBOR SELECTION ALGORITHM (作用于邻居选择算 法的有关选项) 
3.OPTIONS WHICH AFFECT THE CACHE SIZE (定义cache大小的有关选项) 
4.LOGFILE PATHNAMES AND CACHE DIRECTORIES (定义日志文件的路径及cache的目录) 
5.OPTIONS FOR EXTERNAL SUPPORT PROGRAMS (外部支持程序选项) 
6.OPTIONS FOR TUNING THE CACHE (调整cache的选项) 
7.TIMEOUTS (超时) 
8.ACCESS CONTROLS (访问控制) 
9.ADMINISTRATIVE PARAMETERS (管理参数) 
10.OPTIONS FOR THE CACHE REGISTRATION SERVICE (cache注册服务选项) 
11.HTTPD-ACCELERATOR OPTIONS (HTTPD加速选项) 
12.MISCELLANEOUS (杂项) 
13.DELAY POOL PARAMETERS (延时池参数) 
虽然squid的配置文件很庞大,但是如果你只是为一个中小型网络提供代理服务,并且只准备使用一台服务器,那么,你只需要修改配置文件中的几个选项。这些几个常用选项分别是: 
1.http_port 
说明:定义squid监听HTTP客户连接请求的端口。缺省是3128,如果使用HTTPD加速模式 则为80。你可以指定多个端口,但是所有指定的端口都必须在一条命令行上。 
2.cache_mem (bytes) 
说明:该选项用于指定squid可以使用的内存的理想值。这部分内存被用来存储以下对象 : 
In-Transit objects (传入的对象) 
Hot Objects (热对象,即用户常访问的对象) 
Negative-Cached objects (消极存储的对象) 
需要注意的是,这并没有指明squid所使用的内存一定不能超过该值,其实,该选项只 定义了squid所使用的内存的一个方面,squid还在其他方面使用内存。所以squid实际 使用的内存可能超过该值。缺省值为8MB。 
3.cache_dir Directory-Name Mbytes Level-1 Level2 
说明:指定squid用来存储对象的交换空间的大小及其目录结构。可以用多个cache_dir命令来定义多个这样的交换空间,并且这些交换空间可以分布不同的磁盘分区。"directory "指明了该交换空间的顶级目录。如果你想用整个磁盘来作为交换空间,那么你可以将该目录作为装载点将整个磁盘mount上去。缺省值为/var/spool/squid。“Mbytes”定义了可用的空间总量。需要注意的是,squid进程必须拥有对该目录的读写权力。“Level-1”是可以在该顶级目录下建立的第一级子目录的数目,缺省值为16。同理,“Level-2”是可以建立的第二级子目录的数目,缺省值为256。为什么要定义这么多子目录呢?这是因为如果子目录太少,则存储在一个子目录下的文件数目将大大增加,这也会导致系统寻找某一个文件的时间大大增加,从而使系统的整体性能急剧降低。所以,为了减少每个目录下的文件数量,我们必须增加所使用的目录的数量。如果仅仅使用一级子目录则顶级目录下的子目录数目太大了,所以我们使用两级子目录结构。 
那么,怎么来确定你的系统所需要的子目录数目呢?我们可以用下面的公式来估算。 
已知量: 
DS = 可用交换空间总量(单位KB)/ 交换空间数目 
OS = 平均每个对象的大小= 20k 
NO = 平均每个二级子目录所存储的对象数目 = 256 
未知量: 
L1 = 一级子目录的数量 
L2 = 二级子目录的数量 
计算公式: 
L1 x L2 = DS / OS / NO 
注意这是个不定方程,可以有多个解。 
4.acl 
说明:定义访问控制列表。 
定义语法为: 
acl aclname acltype string1 ... 
acl aclname acltype "file" ... 
当使用文件时,该文件的格式为每行包含一个条目。 
acltype 可以是 src dst srcdomain dstdomain url_pattern urlpath_pattern time port proto method browser user 中的一种。 
分别说明如下: 
src 指明源地址。可以用以下的方法指定: 
acl aclname src ip-address/netmask ... (客户ip地址) 
acl aclname src addr1-addr2/netmask ... (地址范围) 
dst 指明目标地址。语法为: 
acl aclname dst ip-address/netmask ... (即客户请求的服务器的ip地址) 
srcdomain 指明客户所属的域。语法为: 
acl aclname srcdomain foo.com ... squid将根据客户ip反向查询DNS。 
dstdomain 指明请求服务器所属的域。语法为: 
acl aclname dstdomain foo.com ... 由客户请求的URL决定。 
注意,如果用户使用服务器ip而非完整的域名时,squid将进行反向的DNS解析来确 定其完整域名,如果失败就记录为“none”。 
time 指明访问时间。语法如下: 
acl aclname time [day-abbrevs] [h1:m1-h2:m2][hh:mm-hh:mm] 
day-abbrevs: 
S - Sunday 
M - Monday 
T - Tuesday 
W - Wednesday 
H - Thursday 
F - Friday 
A - Saturday 
h1:m1 必须小于 h2:m2,表达示为[hh:mm-hh:mm]。 
port 指定访问端口。可以指定多个端口,比如: 
acl aclname port 80 70 21 ... 
acl aclname port 0-1024 ... (指定一个端口范围) 
proto 指定使用协议。可以指定多个协议: 
acl aclname proto HTTP FTP ... 
method 指定请求方法。比如: 
acl aclname method GET POST ... 
5.http_access 
说明:根据访问控制列表允许或禁止某一类用户访问。 
如果某个访问没有相符合的项目,则缺省为应用最后一条项目的“非”。比如最后一条为允许,则缺省就是禁止。所以,通常应该把最后的条目设为"deny all" 或 "allow all" 来避免安全性隐患。
4.2 应用实例 
假想情景:某公司用squid作代理服务器,该代理服务器配置为PII450/256M/8.4G,公司所用ip段为1.2.3.0/24,并且想用8080作为代理端口。 
则相应的squid配置选项为: 
1.http_port 
http_port 8080 
2.cache_mem 
思路:由于该服务器只提供代理服务,所以该值可以尽量设得大一些。 
cache_mem 194M 
3.cache_dir Directory-Name Mbytes Level-1 Level2 
思路:硬盘为8.4G的,在安装系统时应该做好规划,为不同的文件系统划分可用空间。在本例中,我们可以这样来划分: 
/cache1 3.5G 
/cache2 3.5G 
/var 400M 
swap 127M 
/ 剩余部分 
并且,在安装时,我们尽量不安装不必要的包。这样在节约空间的同时可以提高系统的安全性和稳定性。下面我们来计算所需的第一级和第二级子目录数。 
已知量: 
DS = 可用交换空间总量(单位KB)/ 交换空间数目=7G/2=3500000KB 
OS = 平均每个对象的大小= 20k 
NO = 平均每个二级子目录所存储的对象数目 = 256 
未知量: 
L1 = 一级子目录的数量 
L2 = 二级子目录的数量 
计算公式: 
L1 x L2 = DS / OS / NO=3500000/20/256=684 
我们取 
L1=16 
L2=43 
所以,我们的cache_dir语句为: 
cache_dir /cache1 3500M 16 43 
cache_dir /cache2 3500M 16 43 
4.acl 
思路:通过src来定义acl. 
acl allow_ip src 1.2.3.4/255.255.255.0 
5.http_access 
http_access allow allow_ip 

4.3启动、停止squid。 
配置并保存好squid.conf后,可以用以下命令启动squid。 
squid 
或者,使用RedHat的启动脚本来启动squid. 
/etc/rc.d/init.d/squid start 
同样地,你也可以用下列脚本停止运行squid或重启动squid. 
/etc/rc.d/init.d/squid stop 
/etc/rc.d/init.d/squid restart 

五、根据需求配置你的squid——进阶篇 

5.1其它配置选项 
在进行squid的一些高级应用之前,我们有必要对其他有用的配置选项作一个全面的了解。下面我们分类来讲一讲这些选项,用于某些特殊应用的选项我们将放在讲该种应用时来讲。 

5.1.1网络选项 
1.tcp_incoming_address 
tcp_outgoing_address 
udp_incoming_address 
udp_outgoing_address 
说明: 
tcp_incoming_address指定监听来自客户或其他squid代理服务器的绑定ip地址; 
tcp_outgoing_address指定向远程服务器或其他squid代理服务器发起连接的ip地址 
udp_incoming_address为ICP套接字指定接收来自其他squid代理服务器的包的ip地址 udp_outgoing_address为ICP套接字指定向其他squid代理服务器发送包的ip地址; 
缺省为没有绑定任何ip地址。该绑定地址可以用ip指定,也可以用完整的域名指定。 

5.1.2交换空间设定选项 
1.cache_swap_low (percent, 0-100) 
cache_swap_high (percent, 0-100) 
说明:squid使用大量的交换空间来存储对象。那么,过了一定的时间以后,该交换空间就会用完,所以还必须定期的按照某种指标来将低于某个水平线的对象清除。squid使用所谓的“最近最少使用算法”(LRU)来做这一工作。当已使用的交换空间达到cache_swap_high时,squid就根据LRU所计算的得到每个对象的值将低于某个水平线的对象清除。这种清除工作一直进行直到已用空间达到cache_swap_low。这两个值用百分比表示,如果你所使用的交换空间很大的话,建议你减少这两个值得差距,因为这时一个百分点就可能是几百兆空间,这势必影响squid的性能。缺省为: 
cache_swap_low 90 
cache_swap_high 95 
2.maximum_object_size 
说明:大于该值得对象将不被存储。如果你想要提高访问速度,就请降低该值;如果你想最大限度地节约带宽,降低成本,请增加该值。单位为K,缺省值为: 
maximum_object_size 4096 KB 

5.1.3有关日志的选项 
1.cache_access_log 
说明:指定客户请求记录日志的完整路径(包括文件的名称及所在的目录),该请求可以是来自一般用户的HTTP请求或来自邻居的ICP请求。缺省值为: 
cache_access_log /var/log/squid/access.log 
如果你不需要该日志,可以用以下语句取消:cache_access_log none 
2.cache_store_log 
说明:指定对象存储记录日志的完整路径(包括文件的名称及所在的目录)。该记录表明哪些对象被写到交换空间,哪些对象被从交换空间清除。缺省路径为: 
cache_log /var/log/squid/cache.log 
如果你不需要该日志,可以用以下语句取消:cache_store_log none 
3.cache_log 
说明:指定squid一般信息日志的完整路径(包括文件的名称及所在的目录)。 
缺省路径为:cache_log /var/log/squid/cache.log 
4.cache_swap_log 
说明:该选项指明每个交换空间的“swap.log”日志的完整路径(包括文件的名称及所在的目录)。该日志文件包含了存储在交换空间里的对象的元数据(metadata)。通常,系统将该文件自动保存在第一个“cache_dir”说定义的顶级目录里,但是你也可以指定其他的路径。如果你定义了多个“cache_dir”,则相应的日志文件可能是这样的: 
cache_swap_log.00 
cache_swap_log.01 
cache_swap_log.02 
后面的数字扩展名与指定的多个“cache_dir”一一对应。 
需要注意的是,最好不要删除这类日志文件,否则squid将不能正常工作。 
5.pid_filename 
说明:指定记录squid进程号的日志的完整路径(包括文件的名称及所在的目录)。缺省路径为 
pid_filename /var/run/squid.pid 
如果你不需要该文件,可以用以下语句取消:pid_filename none 
6.debug_options 
说明:控制作日志时记录信息的多寡。可以从两个方面控制:section控制从几个方面作记录;level控制每个方面的记录的详细程度。推荐的方式(也是缺省方式)是:debug_options ALL,1 
即,对每个方面都作记录,但详细程度为1(最低)。 
7.log_fqdn on|off 
说明:控制在 access.log 中对用户地址的记录方式。打开该选项时,squid记录客户的完整域名,取消该选项时,squid记录客户的ip地址。注意,如果打开该选项会增加系统的负担,因为squid还得进行客户ip的DNS查询。缺省值为:log_fqdn off 

5.1.4有关外部支持程序的选项 
1.ftp_user 
说明:设置登录匿名ftp服务器时的提供的电子邮件地址,登录匿名ftp服务器时要求用你的电子邮件地址作为登录口令(更多的信息请参看本书的相关章节)。需要注意的是,有的匿名ftp服务器对这一点要求很苛刻,有的甚至会检查你的电子邮件的有效性。缺省值为:ftp_user Squid@ 
2.ftp_list_width 
说明:设置ftp列表的宽度,如果设得太小将不能的浏览到长文件名。缺省值为: ftp_list_width 32 
3.cache_dns_program 
说明:指定DNS查询程序的完整路径(包括文件的名称及所在的目录)。缺省路径为: 
cache_dns_program /usr/lib/squid/dnsserver 
4.dns_children 
说明:设置DNS查询程序的进程数。对于大型的登录服务器系统,建议该值至少为10。最大值可以是32,缺省设置为5个。注意,如果你任意的降低该值,可能会使系统性能急剧降低,因为squid主进程要等待域名查询的结果。没有必要减少该值,因为DNS查询进程并不会消耗太多的系统的资源。 
5.dns_nameservers 
说明:指定一个DNS服务器列表,强制squid使用该列表中的DNS服务器而非使用/etc/resolv.conf文件中定义的DNS服务器。你可以这样指定多个DNS服务器:dns_nameservers 10.0.0.1 192.172.0.4 
缺省设置为:dns_nameservers none 
6.unlinkd_program 
说明:指定文件删除进程的完整路径。 
缺省设置为: 
unlinkd_program /usr/lib/squid/unlinkd 
7.pinger_program 
说明:指定ping进程的完整路径。该进程被squid利用来测量与其他邻居的路由距离。该选项只在你启用了该功能时有用。缺省为: 
pinger_program /usr/lib/squid/pinger 
8.authenticate_program 
说明:指定用来进行用户认证的外部程序的完整路径。squid的用户认证功能我们将在后面的章节讲述。缺省设置为不认证。 

5.1.5用户访问控制选项 
1.request_size (KB) 
说明:设置用户请求通讯量的最大允许值(单位为KB)。如果用户用POST方法请求时,应该设一个较大的值。缺省设置为: 
request_size 100 KB 
2.reference_age 
说明:squid根据对象的LRU(最近最少使用算法)来清除对象,squid依据使用磁盘空间的总量动态地计算对象的LRU年龄。我们用reference_age定义对象的最大LRU年龄。如果一个对象在指定的reference_age内没有被访问,squid将删除该对象。缺省值为一个月。你可以使用如下所示的时间表示方法。 
1 week 
3.5 days 
4 months 
2.2 hours 
3.quick_abort_min (KB) 
quick_abort_max (KB) 
quick_abort_pct (percent) 
说明:控制squid是否继续传输被用户中断的请求。当用户中断请求时,squid将检测 
quick_abort 的值。如果剩余部分小于“quick_abort_min”指定的值,squid 将继续完成剩余部分的传输;如果剩余部分大于“quick_abort_max”指定的值,squid 将终止剩余部分的传输;如果已完成“quick_abort_pct”指定的百分比,squid将继续完成剩余部分的传输。缺省的设置为: 
quick_abort_min 16 KB 
quick_abort_max 16 KB 
quick_abort_pct 95 

5.1.6各类超时设置选项 
1.negative_ttl time-units 
说明:设置消极存储对象的生存时间。所谓的消极存储对象,就是诸如“连接失败”及"404 Not Found"等一类错误信息。缺省设置为:negative_ttl 5 minutes 
2.positive_dns_ttl time-units 
说明:设置缓存成功的DNS查询结果的生存时间。缺省为6小时。 
positive_dns_ttl 6 hours 
3.negative_dns_ttl time-units 
说明:设置缓存失败的DNS查询结果的生存时间。缺省为5分钟。 
negative_dns_ttl 5 minutes 
4.connect_timeout time-units 
说明:设置squid等待连接完成的超时值。缺省值为2分钟。 
connect_timeout 120 seconds 
5.read_timeout time-units 
说明:如果在指定的时间内squid尚未从被请求的服务器读入任何数据,则squid将终止该客户请求。缺省值为15分钟。 
read_timeout 15 minutes 
6.request_timeout 
说明:设置在建立与客户的连接后,squid将花多长时间等待客户发出HTTP请求。缺省值为30秒。 
request_timeout 30 seconds 
7.client_lifetime time-units 
说明:设置客户在与squid建立连接后,可以将该连接保持多长时间。 
注意,因为客户建立的每个连接都会消耗一定的系统资源,所以如果你是为一个大型网络提供代理服务的话,一定要正确地修改该值。因为如果同一时间的连接数量太大的话,可能会消耗大量的系统资源,从而导致服务器宕机。缺省值为1天,该值太大了,建议根据你自己的情况适当减小该值。 
client_lifetime 1 day 
8.half_closed_clients on/off 
说明:有时候由于用户的不正常操作,可能会使与squid的TCP连接处于半关闭状态, 
这时候,该TCP连接的发送端已经关闭,而接收端正常工作。缺省地,squid将一直保持这种处于半关闭状态的TCP连接,直到返回套接字的读写错误才将其关闭。如果将该值设为off,则一旦从客户端返回“no more data to read”的信息,squid就立即关闭该连接。half_closed_clients on 
9.pconn_timeout 
说明:设置squid在与其他服务器和代理建立连接后,该连接闲置多长时间后被关闭。缺省值为120秒。 
pconn_timeout 120 seconds 
10.ident_timeout 
说明:设置squid等待用户认证请求的时间。缺省值为10秒。 
ident_timeout 10 seconds 
11.shutdown_lifetime time-units 
说明:当收到SIGTERM 或者 SIGHUP 信号后, squid将进入一种shutdown pending的模式,等待所有活动的套接字关闭。在过了shutdown_lifetime所定义的时间后,所有活动的用户都将收到一个超时信息。缺省值为30秒。 
shutdown_lifetime 30 seconds 

5.1.7管理参数选项 
1.cache_mgr 
说明:设置管理员邮件地址。缺省为: 
cache_mgr root 
2. cache_effective_user 
cache_effective_group 
说明:如果用root启动squid,squid将变成这两条语句指定的用户和用户组。缺省变为squid用户和squid用户组。注意这里指定的用户和用户组必须真是存在于/etc/passwd中。如果用非root帐号启动squid,则squid将保持改用户及用户组运行,这时候,你不能指定小于1024地http_port。 
cache_effective_user squid 
cache_effective_group squid 
3.visible_hostname 
说明:定义在返回给用户的出错信息中的主机名。 
如: visible_hostname www-cache.foo.org 
4.unique_hostname 
说明:如果你有一个代理服务器阵列,并且你为每个代理服务器指定了同样的“visible_hostname”,同时你必须为它们指定不同的“unique_hostname”来避免“forwarding loops ”(传输循环)发生。 

5.1.8其它杂项 
1. dns_testnames 
说明:设置进行DNS查询测试,如果第一个站点解析成功则立即结束DNS查询测试。如果你不愿意进行DNS查询测试,就不要去掉缺省的设置。 
#dns_testnames netscape.com internic.net nlanr.net microsoft.com 
2.logfile_rotate 
说明:通常,squid会定期的将日志文件更名并打包。比如正在使用的日志文件为access.log,squid会将其更名并打包为access.log.1.gz;过了一定时间后,squid又会将 
access.log.1.gz更名为access.log.2.gz并将当前的日志文件更名并打包为access.log.1.gz,以此循环。logfile_rotate所指定的数字即为打包并备份的文件的数量,当达到这一数目时,squid将删除最老的备份文件。缺省值为10。如果你想手动来进行这些操作,你可以用logfile_rotate 0来取消自动操作。 
3.err_html_text 
说明:用该语句定义一个字符串变量,可以用%L在返回给用户的错误信息文件中引用。错误信息文件通常在/etc/squid/errors目录中,这是一些用HTML写成的脚本文件,你可以自己修改它。 
4.deny_info 
说明:你可以定制自定义的拒绝访问信息文件,并且可以和不同的用户列表相关联。当用户被http_access相关规则拒绝时,squid可以向用户显示你自定义的相应的拒绝访问信息文件。语法为: 
Usage: deny_info err_page_name acl 
比如: 
deny_info ERR_CUSTOM_ACCESS_DENIED bad_guys 
5.memory_pools on|off 
说明:如果你将该项设为on,则squid将保留所有已经分配(但是未使用)的内存池以便在将来使用。缺省为on. 
memory_pools on 
6.log_icp_queries on|off 
说明:设置是否对ICP请求作日志。如果你的系统负载很大,你可以用off来取消该功能。缺省为: 
log_icp_queries on 
7.always_direct 
说明:该选项允许你指定某些用户类,squid将这些用户类的请求直接转发给被请求的服务器。语法为: 
always_direct allow|deny [!]aclname ... 
如:直接转发FTP请求可以这样设置: 
acl FTP proto FTP 
always_direct allow FTP 
8.never_direct 
说明:与always_direct相反。语法为: 
Usage: never_direct allow|deny [!]aclname ... 
比如,为了强制除了本地域的其他用户使用代理服务器,你可以这样设置: 
acl local-servers dstdomain foo.net 
acl all src 0.0.0.0/0.0.0.0 
never_direct deny local-servers 
never_direct allow all 
9.icon_directory 
说明:指明向用户传送错误信息时所用到的图标文件的目录。缺省路径为: icon_directory /usr/lib/squid/icons 
10.error_directory 
说明:指明向用户传送错误信息所用到的错误描述文件的目录。缺省路径为: 
error_directory /etc/squid/errors 

5.2 用户认证设置 
缺省的,squid本身不带任何认证程序,但是我们可以通过外部认证程序来实现用户认证。一般说来有以下的认证程序: 
1.LDAP认证:你可以访问以下资源来获取更多的有用信息。 
http://www.geocities.com/ResearchTriangle/Thinktank/5292/projects/ldap/ 
http://home.iae.nl/users/devet/squid/proxy_auth/contrib/ldap_auth.tar.gz 
2.SMB认证:可以实现基于NT和samba的用户认证。更多的信息请访问以下资源。 
http://www.hacom.nl/~richard/software/smb_auth.html 
3.基于mysql的用户认证。 
http://home.iae.nl/users/devet/squid/proxy_auth/contrib/mysql_auth.c 
4.基于sock5密码用户认证。 
http://nucleo.freeservers.com/ 
5.基于Radius 的用户认证。 
http://home.iae.nl/users/devet/squid/proxy_auth/contrib/auth.pl 
但是我们一般常用的是用ncsa实现的认证和用smb_auth实现的基于NT和samba的用户认证。下面我们就来讲这两种认证方法的具体实现。 

5.2.1 ncsa用户认证的实现 
ncsa是squid源代码包自带的认证程序之一,下面我们以squid-2.3.STABLE2版本为例讲述ncsa的安装和配置。 
1.从www.squid-cache.org下载squid源代码包squid-2.3.STABLE2-src.tar.gz并放到/tmp目录下。 
2.用tar解开: 
tar xvzf squid-2.3.STABLE2-src.tar.gz 
%make 
%make install 
3.然后,进入/tmp/squid-2.3.STABLE2/auth_modules/NCSA目录。 
% make 
% make install 
编译成功后,会生成ncsa_auth的可执行文件。 
4.拷贝生成的执行文件ncsa_auth到/usr/bin目录 
cp ncsa_auth /usr/bin/bin 
5.修改squid.conf中的相关选项如下所示: 
authenticate_program /usr/local/squid/bin/ncsa_auth /usr/bin/passwd 
6.定义相关的用户类 
acl auth_user proxy_auth REQUIRED 
注意,REQUIRED关键字指明了接收所有合法用户的访问。 
7.设置http_access 
http_access allow auth_user 
注意,如果你在改行中指定了多个允许访问的用户类的话,应该把要认证的用户类放在第一个。如下所示: 
错误的配置:http_access allow auth_user all manager 
正确的配置:http_access allow auth_user manager all 
8.利用apache携带的工具软件htpasswd在/usr/local/squid/etc下生成密码文件并添加相应的用户信息。一般说来,该密码文件每行包含一个用户的用户信息,即用户名和密码。 
用htpasswd生成密码文件passwd并添加用户bye。 
htpasswd -c /usr/local/squid/etc/passwd bye 
然后重新启动squid,密码认证已经生效。 

5.2.2 smb用户认证的实现 
国内介绍并使用ncsa实现用户认证的文章不多,而使用smb_auth和samba实现基于NT的用户认证我还没有看到过,下面我们就来看一看在squid中实现基于NT的用户认证。 
当前smb_auth的最高版本是smb_auth-0.05,你可以在以下地址下载。当然,squid的源代码包中也包含smb_auth,但是是0.02版的。 
http://www.hacom.nl/~richard/software/smb_auth-0.05.tar.gz 
smb_auth的主页地址是http://www.hacom.nl/~richard/software/smb_auth.html。 
1.系统需求: 
squid2.0以上版本。 
安装samba2.0.4以上版本。你并不需要运行samba服务,因为smb_auth只用到了 samba的客户端软件。 
2.下载smb_auth-0.05.tar.gz并复制到/tmp. 
3.tar xvzf smb_auth-0.05.tar.gz 
4.根据你的要求修改Makefile中的SAMBAPREFIX和INSTALLBIN参数。SAMBAPREFIX指定了你的samba安装路径,INSTALLBIN指明了smb_auth的安装路径。我们指定: 
SAMBAPREFIX=/usr,INSTALLBIN=/usr/bin. 
5.make 
6.make install,成功后会在INSTALLBIN指定路径中生成可执行文件smb_auth. 
7.按下列步骤设置你要用于认证的主域控制器: 
首先在NETLOG共享目录中建立一个“proxy”文件,该文件只包含一个“allow”的字符串,一般说来,该NETLOG目录位于winntsystem32Replimportscripts目录中;然后,设置所有你想让其访问squid的用户和用户组拥有对该文件的读的权力。 
8.修改squid.conf中的相关选项如下所示: 
authenticate_program /usr/local/squid/bin/smb_auth your_domain_name 
9.定义相关的用户类 
acl auth_user proxy_auth REQUIRED 
注意,REQUIRED关键字指明了接收所有合法用户的访问。 
10.设置http_access 
http_access allow auth_user 
注意,如果你在改行中指定了多个允许访问的用户类的话,应该把要认证的用户类放在第一个。如下所示: 
错误的配置:http_access allow auth_user all manager 
正确的配置:http_access allow auth_user manager all 
如果一切正确的话,然后重新启动squid,密码认证已经生效。 
说明:smb_auth的调用方法: 
1.smb_auth -W your_domain_name 
用your_domain_name指定你的域名。smb_auth将进行广播寻找该主域控制器。 
2.smb_auth -W your_domain_name -B 
如果你有多个网络接口,可以用-B 指定用于广播的网络接口的ip地址。 
3.smb_auth -W your_domain_name -U 
也可以用-U直接指定该主域控制器的ip地址。 
4.smb_auth -W your_domain_name -S share 
可以用-S指定一个不同于NETLOG的共享目录。 

5.2.3 squid.conf中关于认证的其他设置 
1.authenticate_children 
说明:设置认证子进程的数目。缺省为5个。如果你处于一个繁忙的网络环境中,你可以适当增大该值。 
2.authenticate_ttl 
说明:设置一次认证的有效期,缺省是3600秒。 
3.proxy_auth_realm 
说明:设置用户登录认证时向用户显示的域名。 

5.3透明代理的设置 
关于透明代理的概念我们已经在第一节将过了,下面我们看一下怎么样在squid中实现透明代理。 
透明代理的实现需要在Linux 2.0.29以上,但是Linux 2.0.30并不支持该功能,好在我们现在使用的通常是2.2.X以上的版本,所以不必担心这个问题。下面我们就用ipchains+squid来实现透明代理。在开始之前需要说明的是,目前我们只能实现支持HTTP的透明代理,但是也不必太担心,因为我们之所以使用代理,目的是利用squid的缓存来提高Web的访问速度,至于提供内部非法ip地址的访问及提高网络安全性,我们可以用ipchains来解决。 
实现环境:RedHat6.x+squid2.2.x+ipchains 

5.3.1 linux的相关配置 
确定你的内核已经配置了以下特性: 
[*] Network firewalls 
[ ] Socket Filtering 
[*] Unix domain sockets 
[*] TCP/IP networking 
[ ] IP: multicasting 
[ ] IP: advanced router 
[ ] IP: kernel level autoconfiguration 
[*] IP: firewalling 
[ ] IP: firewall packet netlink device 
[*] IP: always defragment (required for masquerading) 
[*] IP: transparent proxy support 
如果没有,请你重新编译内核。一般在RedHat6.x以上,系统已经缺省配置了这些特性。 

5.3.2squid的相关配置选项 
设置squid.conf中的相关选项,如下所示: 
http_port 3218 
httpd_accel_host virtual 
httpd_accel_port 80 
httpd_accel_with_proxy on 
httpd_accel_uses_host_header on 
说明: 
1.http_port 3128 
在本例中,我们假设squid的HTTP监听端口为3128,即squid缺省设置值。然后,把所有来自于客户端web请求的包(即目标端口为80)重定向到3128端口。 
2.httpd_accel_host virtual 
httpd_accel_port 80 
这两个选项本来是用来定义squid加速模式的。在这里我们用virtual来指定为虚拟主机模式。80端口为要加速的请求端口。采用这种模式时,squid就取消了缓存及ICP功能,假如你需要这些功能,这必须设置httpd_accel_with_proxy选项。 
3.httpd_accel_with_proxy on 
该选项在透明代理模式下是必须设置成on的。在该模式下,squid既是web请求的加速器,又是缓存代理服务器。 
4.httpd_accel_uses_host_header on 
在透明代理模式下,如果你想让你代理服务器的缓存功能正确工作的话,你必须将该选项设为on。设为on时,squid会把存储的对象加上主机名而不是ip地址作为索引。这一点在你想建立代理服务器阵列时显得尤为重要。 

5.3.3 ipchains的相关配置 
ipchains在这里所起的作用是端口重定向。我们可以使用下列语句实现将目标端口为80端口的TCP包重定向到3128端口。 
#接收所有的回送包 
/sbin/ipchains -A input -j ACCEPT -i lo 
#将目标端口为80端口的TCP包重定向到3128端口 
/sbin/ipchains -A input -p tcp -d 0.0.0.0/0 80 -j REDIRECT 80 
当然在这以前,我们必须用下面的语句打开包转发功能。 
echo 1 > /proc/sys/net/ipv4/ip_forward 

小节 
开始,我们讨论了代理服务器的概念,代理服务器的分类;然后,我们把注意力集中在squid,讲述了如何安装和配置squid;最后我们讲了一些squid配置中的高级话题,即实现用户认证的两种方法,透明代理的实现等。当然,还有一些高级话题本章没有讲到,如代理阵列的实现,加速模式的运用等等。但是,我们不可能把所有东西都讲完讲全,希望读者能举一反三,自己去摸索,去尝试。
posted @ 2009-03-02 17:53 小马歌 阅读(294) | 评论 (0)编辑 收藏
 
摘要 内存管理对于长期运行的程序,例如服务器守护程序,是相当重要的影响;因此,理解PHP是如何分配与释放内存的对于创建这类程序极为重要。本文将重点探讨PHP的内存管理问题。

  一、 内存

  在PHP中,填充一个字符串变量相当简单,这只需要一个语句"<?php $str = 'hello world '; ?>"即可,并且该字符串能够被自由地修改、拷贝和移动。而在C语言中,尽管你能够编写例如"char *str = "hello world ";"这样的一个简单的静态字符串;但是,却不能修改该字符串,因为它生存于程序空间内。为了创建一个可操纵的字符串,你必须分配一个内存块,并且通过一个函数(例如strdup())来复制其内容。

{
 char *str;
 str = strdup("hello world");
 if (!str) {
  fprintf(stderr, "Unable to allocate memory!");
 }
}

  由于后面我们将分析的各种原因,传统型内存管理函数(例如malloc(),free(),strdup(),realloc(),calloc(),等等)几乎都不能直接为PHP源代码所使用。

  二、 释放内存

  在几乎所有的平台上,内存管理都是通过一种请求和释放模式实现的。首先,一个应用程序请求它下面的层(通常指"操作系统"):"我想使用一些内存空间"。如果存在可用的空间,操作系统就会把它提供给该程序并且打上一个标记以便不会再把这部分内存分配给其它程序。
当应用程序使用完这部分内存,它应该被返回到OS;这样以来,它就能够被继续分配给其它程序。如果该程序不返回这部分内存,那么OS无法知道是否这块内存不再使用并进而再分配给另一个进程。如果一个内存块没有释放,并且所有者应用程序丢失了它,那么,我们就说此应用程序"存在漏洞",因为这部分内存无法再为其它程序可用。

  在一个典型的客户端应用程序中,较小的不太经常的内存泄漏有时能够为OS所"容忍",因为在这个进程稍后结束时该泄漏内存会被隐式返回到OS。这并没有什么,因为OS知道它把该内存分配给了哪个程序,并且它能够确信当该程序终止时不再需要该内存。

  而对于长时间运行的服务器守护程序,包括象Apache这样的web服务器和扩展php模块来说,进程往往被设计为相当长时间一直运行。因为OS不能清理内存使用,所以,任何程序的泄漏-无论是多么小-都将导致重复操作并最终耗尽所有的系统资源。

  现在,我们不妨考虑用户空间内的stristr()函数;为了使用大小写不敏感的搜索来查找一个字符串,它实际上创建了两个串的各自的一个小型副本,然后执行一个更传统型的大小写敏感的搜索来查找相对的偏移量。然而,在定位该字符串的偏移量之后,它不再使用这些小写版本的字符串。如果它不释放这些副本,那么,每一个使用stristr()的脚本在每次调用它时都将泄漏一些内存。最后,web服务器进程将拥有所有的系统内存,但却不能够使用它。

  你可以理直气壮地说,理想的解决方案就是编写良好、干净的、一致的代码。这当然不错;但是,在一个象PHP解释器这样的环境中,这种观点仅对了一半。

  三、 错误处理

  为了实现"跳出"对用户空间脚本及其依赖的扩展函数的一个活动请求,需要使用一种方法来完全"跳出"一个活动请求。这是在Zend引擎内实现的:在一个请求的开始设置一个"跳出"地址,然后在任何die()或exit()调用或在遇到任何关键错误(E_ERROR)时执行一个longjmp()以跳转到该"跳出"地址。

  尽管这个"跳出"进程能够简化程序执行的流程,但是,在绝大多数情况下,这会意味着将会跳过资源清除代码部分(例如free()调用)并最终导致出现内存漏洞。现在,让我们来考虑下面这个简化版本的处理函数调用的引擎代码:

void call_function(const char *fname, int fname_len TSRMLS_DC){
 zend_function *fe;
 char *lcase_fname;
 /* PHP函数名是大小写不敏感的,
 *为了简化在函数表中对它们的定位,
 *所有函数名都隐含地翻译为小写的
 */
 lcase_fname = estrndup(fname, fname_len);
 zend_str_tolower(lcase_fname, fname_len);
 if (zend_hash_find(EG(function_table),lcase_fname, fname_len + 1, (void **)&fe) == FAILURE) {
  zend_execute(fe->op_array TSRMLS_CC);
 } else {
  php_error_docref(NULL TSRMLS_CC, E_ERROR,"Call to undefined function: %s()", fname);
 }
 efree(lcase_fname);
}

  当执行到php_error_docref()这一行时,内部错误处理器就会明白该错误级别是critical,并相应地调用longjmp()来中断当前程序流程并离开call_function()函数,甚至根本不会执行到efree(lcase_fname)这一行。你可能想把efree()代码行移动到zend_error()代码行的上面;但是,调用这个call_function()例程的代码行会怎么样呢?fname本身很可能就是一个分配的字符串,并且,在它被错误消息处理使用完之前,你根本不能释放它。

  注意,这个php_error_docref()函数是trigger_error()函数的一个内部等价实现。它的第一个参数是一个将被添加到docref的可选的文档引用。第三个参数可以是任何我们熟悉的E_*家族常量,用于指示错误的严重程度。第四个参数(最后一个)遵循printf()风格的格式化和变量参数列表式样。

  四、 Zend内存管理器

  在上面的"跳出"请求期间解决内存泄漏的方案之一是:使用Zend内存管理(ZendMM)层。引擎的这一部分非常类似于操作系统的内存管理行为-分配内存给调用程序。区别在于,它处于进程空间中非常低的位置而且是"请求感知"的;这样以来,当一个请求结束时,它能够执行与OS在一个进程终止时相同的行为。也就是说,它会隐式地释放所有的为该请求所占用的内存。图1展示了ZendMM与OS以及PHP进程之间的关系。

网管必读深入探讨PHP中的内存管理问题(2)
图1.Zend内存管理器代替系统调用来实现针对每一种请求的内存分配。

  除了提供隐式内存清除功能之外,ZendMM还能够根据php.ini中memory_limit的设置控制每一种内存请求的用法。如果一个脚本试图请求比系统中可用内存更多的内存,或大于它每次应该请求的最大量,那么,ZendMM将自动地发出一个E_ERROR消息并且启动相应的"跳出"进程。这种方法的一个额外优点在于,大多数内存分配调用的返回值并不需要检查,因为如果失败的话将会导致立即跳转到引擎的退出部分。

  把PHP内部代码和OS的实际的内存管理层"钩"在一起的原理并不复杂:所有内部分配的内存都要使用一组特定的可选函数实现。例如,PHP代码不是使用malloc(16)来分配一个16字节内存块而是使用了emalloc(16)。除了实现实际的内存分配任务外,ZendMM还会使用相应的绑定请求类型来标志该内存块;这样以来,当一个请求"跳出"时,ZendMM可以隐式地释放它。

  经常情况下,内存一般都需要被分配比单个请求持续时间更长的一段时间。这种类型的分配(因其在一次请求结束之后仍然存在而被称为"永久性分配"),可以使用传统型内存分配器来实现,因为这些分配并不会添加ZendMM使用的那些额外的相应于每种请求的信息。然而有时,直到运行时刻才会确定是否一个特定的分配需要永久性分配,因此ZendMM导出了一组帮助宏,其行为类似于其它的内存分配函数,但是使用最后一个额外参数来指示是否为永久性分配。

  如果你确实想实现一个永久性分配,那么这个参数应该被设置为1;在这种情况下,请求是通过传统型malloc()分配器家族进行传递的。然而,如果运行时刻逻辑认为这个块不需要永久性分配;那么,这个参数可以被设置为零,并且调用将会被调整到针对每种请求的内存分配器函数。

  例如,pemalloc(buffer_len,1)将映射到malloc(buffer_len),而pemalloc(buffer_len,0)将被使用下列语句映射到emalloc(buffer_len):

#define in Zend/zend_alloc.h:
#define pemalloc(size, persistent) ((persistent)?malloc(size): emalloc(size))

  所有这些在ZendMM中提供的分配器函数都能够从下表中找到其更传统的对应实现。

  表格1展示了ZendMM支持下的每一个分配器函数以及它们的e/pe对应实现:

  表格1.传统型相对于PHP特定的分配器。

分配器函数 e/pe对应实现
void *malloc(size_t count); void *emalloc(size_t count);void *pemalloc(size_t count,char persistent);
void *calloc(size_t count); void *ecalloc(size_t count);void *pecalloc(size_t count,char persistent);
void *realloc(void *ptr,size_t count); void *erealloc(void *ptr,size_t count);
void *perealloc(void *ptr,size_t count,char persistent);
void *strdup(void *ptr); void *estrdup(void *ptr);void *pestrdup(void *ptr,char persistent);
void free(void *ptr); void efree(void *ptr);
void pefree(void *ptr,char persistent);

  你可能会注意到,即使是pefree()函数也要求使用永久性标志。这是因为在调用pefree()时,它实际上并不知道是否ptr是一种永久性分配。针对一个非永久性分配调用free()能够导致双倍的空间释放,而针对一种永久性分配调用efree()有可能会导致一个段错误,因为内存管理器会试图查找并不存在的管理信息。因此,你的代码需要记住它分配的数据结构是否是永久性的。

  除了分配器函数核心部分外,还存在其它一些非常方便的ZendMM特定的函数,例如:

void *estrndup(void *ptr,int len);

  该函数能够分配len+1个字节的内存并且从ptr处复制len个字节到最新分配的块。这个estrndup()函数的行为可以大致描述如下:

void *estrndup(void *ptr, int len)
{
 char *dst = emalloc(len + 1);
 memcpy(dst, ptr, len);
 dst[len] = 0;
 return dst;
}

  在此,被隐式放置在缓冲区最后的NULL字节可以确保任何使用estrndup()实现字符串复制操作的函数都不需要担心会把结果缓冲区传递给一个例如printf()这样的希望以为NULL为结束符的函数。当使用estrndup()来复制非字符串数据时,最后一个字节实质上都浪费了,但其中的利明显大于弊。

void *safe_emalloc(size_t size, size_t count, size_t addtl);
void *safe_pemalloc(size_t size, size_t count,size_t addtl,char persistent);

  这些函数分配的内存空间最终大小是((size*count)+addtl)。你可以会问:"为什么还要提供额外函数呢?为什么不使用一个emalloc/pemalloc呢?"原因很简单:为了安全。尽管有时候可能性相当小,但是,正是这一"可能性相当小"的结果导致宿主平台的内存溢出。这可能会导致分配负数个数的字节空间,或更有甚者,会导致分配一个小于调用程序要求大小的字节空间。而safe_emalloc()能够避免这种类型的陷井-通过检查整数溢出并且在发生这样的溢出时显式地预以结束。

  注意,并不是所有的内存分配例程都有一个相应的p*对等实现。例如,不存在pestrndup(),并且在PHP 5.1版本前也不存在safe_pemalloc()。

  五、 引用计数

  慎重的内存分配与释放对于PHP(它是一种多请求进程)的长期性能有极其重大的影响;但是,这还仅是问题的一半。为了使一个每秒处理上千次点击的服务器高效地运行,每一次请求都需要使用尽可能少的内存并且要尽可能减少不必要的数据复制操作。请考虑下列PHP代码片断:

<?php
$a = 'Hello World';
$b = $a;
unset($a);
?>

  在第一次调用之后,只有一个变量被创建,并且一个12字节的内存块指派给它以便存储字符串"Hello World",还包括一个结尾处的NULL字符。现在,让我们来观察后面的两行:$b被置为与变量$a相同的值,然后变量$a被释放。

  如果PHP因每次变量赋值都要复制变量内容的话,那么,对于上例中要复制的字符串还需要复制额外的12个字节,并且在数据复制期间还要进行另外的处理器加载。这一行为乍看起来有点荒谬,因为当第三行代码出现时,原始变量被释放,从而使得整个数据复制显得完全不必要。其实,我们不妨再远一层考虑,让我们设想当一个10MB大小的文件的内容被装载到两个变量中时会发生什么。这将会占用20MB的空间,此时,10已经足够了。引擎会把那么多的时间和内存浪费在这样一种无用的努力上吗?

  你应该知道,PHP的设计者早已深谙此理。

  记住,在引擎中,变量名和它们的值实际上是两个不同的概念。值本身是一个无名的zval*存储体(在本例中,是一个字符串值),它被通过zend_hash_add()赋给变量$a。如果两个变量名都指向同一个值,会发生什么呢?

{
 zval *helloval;
 MAKE_STD_ZVAL(helloval);
 ZVAL_STRING(helloval, "Hello World", 1);
 zend_hash_add(EG(active_symbol_table), "a", sizeof("a"),&helloval, sizeof(zval*), NULL);
 zend_hash_add(EG(active_symbol_table), "b", sizeof("b"),&helloval, sizeof(zval*), NULL);
}

  此时,你可以实际地观察$a或$b,并且会看到它们都包含字符串"Hello World"。遗憾的是,接下来,你继续执行第三行代码"unset($a);"。此时,unset()并不知道$a变量指向的数据还被另一个变量所使用,因此它只是盲目地释放掉该内存。任何随后的对变量$b的存取都将被分析为已经释放的内存空间并因此导致引擎崩溃。

  这个问题可以借助于zval(它有好几种形式)的第四个成员refcount加以解决。当一个变量被首次创建并赋值时,它的refcount被初始化为1,因为它被假定仅由最初创建它时相应的变量所使用。当你的代码片断开始把helloval赋给$b时,它需要把refcount的值增加为2;这样以来,现在该值被两个变量所引用:

{
 zval *helloval;
 MAKE_STD_ZVAL(helloval);
 ZVAL_STRING(helloval, "Hello World", 1);
 zend_hash_add(EG(active_symbol_table), "a", sizeof("a"),&helloval, sizeof(zval*), NULL);
 ZVAL_ADDREF(helloval);
 zend_hash_add(EG(active_symbol_table), "b", sizeof("b"),&helloval,sizeof(zval*),NULL);
}

  现在,当unset()删除原变量的$a相应的副本时,它就能够从refcount参数中看到,还有另外其他人对该数据感兴趣;因此,它应该只是减少refcount的计数值,然后不再管它。

  六、 写复制(Copy on Write)

  通过refcounting来节约内存的确是不错的主意,但是,当你仅想改变其中一个变量的值时情况会如何呢?为此,请考虑下面的代码片断:

<?php
$a = 1;
$b = $a;
$b += 5;
?>

  通过上面的逻辑流程,你当然知道$a的值仍然等于1,而$b的值最后将是6。并且此时,你还知道,Zend在尽力节省内存-通过使$a和$b都引用相同的zval(见第二行代码)。那么,当执行到第三行并且必须改变$b变量的值时,会发生什么情况呢?

  回答是,Zend要查看refcount的值,并且确保在它的值大于1时对之进行分离。在Zend引擎中,分离是破坏一个引用对的过程,正好与你刚才看到的过程相反:

zval *get_var_and_separate(char *varname, int varname_len TSRMLS_DC)
{
 zval **varval, *varcopy;
 if (zend_hash_find(EG(active_symbol_table),varname, varname_len + 1, (void**)&varval) == FAILURE) {
  /* 变量根本并不存在-失败而导致退出*/
  return NULL;
 }
 if ((*varval)->refcount < 2) {
  /* varname是唯一的实际引用,
  *不需要进行分离
  */
  return *varval;
 }
 /* 否则,再复制一份zval*的值*/
 MAKE_STD_ZVAL(varcopy);
 varcopy = *varval;
 /* 复制任何在zval*内的已分配的结构*/
 zval_copy_ctor(varcopy);
 /*删除旧版本的varname
 *这将减少该过程中varval的refcount的值
 */
 zend_hash_del(EG(active_symbol_table), varname, varname_len + 1);
 /*初始化新创建的值的引用计数,并把它依附到
 * varname变量
 */
 varcopy->refcount = 1;
 varcopy->is_ref = 0;
 zend_hash_add(EG(active_symbol_table), varname, varname_len + 1,&varcopy, sizeof(zval*), NULL);
 /*返回新的zval* */
 return varcopy;
}

  现在,既然引擎有一个仅为变量$b所拥有的zval*(引擎能知道这一点),所以它能够把这个值转换成一个long型值并根据脚本的请求给它增加5。

  七、 写改变(change-on-write)

  引用计数概念的引入还导致了一个新的数据操作可能性,其形式从用户空间脚本管理器看来与"引用"有一定关系。请考虑下列的用户空间代码片断:

<?php
$a = 1;
$b = &$a;
$b += 5;
?>

  在上面的PHP代码中,你能看出$a的值现在为6,尽管它一开始为1并且从未(直接)发生变化。之所以会发生这种情况是因为当引擎开始把$b的值增加5时,它注意到$b是一个对$a的引用并且认为"我可以改变该值而不必分离它,因为我想使所有的引用变量都能看到这一改变"。

  但是,引擎是如何知道的呢?很简单,它只要查看一下zval结构的第四个和最后一个元素(is_ref)即可。这是一个简单的开/关位,它定义了该值是否实际上是一个用户空间风格引用集的一部分。在前面的代码片断中,当执行第一行时,为$a创建的值得到一个refcount为1,还有一个is_ref值为0,因为它仅为一个变量($a)所拥有并且没有其它变量对它产生写引用改变。在第二行,这个值的refcount元素被增加为2,除了这次is_ref元素被置为1之外(因为脚本中包含了一个"&"符号以指示是完全引用)。

  最后,在第三行,引擎再一次取出与变量$b相关的值并且检查是否有必要进行分离。这一次该值没有被分离,因为前面没有包括一个检查。下面是get_var_and_separate()函数中与refcount检查有关的部分代码:

if ((*varval)->is_ref || (*varval)->refcount < 2) {
 /* varname是唯一的实际引用,
 * 或者它是对其它变量的一个完全引用
 *任何一种方式:都没有进行分离
 */
 return *varval;
}

  这一次,尽管refcount为2,却没有实现分离,因为这个值是一个完全引用。引擎能够自由地修改它而不必关心其它变量值的变化。

  八、 分离问题

  尽管已经存在上面讨论到的复制和引用技术,但是还存在一些不能通过is_ref和refcount操作来解决的问题。请考虑下面这个PHP代码块:

<?php
$a = 1;
$b = $a;
$c = &$a;
?>

  在此,你有一个需要与三个不同的变量相关联的值。其中,两个变量是使用了"change-on-write"完全引用方式,而第三个变量处于一种可分离的"copy-on-write"(写复制)上下文中。如果仅使用is_ref和refcount来描述这种关系,有哪些值能够工作呢?

  回答是:没有一个能工作。在这种情况下,这个值必须被复制到两个分离的zval*中,尽管两者都包含完全相同的数据(见图2)。

网管必读深入探讨PHP中的内存管理问题(4)
图2.引用时强制分离

  同样,下列代码块将引起相同的冲突并且强迫该值分离出一个副本(见图3)。

网管必读深入探讨PHP中的内存管理问题(4)
图3.复制时强制分离

<?php
$a = 1;
$b = &$a;
$c = $a;
?>

  注意,在这里的两种情况下,$b都与原始的zval对象相关联,因为在分离发生时引擎无法知道介于到该操作当中的第三个变量的名字。

  九、 总结

  PHP是一种托管语言。从普通用户角度来看,这种仔细地控制资源和内存的方式意味着更为容易地进行原型开发并导致出现更少的冲突。然而,当我们深入"内里"之后,一切的承诺似乎都不复存在,最终还要依赖于真正有责任心的开发者来维持整个运行时刻环境的一致性。

posted @ 2009-03-02 17:52 小马歌 阅读(148) | 评论 (0)编辑 收藏
 
1. 安装postfix
   tar -zxf postfix-2.4.6.tar.gz
   cd postfix-2.4.6
   mv /usr/sbin/sendmail /usr/sbin/sendmail.OFF
   mv /usr/bin/newaliases /usr/bin/newaliases.OFF
   mv /usr/bin/mailq /usr/bin/mailq.OFF
   chmod 755 /usr/sbin/sendmail.OFF /usr/bin/newaliases.OFF /usr/bin/mailq.OFF
   vi /etc/passwd:
           postfix:*:12345:12345:postfix:/no/where:/no/shell
   vi  /etc/group:
           postfix:*:12345:

或者用命令
    groupadd postfix -c 12345
    useradd -u 12345 -g 12345 -c postfix -d/dev/null -s/bin/false postfix
    groupadd -c 54321 postdrop

开始安装
  make
  make install

一路enter直到安装完成

2.安装 vm-pop3d (为了收邮件)
  tar zxvf vm-pop3d-1.1.6.tar.gz
  cd vm-pop3d-1.1.6
  ./configure --prefix=/usr/local/pop3 --enable-pam  --enable-virtual --enable-ip-based-virtual
  make
  make install


vi /etc/xinetd.d/pop3
# default: off
# description: The rsync server is a good addition to an ftp server, as it \
#       allows crc checksumming etc.
service pop3 {      
disable = no      
socket_type     = stream      
wait            = no      
protocol        = tcp      
user            = root      
server          = /usr/local/pop3/sbin/vm-pop3d
}
然后就service xinetd start 启动pop3
3.配置安装完的postfix配置目录在/etc/postfix下,主要配置main.cf,粗体部分是我修改过的,mail.jq.com配hosts
vi /etc/postfix/main.cf
queue_directory = /var/spool/postfix
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
mail_owner = postfix
myhostname = mail.jq.com
inet_interfaces = all
mydestination = $myhostname
unknown_local_recipient_reject_code = 550
mynetworks = 192.168.0.0/16, 127.0.0.0/8, 0.0.0.0/0
alias_maps = hash:/etc/aliases  
alias_database = hash:/etc/aliases
local_recipient_maps = $alias_maps unix:passwd.byname  
debug_peer_level = 2  
debugger_command =        PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin        
xxgdb $daemon_directory/$process_name $process_id & sleep 5  
sendmail_path = /usr/sbin/sendmail  
newaliases_path = /usr/bin/newaliases  
mailq_path = /usr/bin/mailq  
setgid_group = postdrop  
html_directory = no  
manpage_directory = /usr/local/man  
sample_directory = /etc/postfix  
readme_directory = no
启动postfix: postfix start
注意iptables要开放25和110端口,hosts.allow加入vm-pop3d:all
此时应该就可以用foxmail收发邮件了,我测试的时候一直提示输入密码,输入系统密码又不对看日志cat /var/log/maillog
  Dec  3 09:18:08 zhangwenhao1 vm-pop3d[12632]: Couldn't open password file: /etc/virtual/mail.jq.com/passwd
之后
  mkdir -p /etc/virtual/mail.jq.com/ ln -s /etc/shadow /etc/virtual/mail.jq.com/passwd
再次测试,发送接收邮件成功,这里是使用系统的帐户,也可以自己新建虚拟账户,利用apache的htpasswd
/usr/local/apache2/bin/htpasswd -c /etc/virtual/mail.jq.com/passwd jq
输入密码,就可以使用虚拟帐户收发邮件了
posted @ 2009-02-26 14:10 小马歌 阅读(1392) | 评论 (0)编辑 收藏
仅列出标题
共95页: First 上一页 71 72 73 74 75 76 77 78 79 下一页 Last