2015年4月22日 星期三

淺談網頁前後端 Encoding

這篇基本上想到什麼寫什麼,所以沒有特別設計排版與章節~ Sorry

【前言】
 在開發網頁程式上,最多人遇到的一個問題,應該就是「字元編碼」,就以一個最簡單
的 PHP + MySQL 動態網頁來說,資料庫有文字的編碼,後端語言(PHP)也有編碼,還
有檔案的編碼,最後還有呈現在瀏覽器上的編碼,要搞懂這些東西之間的關聯性,首
要工作就是要先了解何謂文字編碼。



符號解釋】
 底下若看到 0x 開頭,代表後面的數字為 16 進位,如 0x10 代表 16  進位的 10 也
就是十進位的 16 , 其餘都會解釋,沒特別提到的,都是十進位

【淺談文字編碼】
我們知道,電腦中是以 0,1 在以二進位的方式儲存資料,為了方便觀察,我們定義
每 8 個 bit 為 1 個 byte , 並且使用 16 進位來表示( 00~FF ),比如:


在此必須要先有一個概念:電腦中所有資料都是用這種 00 ~ FF的 byte 排列組合而成

文字當然也不例外,我們人類定義,文字 A 的字元碼是 65 ;B 是 66
( 參考 ASCII 可顯示字符 )

因此在如果有一個文字檔案內容是 AB , 則實際儲存在硬碟上的資料就是
0x41 0x42 , 如下圖所示: PS : 65=0x41 , 66=0x42


但問題在於,因為電腦是西方國家發展出來的,這些規範也只有考慮
到 A-Z , a-z 0-9 還有一些常用符號等等,不到 256 個字元,因此當初
電腦設定一個字元只占用 1 byte,這對於其他語言的國家來說,完完
全不夠用,例如中文字光常用字應該就有兩三萬個字。

因此許多國家各自發展出了自己的編碼,大多數以 1~2 bytes 顯示一個
字,如台灣的 Big-5 , 大陸的 GBK 等等。

Big-5 是以 ASCII 為基礎,且為了能相容於 ASCII 的 1 byte 文字,於是
定義當遇到值大於 128 的 byte 值,則代表此為中文字,要連後方的 byte
一起看,也就是以兩個 bytes 代表一個中文字。

比如一個編碼為 Big-5 的文字檔內容為:A好


0x41 = 65 => 字元 A
0xA6 = 166 => 大於 128 , 連同後面一起看

===> A6 6E 就是 Big-5 中,「好」的文字編碼
可以到這裡輸入「好」查看編碼
或者參考維基百科大五碼

然而實際上對電腦來說,他就是三個 bytes :  0x41 , 0xA6 , 0x6E ,
程式透過 Big-5 的方式解讀,就可以得到 「A好」

因此所謂「編碼」,也就是定義要如何去看待、解讀這些 bytes 資料。

反過來說,當一個軟體要儲存文字資料時,也會因為採用編碼的不同
而儲存成不同的 bytes 資料,底下文字內容都是「A好」,分別是
儲存成 Big-5、UTF-8、Unicode 的內容: ( 點圖放大 )



到這邊為止,應該要了解的知識:
  • 實際儲存在電腦上,無論是記憶體還是硬碟,都是 bytes 的資料
  • 軟體儲存文字資料時,也會根據編碼方式,儲存不同的 bytes 資料
  • 文字要如何顯示,是看電腦如何去解讀這些儲存的 bytes資料


【網頁中的編碼】
在製作一個動態網頁的過程中,有底下幾個地方通常會與編碼有關:
  1.  資料庫的儲存文字的編碼
  2.  php 檔案的編碼
  3. 輸出的編碼
  4. 使用者瀏覽器看到的編碼

這邊一個一個來講述。


首先,記得,無論如何電腦在儲存資料時一律都是 bytes 資料,這是絕對的定律

我們以底下這兩個字為例,在 Big-5 和 UTF-8 中的編碼分別為:


(十六進位)
Big-5
UTF-8 
A6 6E
E5 A5 BD
E5 A5
E6 86 9F


第一個來看資料庫的編碼,資料庫儲存文字資料時,也可選擇編碼方式,
而這部分,資料庫會以文字本身作為依據,假設儲存的文字是「」:
  • 選擇編碼為 Big-5 時,會存入 A6 6E
  • 選擇編碼為 UTF-8 時,會存入 E5 A5 BD
這邊直接把儲存資料內容的檔案 開啟來看,可以看到儲存文字的方式
有所不同: ( 點圖放大,同時可參考上表文字編碼 )



因此這邊實際儲存在檔案中的 bytes 就會有所差異。
PS : 不建議自己使用 phpMyAdmin 嘗試,這些工具可能會試著幫你轉換編碼

[ 底下會複雜許多 ]

而在 php 中,要連接資料庫時,大多會使用 mysql_set_charset 之類的函數來
設定與資料庫交換內容時所採用的編碼,這個部分由於 php 在該函數內已經做
過了許多優化以及調整,因此在編碼轉換上不會有什麼問題,除非遇到所謂
的「蓋許功」,否則不太容易出太大的麻煩。
PS:當然,如果不指定編碼會有很大的問題,所以請務必記得設定。

和資料庫交換資料時,真正麻煩的是:
  PHP 原始碼檔案編碼以及 mysql_set_charset 設定之間的問題

舉例,參考程式碼: http://pastie.org/10106828 以及資料庫內容:

我們設定 php 與 MySQL 之間交換內容的編碼為 Big-5 ( Line #5 ),
並將 id=1 的 txt 欄位資料取出,取出後直接再新增回資料庫中。

由於 mysql_set_charset 已經將資料庫讀出來的資料給進行轉換編
碼了,因此讀出來儲存在 $txt 變數上的文字是「好」的 Big-5 編碼,
也就是 A6 6E 。

寫回去資料庫的時候,傳給 php MySQL 函數處理的內容也
A6 6E,而由於 mysql_set_charset 指定為 Big-5,因此
會將 A6 6E 以 Big-5 編碼看待,也就是「好」,再交給資料庫。

這邊,無論資料庫是採用 UTF-8 還是 Big-5,在 php 的 mysql 函數
裡面會自動幫我們處理好,按照該有的格式寫入回庫中。

整體流程與資料交換的圖如下:



===========================================

現在,我們將程式碼最後面的
mysqli_query($db_link,"INSERT INTO test (id,txt) VALUES ( 2 , '$txt' )")
改成
mysqli_query($db_link,"INSERT INTO test (id,txt) VALUES ( 2 , '' )")
而 php程式檔案的文字編碼採用 UTF-8,會是怎樣的狀況呢?

從這個例子我們可以了解:撰寫 php 程式的檔案編碼也是很重要的

===========【小結】===========
  • 資料庫編碼影響到儲存方式以及實際送出的 bytes 資料
  • php 的 MySQL 函數庫會根據設定自動與取出的資料進行編碼的轉換
  • php 程式撰寫時務必指定讀取資料庫時合適的編碼
  • 注意 php 程式檔案本身的編碼



【瀏覽與 HTML 編碼】

在製作網頁時,最重要的還是呈現在使用者端的介面。

簡述一個完整的網頁瀏覽過程:
  1. Client 發送 Request 給 Server (我要看哪個網頁)
  2. Server 將網頁的資料 Response 給 Client,過程當然是以 Bytes 資料傳輸
  3. Client 收到資料後進行分析以及編碼、排版,最後呈現給使用者看
 ( php 的運作時機,是在 2 ,網頁資料傳給 Client 之前先做處理 )

切記,這邊還是以 Bytes 資料傳送給 Client,因此要考慮的東西就是原始 bytes
資料到底長什麼樣子。

舉例來說,底下這個 html 網頁檔:(我們只看綠色的部分)
<html>
 <head>(略)</head>
  <body>
   
  </body>
</html>



假設這個 html 是以 Big-5 編碼,那麼傳給 Client 網頁內容的資料會是
.... A6 6E ....

那如果這個 html 是以 UTF-8 編碼,那麼傳給 Client 的資料就會是
.... E5 A5 BD ....


傳送給 Client 之後,要如何解讀就是瀏覽器的問題了,參考下圖:


瀏覽器以 UTF-8 呈現
Html 檔案以 UTF-8 編碼
瀏覽器以 UTF-8 呈現
Html 檔案以 Big-5 編碼
瀏覽器以 Big-5 呈現
Html 檔案以 UTF-8 編碼
瀏覽器以 Big-5 呈現
Html 檔案以 Big-5 編碼

 以左下角的為例,由於檔案是 UTF-8 編碼,因此「好」是: E5 A5 BD

 所以傳給 Client 的也是 E5 A5 BD;但瀏覽器以 Big-5 去看待所以會解讀成:

     E5 A5      BD   ===>    ?   
    ( BD 超過 128 , 後方應該要再接一個 byte , 但沒有,因此以 ? 呈現 )

所以最後呈現就是如左下角的圖所示



如果這邊再加上 php 和資料庫的連動:http://pastie.org/10114657

以此為例,由於在 mysql_set_charset 設定為 Big-5 , 所以 $txt 的 "好"
是 A6 6E,因此瀏覽器要以 Big-5 編碼顯示才可正常看到「好」這個字。
( 試試看:將 mysql_set_charset 改為 UTF-8 , 瀏覽器採用 Big-5 會看到什麼結果呢 )



最後,有不少網站會告訴大家,這樣可以解決編碼問題:
php 寫上:
 header("Content-Type:text/html; charset=utf-8");
或者在 html 的 裡面加上:
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

但其實不知道這兩個用途在哪時,盲目使用是沒有幫助的,反而更容易混淆。
簡單來說,這兩個方法,功能都相同,就是:告訴瀏覽器我用什麼編碼
前者在 header 階段告知,後者在呈現網頁時告知

因為瀏覽器其實不知道網頁是哪種編碼,因此若 server 有告知這個網頁採用哪
種編碼( header 階段),或者讀取完畢,在分析 head 區塊時讀取到編碼設定,
瀏覽器就可以知道網頁採用何種文字編碼。

但若沒有任何資訊,瀏覽器其實只能猜測 ( 例如從文字中猜測可能的編碼 ),或
者根據瀏覽器程式預設的設定顯示,這種情況下比較容易發生出現所謂亂碼的情況。





沒有留言:

張貼留言