一、我們可以且應該優化什么? 
            硬件
            
            操作系統/軟件庫
            
            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 小馬歌 閱讀(135) | 評論 (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 小馬歌 閱讀(831) | 評論 (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 小馬歌 閱讀(5938) | 評論 (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 小馬歌 閱讀(474) | 評論 (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 小馬歌 閱讀(207) | 評論 (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 小馬歌 閱讀(145) | 評論 (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 小馬歌 閱讀(103) | 評論 (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 小馬歌 閱讀(298) | 評論 (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 小馬歌 閱讀(151) | 評論 (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 小馬歌 閱讀(1395) | 評論 (0)編輯 收藏
          僅列出標題
          共95頁: First 上一頁 71 72 73 74 75 76 77 78 79 下一頁 Last 
           
          主站蜘蛛池模板: 句容市| 桐柏县| 墨脱县| 凭祥市| 南汇区| 衡阳县| 疏附县| 体育| 晋州市| 河池市| 天门市| 锦州市| 辽阳县| 义乌市| 漳浦县| 凤阳县| 巢湖市| 双辽市| 屏东县| 大庆市| 永安市| 大石桥市| 庆阳市| 额敏县| 化州市| 望奎县| 桑植县| 互助| 岳阳市| 保定市| 襄樊市| 民乐县| 陈巴尔虎旗| 屏山县| 海城市| 来安县| 蓝山县| 阆中市| 南岸区| 雷波县| 利川市|