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