熊志斌,田興彥
(海南熱帶海洋學院藝術與創(chuàng)意學院,三亞 572022)
亂碼的英文術語是“mojibake”,源于日語,指以非期望的編碼格式對文本解碼而產生的混亂文字[1]。在PHP教學中,學生經(jīng)常被亂碼所困擾,不能有效應對。一些編程論壇和文獻[2-3],雖然羅列了一些亂碼的處理方法,但學生往往是知其然而不知其所以然,生搬硬套,有時能解決問題,有時不能解決問題。本文介紹了字符編碼的基本知識和Web工作原理,HTTP響應報文格式,動態(tài)網(wǎng)頁的內容構成,從PHP程序運行過程出發(fā),沿著HTTP請求響應的數(shù)據(jù)流,分析了在Web服務器和數(shù)據(jù)庫服務器中可能導致亂碼的原因及解決辦法,以供教學參考。
字符(character)是包括文字、數(shù)字、標點符號、圖形符號等人類可以理解的各種符號的總稱。字符集(character set)是包含一定數(shù)量字符的集合。編碼字符集(coded character set)是每個字符對應唯一整數(shù)編碼的字符集,編碼字符集中的字符所對應的整數(shù)稱為碼點(code point)。編碼字符集常常被簡稱為字符集,要結合上下文語境來理解。常見字符集有:ASCII字符集、Unicode字符集、UCS字符集、GB2312字符集、GBK字符集,BIG5字符集、GB18030字符集。從碼點到二進制字節(jié)序列的映射稱為編碼方案,每種字符集都有一種或幾種編碼方案。從字符轉換成碼點,碼點按編碼方案轉換成二進制序列,這個過程稱之為編碼。從二進制序列按編碼方案轉換成碼點,碼點轉換成字符,這個過程稱之為解碼。
最早的字符集編碼是美國計算機專家制定的適用英文系統(tǒng)的ASCII碼(American Standard Code for Infor?mation Interchange,美國信息交換標準代碼)。ASCII碼包括英文大小寫字符、阿拉伯數(shù)字、西文符號等可顯示字符,以及回車、換行、退格等打印控制符。
中文字符集編碼有GB2312字符集、GBK字符集、GB18030字符集、BIG5字符集。字符集GB2312(信息交換用漢字編碼字符集·基本集)收錄6763個漢字,基本滿足漢字的計算機信息處理。GBK字符集是對GB2312字符集的擴展,收錄了GB2312字符集之外的生僻字,港澳臺地區(qū)使用的繁體字,GBK字符集并不是國家頒布的標準,編碼由微軟制定,最早用于Windows系統(tǒng)。GB18030字符集(信息技術中文編碼字符集)是我國最新的中文字符編碼標準,與GB2312字符集完全兼容,與GBK字符集基本兼容,總過收錄了70244個漢字符號,包括簡體漢字,繁體漢字,我國少數(shù)民族的字符。BIG5字符集是我國港澳臺地區(qū)使用的繁體中文字符集,BIG5字符集用兩個字節(jié)表示一個字符。
由于各國的編碼方案可能不兼容,同一個文件在不同國家的信息處理系統(tǒng)會產生亂碼,隨著互聯(lián)網(wǎng)的發(fā)展,這一弊端日益凸顯。為了解決這個問題,非盈利組織機構Unicode聯(lián)盟(Unicode Consortium)制定了包括世界各國文字的Unicode字符集標準,Unicode字符集上有3種編碼方案,分別是UTF-32、UTF-16、UTF-8。UTF-32采用4個字節(jié)表示一個字符,UTF-16采用2個字節(jié)表示一個字符。UTF-8是一種變長編碼,用1到4個字節(jié)表示一個字符,UTF-8用3個字節(jié)表示一個漢字。同一時期,ISO組織制定了一個涵蓋世界各國文字的 UCS(Universal Coded Character Set)字符標準(ISO/IEC 10646)。ISO/IEC 10646標準與Unicode標準定義的字符、字符編碼、字符名稱是完全相同的,目前二者相互協(xié)調發(fā)展[4]。這兩個組織仍獨立地公布自己的標準,Unicode標準已經(jīng)成為事實上的工業(yè)標準。
亂碼的根源就在于沒有按期望的編碼格式進行解碼。計算機中的文件都以二進制字節(jié)序列形式保存在硬盤上,打開文件需要解碼,把二進制序列轉換成自然語言中的字符,如果沒有按文件保存時的編碼格式進行解碼,顯示出來的就是亂碼。例如,文件保存的是UTF-8編碼,打開文件的時候按GBK編碼格式解碼,顯示出來的就是亂碼。
PHP網(wǎng)站運行環(huán)境包括:客戶端瀏覽器、Web服務器和MySQL數(shù)據(jù)庫服務器,其中,Web服務器中包括Apache服務器和PHP預處理器。在Windows中文系統(tǒng)中,瀏覽器默認編碼一般是GBK,也可以通過瀏覽器的設置功能修改默認編碼,MySQL數(shù)據(jù)庫在安裝的時候可以指定默認編碼,如果沒有指定,則服務器默認編碼是latin-1。
Apache+MySQL運行環(huán)境下的Web工作原理如圖1所示:客戶端瀏覽器發(fā)送訪問某個頁面的請求,Web服務器端瀏覽器發(fā)送響應報文,如果是HTML靜態(tài)頁面,Apache服務器返回HTML文件(包含CSS、Java-Script)到客戶端瀏覽器,如果是PHP動態(tài)頁面文件,PHP解釋器執(zhí)行腳本,如果涉及到訪問數(shù)據(jù)庫,查詢結果再通過Web服務器以HTML文件返回到客戶端瀏覽器。
圖1 Web工作原理
瀏覽器和Web服務器之間的數(shù)據(jù)傳輸協(xié)議是HTTP協(xié)議,HTTP協(xié)議規(guī)定了瀏覽器的請求報文格式和Web服務器的響應報文格式。請求報文由請求行、請求頭部和請求正文3部分構成,響應報文由狀態(tài)行、響應頭部和響應正文3部分構成,響應正文也就是頁面文件。其中與亂碼有關的是響應頭部里有一個字段charset,此字段的值是某種編碼格式,如UTF-8,該字段的功能是通知瀏覽器,響應正文(網(wǎng)頁文件)的編碼格式。瀏覽器正是按響應報文頭部charset的值對網(wǎng)頁文件的字節(jié)序列解碼,渲染成網(wǎng)頁。
瀏覽器在接受到響應報文后,按響應頭部的字段charset的編碼格式對響應正文解碼。如果響應報文中字段charset的指定編碼格式與網(wǎng)頁文件的編碼格式不一致,或者字段charset的值為空,而瀏覽器默認的字符編碼與網(wǎng)頁文件的編碼格式不一致,則瀏覽器顯示亂碼。Web服務器沒有正確的指定編碼格式,有3種處理方式。
(1)Web服務器指定編碼
Web服務器響應頭部的charset字段值和網(wǎng)頁文件編碼一致,瀏覽器就能正常顯示網(wǎng)頁。charset字段值由Web服務器中Apache和PHP的配置參數(shù)決定,在PHP的php.ini文件中有默認的配置參數(shù)項:
default_charset="UTF-8"
通過修改此項可以為Web服務器指定其他字符集編碼。
在Apache中的httpd.conf配置文件中并沒有默認配置參數(shù),用戶可以在httpd.conf文件尾部添加:
AddDefaultCharset UTF-8
通過此項為Web服務器指定字符集編碼。php.ini文件和httpd.conf文件有各自的字符編碼配置參數(shù),但只有一個配置參數(shù)在Web服務器生效,php.ini配置參數(shù)的優(yōu)先級高于httpd.conf配置參數(shù)。如果php.ini和httpd.conf同時指定了不同的默認字符集,則php.ini中的默認字符集生效。
在php.ini文件或httpd.conf文件配置了字符集編碼參數(shù)后,Web服務器在發(fā)送網(wǎng)頁文件時,無論是HT?ML文件還是PHP文件,都會把配置文件的默認字符編碼作為響應報文頭部的charset字段值發(fā)送到瀏覽器。
這種解決方法的本質就是使Web服務器的默認字符編碼和網(wǎng)頁文件的編碼保持一致,因此,在開發(fā)PHP程序時,把開發(fā)環(huán)境的編輯器的字符編碼設置成服務器端指定的字符編碼就可以了。
(2)網(wǎng)頁文件指定編碼
如果Web服務器中php.ini和httpd.conf都不指定默認字符集,則響應報文頭部的charset字段值為空,Web程序員可以通過網(wǎng)頁文件中的代碼來控制瀏覽器的解碼,使瀏覽器按正確的字符編碼解析網(wǎng)頁文件。HTML文件和PHP文件有不同的控制方式。
對于HTML文件,程序員必須在每一個文件的頭部添加meta標簽,聲明HTML文件的編碼格式。如HTML文件本身就是GBK編碼,聲明方式如下:
<meta http-equiv="Content-Type"content="text/html;charset=GBK">
瀏覽器接到從Web服務器傳來的HTML文件字節(jié)序列流時,就會按GBK編碼格式進行解碼。
對于PHP文件,程序員必須在每一個文件的起始處添加header函數(shù),假如PHP文件本身是GBK編碼,聲明方式如下:
<?php header("content-type:text/html;charset=GBK")?>
header函數(shù)的作用是把括號里面的信息發(fā)到Web服務器的響應報文中的響應頭部,瀏覽器按響應頭部的字段charset的值來解碼PHP文件。
header函數(shù)指定字符集編碼的優(yōu)先級高于php.ini,也就是說即使Web服務器端的php.ini文件有字符編碼的配置參數(shù),參數(shù)對PHP文件不會生效。但是,由于在HTML文件中不能使用header函數(shù),所以php.ini中字符編碼的配置參數(shù)對HTML文件還是生效的。因此,為了統(tǒng)一處理HTML文件和PHP文件,Web服務器中php.ini文件和httpd.conf文件都不指定默認字符集,這種方式靈活性很大,可以使Web程序員靈活控制每一個網(wǎng)頁文件解碼。
(3).htaccess文件指定編碼
.htaccess文件是Apache服務器支持的基于目錄的配置文件,可以放置在站點任何目錄下,其功能非常廣,包括:用戶自動重定向、自定義錯誤頁面、封禁特定IP地址、拒絕訪問目錄,以及指定字符編碼[5]。.htaccess文件的配置參數(shù)的優(yōu)先級高于Web服務器中的php.ini文件和httpd.conf文件中配置參數(shù)。在.htaccess文件中加入下列參數(shù),可以指定字符編碼:
IndexOptions Charset=utf-8
AddDefaultCharset utf-8
php_value default_charset“utf-8”
其中IndexOptions Charset=utf-8用來設置Apache服務器目錄字符編碼,AddDefaultCharset UTF-8將覆蓋httpd.conf文件中配置參數(shù),php_value default_char?set“utf-8”將覆蓋php.ini配置參數(shù)。將上述參數(shù)的第二行第三行的utf-8改成off,則httpd.conf文件和php.ini文件中字符集的配置參數(shù)失效,Web服務器響應報文的頭部的字段charset的值為空。
寫有上述配置參數(shù)的.htaccess文件放入某個目錄下,Web服務器在發(fā)送該目錄及子目錄下的網(wǎng)頁文件時,報文響應頭部的字段charset的值就是.htaccess文件指定的編碼。這種方式適合于用戶無權修改Web服務器配置文件的虛擬主機網(wǎng)站。
PHP文件是動態(tài)網(wǎng)頁,從字節(jié)序列的角度考察,網(wǎng)頁文件的字節(jié)序列可能由兩部分構成,一部分是網(wǎng)頁文件本身的字節(jié)序列,另一部分字節(jié)序列是讀取數(shù)據(jù)庫中的內容。如果數(shù)據(jù)庫的編碼格式與PHP文件的編碼格式不一致,造成網(wǎng)頁文件這兩部分的字節(jié)序列編碼格式不一致,按網(wǎng)頁文件編碼格式解碼,必然出現(xiàn)亂碼。開發(fā)PHP程序時,有時會出現(xiàn)網(wǎng)頁部分內容正常顯示,而在表格中數(shù)據(jù)庫內容亂碼的情況,就是PHP文件的編碼與數(shù)據(jù)庫的編碼不一致造成的。處理數(shù)據(jù)庫服務器造成的亂碼有3種方法。
(1)創(chuàng)建數(shù)據(jù)庫指定編碼
MySQL數(shù)據(jù)庫服務器支持在創(chuàng)建數(shù)據(jù)庫時指定數(shù)據(jù)庫級的編碼格式,所以無論是圖形化的向導創(chuàng)建數(shù)據(jù)庫,還是通過執(zhí)行腳本都可以指定數(shù)據(jù)庫的字符編碼。為了避免亂碼,創(chuàng)建數(shù)據(jù)庫時,可以根據(jù)PHP文件的編碼格式,指定數(shù)據(jù)庫的編碼,使得數(shù)據(jù)庫和PHP文件的編碼格式保持一致。在創(chuàng)建數(shù)據(jù)庫時,不僅可以指定數(shù)據(jù)庫的字符編碼,甚至可以指定數(shù)據(jù)庫中某個表,或表中的某列采取特定的編碼格式。
(2)程序控制編碼
數(shù)據(jù)庫和PHP文件的編碼格式不一致時,在DSN字符串里添加charset參數(shù),charset參數(shù)的值就是PHP文件的編碼。PDO對象在讀寫數(shù)據(jù)庫時,按指定的字符編碼轉換。在PHP程序中,DSN字符串變量形式:
$dsn="mysql:host=localhost;dbname=students;charset=utf8";
$db=new PDO($dsn,$user,$password);
無論數(shù)據(jù)庫的編碼格式是什么,指定DSN字符串里charset參數(shù)的值是穩(wěn)妥的。有些虛擬主機網(wǎng)站,用戶連建庫的權限都沒有,根本就不可能指定數(shù)據(jù)庫的字符編碼,只能采用這種方式。
(3)修改配置參數(shù)
MySQL數(shù)據(jù)庫服務器有服務器級和數(shù)據(jù)庫級兩個級別的字符集編碼和校驗規(guī)則,其中服務器級的字符集和校驗規(guī)則是不可缺少的,在安裝MySQL服務器時就必須指定字符集編碼和校驗規(guī)則,默認字符集編碼是latin-1。在創(chuàng)建數(shù)據(jù)庫時,如果沒有指定字符集編碼和校驗規(guī)則,則延用服務器的字符集編碼和校驗規(guī)則。
服務器級字符集編碼,可以通過修改配置參數(shù)更改默認的字符集編碼。在MySQL的安裝目錄下,有配置文件my.ini,
#SERVER SECTION
#The default character set that will be used when a new schema or table is
#created and no character set is defined
character-set-server=utf8
通過修改參數(shù)項character-set-server=utf8可以更改服務器的字符集編碼。
某個具體數(shù)據(jù)庫的字符集也可以通過配置文件更改默認的字符集編碼,在安裝目錄下,有個Data文件,找到具體數(shù)據(jù)庫目錄,其中有個db.opt配置文件,有類似參數(shù)信息:
default-character-set=utf8
default-collation=utf8_general_ci
修改此參數(shù)可以更改服務器的字符集編碼。
網(wǎng)頁亂碼的根源就在于瀏覽器沒有按網(wǎng)頁文件的編碼格式解碼,處理PHP亂碼關鍵點就在于:一要考察瀏覽器的編碼格式來自服務器指令還是網(wǎng)頁文件代碼,二要考察PHP文件編碼格式與數(shù)據(jù)庫編碼格式是否一致,找到問題出在哪個環(huán)節(jié),就可以靈活地制定處理策略。在PHP教學中,面對中文亂碼的問題,不能簡單羅列亂碼的處理方法,要從字符編碼,從Web工作原理,從HTTP協(xié)議的響應報文的格式,分析亂碼產生的根源,分析Web應用程序各環(huán)節(jié)可能產生亂碼的地方及對應的處理方法。學生掌握了這些基本原理和基本應對能力后,面對亂碼現(xiàn)象,就能根據(jù)實際應用的需求,靈活地制定處理方案。
[1]Mojibake[EB/OL].https://en.wikipedia.org/wiki/Mojibake.
[2]曹暉.字符集與字符編碼標準[J].西北民族大學學報(自然科學版),2006,27(3):36-42.
[3]張博.基于mysqlphp程序開發(fā)的中文亂碼問題及對策分析[J].電子制作,2013(4):67-67.
[4]龐天丙.AMP環(huán)境下“亂碼”問題的解決[J].電腦知識與技術,2011,07(16):3869-3870.
[5]Apache HTTP Server Tutorial:.htaccess Files[EB/OL].http://httpd.apache.org/docs/current/howto/htaccess.html