談談Unicode編碼,簡要解釋UCS、UTF、BMP、BOM等名詞
這是一篇程序員寫給程序員的趣味讀物。所謂趣味是指可以比較輕松地了解一些原來不清楚的概念,增進知識,類似于打RPG游戲的升級。整理這篇文章的動機是兩個問題:
問題一:
使用Windows記事本的“另存為”,可以在GBK、Unicode、Unicode big
endian和UTF-8這幾種編碼方式間相互轉換。同樣是txt文件,Windows是怎樣識別編碼方式的呢?
我很早前就發現Unicode、Unicode big
endian和UTF-8編碼的txt文件的開頭會多出幾個字節,分別是FF、FE(Unicode),FE、FF(Unicode big
endian),EF、BB、BF(UTF-8)。但這些標記是基于什么標準呢?
問題二:
最近在網上看到一個ConvertUTF.c,實現了UTF-32、UTF-16和UTF-8這三種編碼方式的相互轉換。對于Unicode(UCS2)、GBK、UTF-8這些編碼方式,我原來就了解。但這個程序讓我有些糊涂,想不起來UTF-16和UCS2有什么關系。
查了查相關資料,總算將這些問題弄清楚了,順帶也了解了一些Unicode的細節。寫成一篇文章,送給有過類似疑問的朋友。本文在寫作時盡量做到通俗易懂,但要求讀者知道什么是字節,什么是十六進制。
0、big endian和little endian
big endian和little
endian是CPU處理多字節數的不同方式。例如“漢”字的Unicode編碼是6C49。那么寫到文件里時,究竟是將6C寫在前面,還是將49寫在前面?如果將6C寫在前面,就是big
endian。還是將49寫在前面,就是little endian。
“endian”這個詞出自《格列佛游記》。小人國的內戰就源于吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開,由此曾發生過六次叛亂,其中一個皇帝送了命,另一個丟了王位。
我們一般將endian翻譯成“字節序”,將big endian和little endian稱作“大尾”和“小尾”。
1、字符編碼、內碼,順帶介紹漢字編碼
字符必須編碼后才能被計算機處理。計算機使用的缺省編碼方式就是計算機的內碼。早期的計算機使用7位的ASCII編碼,為了處理漢字,程序員設計了用于簡體中文的GB2312和用于繁體中文的big5。
GB2312(1980年)一共收錄了7445個字符,包括6763個漢字和682個其它符號。漢字區的內碼范圍高字節從B0-F7,低字節從A1-FE,占用的碼位是72*94=6768。其中有5個空位是D7FA-D7FE。
GB2312支持的漢字太少。1995年的漢字擴展規范GBK1.0收錄了21886個符號,它分為漢字區和圖形符號區。漢字區包括21003個字符。2000年的GB18030是取代GBK1.0的正式國家標準。該標準收錄了27484個漢字,同時還收錄了藏文、蒙文、維吾爾文等主要的少數民族文字。現在的PC平臺必須支持GB18030,對嵌入式產品暫不作要求。所以手機、MP3一般只支持GB2312。
從ASCII、GB2312、GBK到GB18030,這些編碼方法是向下兼容的,即同一個字符在這些方案中總是有相同的編碼,后面的標準支持更多的字符。在這些編碼中,英文和中文可以統一地處理。區分中文編碼的方法是高字節的最高位不為0。按照程序員的稱呼,GB2312、GBK到GB18030都屬于雙字節字符集
(DBCS)。
有的中文Windows的缺省內碼還是GBK,可以通過GB18030升級包升級到GB18030。不過GB18030相對GBK增加的字符,普通人是很難用到的,通常我們還是用GBK指代中文Windows內碼。
這里還有一些細節:
GB2312的原文還是區位碼,從區位碼到內碼,需要在高字節和低字節上分別加上A0。
在DBCS中,GB內碼的存儲格式始終是big endian,即高位在前。
GB2312的兩個字節的最高位都是1。但符合這個條件的碼位只有128*128=16384個。所以GBK和GB18030的低字節最高位都可能不是1。不過這不影響DBCS字符流的解析:在讀取DBCS字符流時,只要遇到高位為1的字節,就可以將下兩個字節作為一個雙字節編碼,而不用管低字節的高位是什么。
2、Unicode、UCS和UTF
前面提到從ASCII、GB2312、GBK到GB18030的編碼方法是向下兼容的。而Unicode只與ASCII兼容(更準確地說,是與ISO-8859-1兼容),與GB碼不兼容。例如“漢”字的Unicode編碼是6C49,而GB碼是BABA。
Unicode也是一種字符編碼方法,不過它是由國際組織設計,可以容納全世界所有語言文字的編碼方案。Unicode的學名是"Universal
Multiple-Octet Coded Character Set",簡稱為UCS。UCS可以看作是"Unicode Character Set"的縮寫。
根據維基百科全書(
http://zh.wikipedia.org/wiki/)的記載:歷史上存在兩個試圖獨立設計Unicode的組織,即國際標準化組織(ISO)和一個軟件制造商的協會(unicode.org)。ISO開發了ISO
10646項目,Unicode協會開發了Unicode項目。
在1991年前后,雙方都認識到世界不需要兩個不兼容的字符集。于是它們開始合并雙方的工作成果,并為創立一個單一編碼表而協同工作。從Unicode2.0開始,Unicode項目采用了與ISO
10646-1相同的字庫和字碼。
目前兩個項目仍都存在,并獨立地公布各自的標準。Unicode協會現在的最新版本是2005年的Unicode
4.1.0。ISO的最新標準是10646-3:2003。
UCS規定了怎么用多個字節表示各種文字。怎樣傳輸這些編碼,是由UTF(UCS
Transformation Format)規范規定的,常見的UTF規范包括UTF-8、UTF-7、UTF-16。
IETF的RFC2781和RFC3629以RFC的一貫風格,清晰、明快又不失嚴謹地描述了UTF-16和UTF-8的編碼方法。我總是記不得IETF是Internet
Engineering Task Force的縮寫。但IETF負責維護的RFC是Internet上一切規范的基礎。
3、UCS-2、UCS-4、BMP
UCS有兩種格式:UCS-2和UCS-4。顧名思義,UCS-2就是用兩個字節編碼,UCS-4就是用4個字節(實際上只用了31位,最高位必須為0)編碼。下面讓我們做一些簡單的數學游戲:
UCS-2有2^16=65536個碼位,UCS-4有2^31=2147483648個碼位。
UCS-4根據最高位為0的最高字節分成2^7=128個group。每個group再根據次高字節分為256個plane。每個plane根據第3個字節分為256行
(rows),每行包含256個cells。當然同一行的cells只是最后一個字節不同,其余都相同。
group 0的plane
0被稱作Basic Multilingual Plane, 即BMP。或者說UCS-4中,高兩個字節為0的碼位被稱作BMP。
將UCS-4的BMP去掉前面的兩個零字節就得到了UCS-2。在UCS-2的兩個字節前加上兩個零字節,就得到了UCS-4的BMP。而目前的UCS-4規范中還沒有任何字符被分配在BMP之外。
4、UTF編碼
UTF-8就是以8位為單元對UCS進行編碼。從UCS-2到UTF-8的編碼方式如下:
UCS-2編碼(16進制) UTF-8 字節流(二進制)
0000 - 007F 0xxxxxxx
0080 - 07FF
110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
例如“漢”字的Unicode編碼是6C49。6C49在0800-FFFF之間,所以肯定要用3字節模板了:1110xxxx 10xxxxxx
10xxxxxx。將6C49寫成二進制是:0110 110001 001001, 用這個比特流依次代替模板中的x,得到:11100110 10110001
10001001,即E6 B1 89。
讀者可以用記事本測試一下我們的編碼是否正確。
UTF-16以16位為單元對UCS進行編碼。對于小于0x10000的UCS碼,UTF-16編碼就等于UCS碼對應的16位無符號整數。對于不小于0x10000的UCS碼,定義了一個算法。不過由于實際使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以認為UTF-16和UCS-2基本相同。但UCS-2只是一個編碼方案,UTF-16卻要用于實際的傳輸,所以就不得不考慮字節序的問題。
5、UTF的字節序和BOM
UTF-8以字節為編碼單元,沒有字節序的問題。UTF-16以兩個字節為編碼單元,在解釋一個UTF-16文本前,首先要弄清楚每個編碼單元的字節序。例如收到一個“奎”的Unicode編碼是594E,“乙”的Unicode編碼是4E59。如果我們收到UTF-16字節流“594E”,那么這是“奎”還是“乙”?
Unicode規范中推薦的標記字節順序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order
Mark。BOM是一個有點小聰明的想法:
在UCS編碼中有一個叫做"ZERO WIDTH NO-BREAK
SPACE"的字符,它的編碼是FEFF。而FFFE在UCS中是不存在的字符,所以不應該出現在實際傳輸中。UCS規范建議我們在傳輸字節流前,先傳輸字符"ZERO
WIDTH NO-BREAK SPACE"。
這樣如果接收者收到FEFF,就表明這個字節流是Big-Endian的;如果收到FFFE,就表明這個字節流是Little-Endian的。因此字符"ZERO
WIDTH NO-BREAK SPACE"又被稱作BOM。
UTF-8不需要BOM來表明字節順序,但可以用BOM來表明編碼方式。字符"ZERO
WIDTH NO-BREAK SPACE"的UTF-8編碼是EF BB BF(讀者可以用我們前面介紹的編碼方法驗證一下)。所以如果接收者收到以EF BB
BF開頭的字節流,就知道這是UTF-8編碼了。
Windows就是使用BOM來標記文本文件的編碼方式的。
6、進一步的參考資料
本文主要參考的資料是 "Short overview of ISO-IEC 10646 and Unicode" (
http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。
我還找了兩篇看上去不錯的資料,不過因為我開始的疑問都找到了答案,所以就沒有看:
"Understanding Unicode A
general introduction to the Unicode Standard" (
http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter04a)
"Character set encoding basics Understanding character set encodings and
legacy encodings" (
http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter03)
我寫過UTF-8、UCS-2、GBK相互轉換的軟件包,包括使用Windows API和不使用Windows
API的版本。以后有時間的話,我會整理一下放到我的個人主頁上(
http://fmddlmyy.home4u.china.com)。
我是想清楚所有問題后才開始寫這篇文章的,原以為一會兒就能寫好。沒想到考慮措辭和查證細節花費了很長時間,竟然從下午1:30寫到9:00。希望有讀者能從中受益。
作者Blog:
http://blog.csdn.net/fmddlmyy/參考資料:
http://blog.csdn.net/fmddlmyy/
先準備好軟件:
Apache官方下載地址:apache_2.0.55-win32-x86-no_ssl.msi,更多版本在這里;
php官方下載地址:php-5.0.5-Win32.zip,更多鏡像下載地址,更多版本下載;
mysql官方下載地址:mysql-4.1.14-win32.zip,更多鏡像下載地址,更多版本下載。
一、安裝Apache,配置成功一個普通網站服務器
運行下載好的“apache_2.0.55-win32-x86-no_ssl.msi”,出現如下界面:
出現Apache HTTP Server 2.0.55的安裝向導界面,點“Next”繼續
確認同意軟件安裝使用許可條例,選擇“I accept the terms in the license agreement”,點“Next”繼續
將Apache安裝到Windows上的使用須知,請閱讀完畢后,按“Next”繼續
設置系統信息,在Network Domain下填入您的域名(比如:goodwaiter.com),在Server Name下填入您的服務器名稱(比如:www.goodwaiter.com,也就是主機名加上域名),在Administrator's Email Address下填入系統管理員的聯系電子郵件地址(比如:yinpeng@xinhuanet.com),上述三條信息僅供參考,其中聯系電子郵件地址會在當系統故障時提供給訪問者,三條信息均可任意填寫,無效的也行。下面有兩個選擇,圖片上選擇的是為系統所有用戶安裝,使用默認的80端口,并作為系統服務自動啟動;另外一個是僅為當前用戶安裝,使用端口8080,手動啟動。一般選擇如圖所示。按“Next”繼續。]
選擇安裝類型,Typical為默認安裝,Custom為用戶自定義安裝,我們這里選擇Custom,有更多可選項。按“Next”繼續
出現選擇安裝選項界面,如圖所示,左鍵點選“Apache HTTP Server 2.0.55”,選擇“This feature, and all subfeatures, will be installed on local hard drive.”,即“此部分,及下屬子部分內容,全部安裝在本地硬盤上”。點選“Change...”,手動指定安裝目錄。
我這里選擇安裝在“D:\”,各位自行選取了,一般建議不要安裝在操作系統所在盤,免得操作系統壞了之后,還原操作把Apache配置文件也清除了。選“OK”繼續。
返回剛才的界面,選“Next”繼續。
確認安裝選項無誤,如果您認為要再檢查一遍,可以點“Back”一步步返回檢查。點“Install”開始按前面設定的安裝選項安裝。
正在安裝界面,請耐心等待,直到出現下面的畫面。
安裝向導成功完成,這時右下角狀態欄應該出現了下面的這個綠色圖標,表示Apache服務已經開始運行,按“Finish”結束Apache的軟件安裝
我們來熟悉一下這個圖標,很方便的,在圖標上左鍵單擊,出現如下界面,有“Start(啟動)”、“Stop(停止)”、“Restart(重啟動)”三個選項,可以很方便的對安裝的Apache服務器進行上述操作。
好了現在我們來測試一下按默認配置運行的網站界面,在IE地址欄打“http://127.0.0.1”,點“轉到”,就可以看到如下頁面,表示Apache服務器已安裝成功。
現在開始配置Apache服務器,使它更好的替我們服務,事實上,如果不配置,你的安裝目錄下的Apache2\htdocs文件夾就是網站的默認根目錄,在里面放入文件就可以了。這里我們還是要配置一下,有什么問題或修改,配置始終是要會的,如圖所示,“開始”、“所有程序”、“Apache HTTP Server 2.0.55”、“Configure Apache Server”、“Edit the Apache httpd conf Configuration file”,點擊打開。
XP的記事本有了些小變化,很實用的一個功能就是可以看到文件內容的行、列位置,按下圖所示,點“查看”,勾選“狀態欄”,界面右下角就多了個標記,“Ln 78, Col 10”就表示“行 78,列 10”,這樣可以迅速的在文件中定位,方便解說。當然,你也可以通過“編輯”,“查找”輸入關鍵字來快速定位。每次配置文件的改變,保存后,必須在 Apache服務器重啟動后生效,可以用前面講的小圖標方便的控制服務器隨時“重啟動”。
現在正式開始配置Apache服務器,“Ln 228”,或者查找關鍵字“DocumentRoot”(也就是網站根目錄),找到如下圖所示地方,然后將""內的地址改成你的網站根目錄,地址格式請照圖上的寫,主要是一般文件地址的“\”在Apache里要改成“/”。
“Ln 253”,同樣,你也可以通過查找“
“Ln321”,DirectoryIndex(目錄索引,也就是在僅指定目錄的情況下,默認顯示的文件名),可以添加很多,系統會根據從左至右的順序來優先顯示,以單個半角空格隔開,比如有些網站的首頁是index.htm,就在光標那里加上“index.htm ”文件名是任意的,不一定非得“index.html”,比如“test.php”等,都可以。
這里有一個選擇配置選項,以前可能要配置,現在好像修正過來了,不用配置了,就是強制所有輸出文件的語言編碼,html文件里有語言標記(,這個就是設定文檔語言為gb2312)的也會強制轉換。如果打開的網頁出現亂碼,請先檢查網頁內有沒有上述 html語言標記,如果沒有,添加上去就能正常顯示了。把“# DefaultLanguage nl”前面的“# ”去掉,把“nl”改成你要強制輸出的語言,中文是“zh-cn”,保存,關閉。
好了,簡單的Apache配置就到此結束了,現在利用先前的小圖標重啟動,所有的配置就生效了,你的網站就成了一個網站服務器,如果你加載了防火墻,請打開80或8080端口,或者允許Apache程序訪問網絡,否則別人不能訪問。如果你有公網IP(一般ADSL或電話撥號上網的都是),就可以邀請所有能上網的朋友訪問使用http://你的IP地址(IP地址查詢可訪問http://www.goodwaiter.com,查詢內容內即是)你的網站了;如果你沒有公網IP,也可以把內網IP地址告訴局域網內的其它用戶,讓他們通過http://你的內網IP地址,訪問你的網站。
二、php的安裝、以module方式,將php與apache結合使你的網站服務器支持php服務器腳本程序
將下載的php安裝文件php-5.0.5-Win32.zip右鍵解壓縮。
指定解壓縮的位置,我的設定在“D:\php”
查看解壓縮后的文件夾內容,找到“php.ini-dist”文件,將其重命名為“php.ini”,打開編輯,找到下面圖中的地方, Ln385,有一個“register_globals = Off”值,這個值是用來打開全局變量的,比如表單送過來的值,如果這個值設為“Off”,就只能用“$_POST['變量名']、$_GET['變量名 ']”等來取得送過來的值,如果設為“On”,就可以直接使用“$變量名”來獲取送過來的值,當然,設為“Off”就比較安全,不會讓人輕易將網頁間傳送的數據截取。這個值是否改成“On”就看自己感覺了,是安全重要還是方便重要?
這里還有一個地方要編輯,功能就是使php能夠直接調用其它模塊,比如訪問mysql,如下圖所示,Ln563,選擇要加載的模塊,去掉前面的 “;”,就表示要加載此模塊了,加載的越多,占用的資源也就多一點,不過也多不到哪去,比如我要用mysql,就要把“;extension= php_mysql.dll”前的“;”去掉。所有的模塊文件都放在php解壓縮目錄的“ext”之下,我這里的截圖是把所有能加載的模塊都加載上去了,前面的“;”沒去掉的,是因為“ext”目錄下默認沒有此模塊,加載會提示找不到文件而出錯。這里只是參考,一般不需要加載這么多,需要的加載上就可以了,編輯好后保存,關閉。
如果上一步加載了其它模塊,就要指明模塊的位置,否則重啟Apache的時候會提示“找不到指定模塊”的錯誤,這里介紹一種最簡單的方法,直接將php安裝路徑、里面的ext路徑指定到windows系統路徑中——在“我的電腦”上右鍵,“屬性”,選擇“高級”標簽,點選“環境變量”,在“系統變量”下找到“Path”變量,選擇,雙擊或點擊“編輯”,將“;D:\php;D:\php\ext”加到原有值的后面,當然,其中的“D:\php” 是我的安裝目錄,你要將它改為自己的php安裝目錄,如下圖所示,全部確定。系統路徑添加好后要重啟電腦才能生效,可以現在重啟,也可以在所有軟件安裝或配置好后重啟。
現在開始將php以module方式與Apache相結合,使php融入Apache,照先前的方法打開Apache的配置文件,Ln 173,找到這里,添加進如圖所示選中的兩行,第一行“LoadModule php5_module D:/php/php5apache2.dll”是指以module方式加載php,第二行“PHPIniDir "D:/php"”是指明php的配置文件php.ini的位置,是當然,其中的“D:/php”要改成你先前選擇的php解壓縮的目錄。
還是Apache的配置文件,Ln 757,加入“AddType application/x-httpd-php .php”、“AddType application/x-httpd-php .html”兩行,你也可以加入更多,實質就是添加可以執行php的文件類型,比如你再加上一行“AddType application/x-httpd-php .htm”,則.htm文件也可以執行php程序了,你甚至還可以添加上一行“AddType application/x-httpd-php .txt”,讓普通的文本文件格式也能運行php程序。
前面所說的目錄默認索引文件也可以改一下,因為現在加了php,有些文件就直接存為.php了,我們也可以把“index.php”設為默認索引文件,優先順序就自己排了,我的是放在第一位。編輯完成,保存,關閉。
現在,php的安裝,與Apache的結合已經全部完成,用屏幕右下角的小圖標重啟Apache,你的Apache服務器就支持了php。
三、mysql的安裝,與php、Apache相結合
打開下載的mysql安裝文件mysql-4.1.14-win32.zip,雙擊解壓縮,運行“setup.exe”,出現如下界面
mysql安裝向導啟動,按“Next”繼續
選擇安裝類型,有“Typical(默認)”、“Complete(完全)”、“Custom(用戶自定義)”三個選項,我們選擇“Custom”,有更多的選項,也方便熟悉安裝過程
在“Developer Components(開發者部分)”上左鍵單擊,選擇“This feature, and all subfeatures, will be installed on local hard drive.”,即“此部分,及下屬子部分內容,全部安裝在本地硬盤上”。在上面的“MySQL Server(mysql服務器)”、“Client Programs(mysql客戶端程序)”、“Documentation(文檔)”也如此操作,以保證安裝所有文件。點選“Change...”,手動指定安裝目錄。
填上安裝目錄,我的是“D:\mysql”,也建議不要放在與操作系統同一分區,這樣可以防止系統備份還原的時候,數據被清空。按“OK”繼續。
返回剛才的界面,按“Next”繼續。
確認一下先前的設置,如果有誤,按“Back”返回重做。按“Install”開始安裝。
正在安裝中,請稍候,直到出現下面的界面
這里是詢問你是否要注冊一個mysql.com的賬號,或是使用已有的賬號登陸mysql.com,一般不需要了,點選“Skip Sign-Up”,按“Next”略過此步驟。
現在軟件安裝完成了,出現上面的界面,這里有一個很好的功能,mysql配置向導,不用向以前一樣,自己手動亂七八糟的配置my.ini了,將 “Configure the Mysql Server now”前面的勾打上,點“Finish”結束軟件的安裝并啟動mysql配置向導。
mysql配置向導啟動界面,按“Next”繼續。
選擇配置方式,“Detailed Configuration(手動精確配置)”、“Standard Configuration(標準配置)”,我們選擇“Detailed Configuration”,方便熟悉配置過程。
選擇服務器類型,“Developer Machine(開發測試類,mysql占用很少資源)”、“Server Machine(服務器類型,mysql占用較多資源)”、“Dedicated MySQL Server Machine(專門的數據庫服務器,mysql占用所有可用資源)”,大家根據自己的類型選擇了,一般選“Server Machine”,不會太少,也不會占滿。
選擇mysql數據庫的大致用途,“Multifunctional Database(通用多功能型,好)”、“Transactional Database Only(服務器類型,專注于事務處理,一般)”、“Non-Transactional Database Only(非事務處理型,較簡單,主要做一些監控、記數用,對MyISAM數據類型的支持僅限于non-transactional),隨自己的用途而選擇了,我這里選擇“Transactional Database Only”,按“Next”繼續。
對InnoDB Tablespace進行配置,就是為InnoDB 數據庫文件選擇一個存儲空間,如果修改了,要記住位置,重裝的時候要選擇一樣的地方,否則可能會造成數據庫損壞,當然,對數據庫做個備份就沒問題了,這里不詳述。我這里沒有修改,使用用默認位置,直接按“Next”繼續
選擇您的網站的一般mysql訪問量,同時連接的數目,“Decision Support(DSS)/OLAP(20個左右)”、“Online Transaction Processing(OLTP)(500個左右)”、“Manual Setting(手動設置,自己輸一個數)”,我這里選“Online Transaction Processing(OLTP)”,自己的服務器,應該夠用了,按“Next”繼續
是否啟用TCP/IP連接,設定端口,如果不啟用,就只能在自己的機器上訪問mysql數據庫了,我這里啟用,把前面的勾打上,Port Number:3306,按“Next”繼續
這個比較重要,就是對mysql默認數據庫語言編碼進行設置,第一個是西文編碼,第二個是多字節的通用utf8編碼,都不是我們通用的編碼,這里選擇第三個,然后在Character Set那里選擇或填入“gbk”,當然也可以用“gb2312”,區別就是gbk的字庫容量大,包括了gb2312的所有漢字,并且加上了繁體字、和其它亂七八糟的字——使用mysql的時候,在執行數據操作命令之前運行一次“SET NAMES GBK;”(運行一次就行了,GBK可以替換為其它值,視這里的設置而定),就可以正常的使用漢字(或其它文字)了,否則不能正常顯示漢字。按 “Next”繼續。
選擇是否將mysql安裝為windows服務,還可以指定Service Name(服務標識名稱),是否將mysql的bin目錄加入到Windows PATH(加入后,就可以直接使用bin下的文件,而不用指出目錄名,比如連接,“mysql.exe -uusername -ppassword;”就可以了,不用指出mysql.exe的完整地址,很方便),我這里全部打上了勾,Service Name不變。按“Next”繼續。
這一步詢問是否要修改默認root用戶(超級管理)的密碼(默認為空),“New root password”如果要修改,就在此填入新密碼(如果是重裝,并且之前已經設置了密碼,在這里更改密碼可能會出錯,請留空,并將“Modify Security Settings”前面的勾去掉,安裝配置完成后另行修改密碼),“Confirm(再輸一遍)”內再填一次,防止輸錯。“Enable root access from remote machines(是否允許root用戶在其它的機器上登陸,如果要安全,就不要勾上,如果要方便,就勾上它)”。最后“Create An Anonymous Account(新建一個匿名用戶,匿名用戶可以連接數據庫,不能操作數據,包括查詢)”,一般就不用勾了,設置完畢,按“Next”繼續。
確認設置無誤,如果有誤,按“Back”返回檢查。按“Execute”使設置生效。
設置完畢,按“Finish”結束mysql的安裝與配置——這里有一個比較常見的錯誤,就是不能“Start service”,一般出現在以前有安裝mysql的服務器上,解決的辦法,先保證以前安裝的mysql服務器徹底卸載掉了;不行的話,檢查是否按上面一步所說,之前的密碼是否有修改,照上面的操作;如果依然不行,將mysql安裝目錄下的data文件夾備份,然后刪除,在安裝完成后,將安裝生成的 data文件夾刪除,備份的data文件夾移回來,再重啟mysql服務就可以了,這種情況下,可能需要將數據庫檢查一下,然后修復一次,防止數據出錯。
與Apache及php相結合,前面已提過,這里再說一下,在php安裝目錄下,找到先前重命名并編輯過的 php.ini,如下圖所示,Ln563,把“;extension=php_mysql.dll”前的“;”去掉,加載mysql模塊。保存,關閉后,重啟apache就可以了。這里也可以選擇其它要加載的模塊,去掉前面的“;”,就表示要加載此模塊了,加載的越多,占用的資源也就多一點,不過也多不到哪去。所有的模塊文件都放在php解壓縮目錄的“ext”之下,我這里的截圖是把所有能加載的模塊都加載上去了,前面的“;”沒去掉的,是因為“ext” 目錄下默認沒有此模塊,加載會提示找不到文件而出錯。這里只是參考,一般不需要加載這么多,需要的加載上就可以了,編輯好后保存,關閉。
同樣,加載了模塊后,就要指明模塊的位置,否則重啟Apache的時候會提示“找不到指定模塊”的錯誤,這里介紹一種最簡單的方法,直接將 php安裝路徑、里面的ext路徑指定到windows系統路徑中——在“我的電腦”上右鍵,“屬性”,選擇“高級”標簽,點選“環境變量”,在“系統變量”下找到“Path”變量,選擇,雙擊或點擊“編輯”,將“;D:\php;D:\php\ext”加到原有值的后面,當然,其中的“D:\php”是我的安裝目錄,你要將它改為自己的php安裝目錄,如下圖所示,全部確定。系統路徑添加好后要重啟電腦才能生效,可以現在重啟,也可以在所有軟件安裝或配置好后重啟。
轉:g++參數說明
被轉載作者email(pianopan@beeship.com ).
[介紹]
gcc and g++分別是gnu的c & c++編譯器
gcc/g++在執行編譯工作的時候,總共需要4步
1.預處理,生成.i的文件
2.將預處理后的文件不轉換成匯編語言,生成文件.s
3.有匯編變為目標代碼(機器代碼)生成.o的文件
4.連接目標代碼,生成可執行程序
[參數詳解]
-x language filename
設定文件所使用的語言,使后綴名無效,對以后的多個有效.也就是根
據約定C語言的后綴名稱是.c的,而C++的后綴名是.C或者.cpp,如果
你很個性,決定你的C代碼文件的后綴名是.pig 哈哈,那你就要用這
個參數,這個參數對他后面的文件名都起作用,除非到了下一個參數
的使用。
可以使用的參數嗎有下面的這些
`c’, `objective-c’, `c-header’, `c++’, `cpp-output’,
`assembler’, and `assembler-with-cpp’.
看到英文,應該可以理解的。
例子用法:
gcc -x c hello.pig
-x none filename
關掉上一個選項,也就是讓gcc根據文件名后綴,自動識別文件類型
例子用法:
gcc -x c hello.pig -x none hello2.c
-c
只激活預處理,編譯,和匯編,也就是他只把程序做成obj文件
例子用法:
gcc -c hello.c
他將生成.o的obj文件
-S
只激活預處理和編譯,就是指把文件編譯成為匯編代碼。
例子用法
gcc -S hello.c
他將生成.s的匯編代碼,你可以用文本編輯器察看
-E
只激活預處理,這個不生成文件,你需要把它重定向到一個輸出文件里
面.
例子用法:
gcc -E hello.c > pianoapan.txt
gcc -E hello.c | more
慢慢看吧,一個hello word 也要與處理成800行的代碼
-o
制定目標名稱,缺省的時候,gcc 編譯出來的文件是a.out,很難聽,如果
你和我有同感,改掉它,哈哈
例子用法
gcc -o hello.exe hello.c (哦,windows用習慣了)
gcc -o hello.asm -S hello.c
-pipe
使用管道代替編譯中臨時文件,在使用非gnu匯編工具的時候,可能有些問
題
gcc -pipe -o hello.exe hello.c
-ansi
關閉gnu c中與ansi c不兼容的特性,激活ansi c的專有特性(包括禁止一
些asm inline typeof關鍵字,以及UNIX,vax等預處理宏,
-fno-asm
此選項實現ansi選項的功能的一部分,它禁止將asm,inline和typeof用作
關鍵字。
-fno-strict-prototype
只對g++起作用,使用這個選項,g++將對不帶參數的函數,都認為是沒有顯式
的對參數的個數和類型說明,而不是沒有參數.
而gcc無論是否使用這個參數,都將對沒有帶參數的函數,認為城沒有顯式說
明的類型
-fthis-is-varialble
就是向傳統c++看齊,可以使用this當一般變量使用.
-fcond-mismatch
允許條件表達式的第二和第三參數類型不匹配,表達式的值將為void類型
-funsigned-char
-fno-signed-char
-fsigned-char
-fno-unsigned-char
這四個參數是對char類型進行設置,決定將char類型設置成unsigned char(前
兩個參數)或者 signed char(后兩個參數)
-include file
包含某個代碼,簡單來說,就是便以某個文件,需要另一個文件的時候,就可以
用它設定,功能就相當于在代碼中使用#include<filename>
例子用法:
gcc hello.c -include /root/pianopan.h
-imacros file
將file文件的宏,擴展到gcc/g++的輸入文件,宏定義本身并不出現在輸入文件
中
-Dmacro
相當于C語言中的#define macro
-Dmacro=defn
相當于C語言中的#define macro=defn
-Umacro
相當于C語言中的#undef macro
-undef
取消對任何非標準宏的定義
-Idir
在你是用#include"file"的時候,gcc/g++會先在當前目錄查找你所制定的頭
文件,如果沒有找到,他回到缺省的頭文件目錄找,如果使用-I制定了目錄,他
回先在你所制定的目錄查找,然后再按常規的順序去找.
對于#include<file>,gcc/g++會到-I制定的目錄查找,查找不到,然后將到系
統的缺省的頭文件目錄查找
-I-
就是取消前一個參數的功能,所以一般在-Idir之后使用
-idirafter dir
在-I的目錄里面查找失敗,講到這個目錄里面查找.
-iprefix prefix
-iwithprefix dir
一般一起使用,當-I的目錄查找失敗,會到prefix+dir下查找
-nostdinc
使編譯器不再系統缺省的頭文件目錄里面找頭文件,一般和-I聯合使用,明確
限定頭文件的位置
-nostdin C++
規定不在g++指定的標準路經中搜索,但仍在其他路徑中搜索,.此選項在創建
libg++庫使用
-C
在預處理的時候,不刪除注釋信息,一般和-E使用,有時候分析程序,用這個很
方便的
-M
生成文件關聯的信息。包含目標文件所依賴的所有源代碼
你可以用gcc -M hello.c來測試一下,很簡單。
-MM
和上面的那個一樣,但是它將忽略由#include<file>造成的依賴關系。
-MD
和-M相同,但是輸出將導入到.d的文件里面
-MMD
和-MM相同,但是輸出將導入到.d的文件里面
-Wa,option
此選項傳遞option給匯編程序;如果option中間有逗號,就將option分成多個選
項,然后傳遞給會匯編程序
-Wl.option
此選項傳遞option給連接程序;如果option中間有逗號,就將option分成多個選
項,然后傳遞給會連接程序.
-llibrary
制定編譯的時候使用的庫
例子用法
gcc -lcurses hello.c
使用ncurses庫編譯程序
-Ldir
制定編譯的時候,搜索庫的路徑。比如你自己的庫,可以用它制定目錄,不然
編譯器將只在標準庫的目錄找。這個dir就是目錄的名稱。
-O0
-O1
-O2
-O3
編譯器的優化選項的4個級別,-O0表示沒有優化,-O1為缺省值,-O3優化級別最
高
-g
只是編譯器,在編譯的時候,產生條是信息。
-gstabs
此選項以stabs格式聲稱調試信息,但是不包括gdb調試信息.
-gstabs+
此選項以stabs格式聲稱調試信息,并且包含僅供gdb使用的額外調試信息.
-ggdb
此選項將盡可能的生成gdb的可以使用的調試信息.
[參考資料]
-Linux/UNIX高級編程
中科紅旗軟件技術有限公司編著.清華大學出版社出版
fedora core 5
g++
服務器端程序:
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 8848
#define BACKLOG 5
#define MAXDATASIZE 1000
void process_cli(int connectfd, sockaddr_in client);
void* start_routine(void* arg);
struct ARG {
int connfd;
sockaddr_in client;
};
main()
{
int listenfd, connectfd;
pthread_t thread; //id of thread
ARG *arg;
struct sockaddr_in server; //server's address info
struct sockaddr_in client; //client's
int sin_size;
//create tcp socket
printf("socket.... ");
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("creating socket failed.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
printf("bind.... ");
if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1) {
perror("bind error.");
exit(1);
}
printf("listen.... ");
if(listen(listenfd,BACKLOG) == -1) {
perror("listen() error ");
exit(1);
}
sin_size = sizeof(struct sockaddr_in);
while(1)
{
//accept() using main thread
printf("accepting.... ");
if((connectfd = accept(listenfd,
(struct sockaddr *)&client,
(socklen_t*)&sin_size)) == -1) {
perror("accept() error ");
exit(1);
}
arg = new ARG;
arg->connfd = connectfd;
memcpy((void *)&arg->client, &client, sizeof(client));
//invoke start_routine to handle this thread
printf("thread_creating....");
if(pthread_create(&thread, NULL, start_routine, (void*)arg)){
perror("pthread_creat() error");
exit(1);
}
}
close(listenfd);
}
void process_cli(int connectfd, sockaddr_in client)
{
int num;
char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];
printf("you got a connection from %s. ",inet_ntoa(client.sin_addr) );
//get client's name from client
num = recv(connectfd, cli_name, MAXDATASIZE, 0);
if(num == 0) {
close(connectfd);
printf("Client disconnected. ");
return;
}
cli_name[num - 1] = '
客戶端代碼:
/* cthread.c */
#include <stdio.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#define PORT 8848
#define MAXDATASIZE 100
void process(FILE *fp, int sockfd);
char* getMessage(char* sendline, int len, FILE* fp);
int main(int argc, char *argv[])
{
int fd;
struct hostent *he;
struct sockaddr_in server; //server's address info
if(argc != 2) {
printf("Usage: %s <ip address> ",argv[0]);
exit(1);
}
if((he = gethostbyname(argv[1])) == NULL){
perror("gethostbyname() error");
exit(1);
}
if((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket() error");
exit(1);
}
bzero(&server , sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = *((struct in_addr *)he->h_addr);
if(connect(fd, (struct sockaddr *)&server,
sizeof(struct sockaddr)) == -1){
perror("connect() error");
exit(1);
}
process(stdin,fd);
close(fd);
}
void process(FILE *fp, int sockfd)
{
char sendline[MAXDATASIZE],recvline[MAXDATASIZE];
int numbytes;
printf("connected to server. ");
//send name to server
printf("Input name:");
if(fgets(sendline, MAXDATASIZE, fp) == NULL){
printf(" Exit. ");
return;
}
send(sockfd, sendline, strlen(sendline), 0);
//send message to server
//when the string is not NULL , send another!
while(getMessage(sendline,MAXDATASIZE,fp) != NULL) {
send(sockfd, sendline, strlen(sendline), 0);
if((numbytes = recv(sockfd, recvline, MAXDATASIZE, 0)) == 0){
printf("server terminated. ");
return;
}
recvline[numbytes] = '
編譯命令:
g++ -g -o s sthread.c -pthread
g++ -g -o c cthread.c -lc -lnsl
啟動服務器:
./s
啟動客戶端:
./c 127.0.0.1
然后提示輸入客戶機的名字,再就提示輸入一串字符,然后服務器就反轉再發回來。
系統:fedora core 5
服務器端程序:
/* server */
#include <stdio.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 8848
#define BACKLOG 1
main()
{
int listenfd,connectfd;
struct sockaddr_in server;
struct sockaddr_in client;
int sin_size;
if (( listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Creating socket failed.");
return 0;
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
printf("binding... ");
if(bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
perror("Bind error");
return 0;
}
printf("listen... ");
if(listen(listenfd,BACKLOG) == -1){
perror("listen() error ");
return 0;
}
printf("accept.... ");
sin_size = sizeof(struct sockaddr_in);
if ((connectfd = accept(listenfd, (struct sockaddr *)&client,(socklen_t *) &sin_size)) == -1) {
perror("accept() error ");
return 0;
}
printf("you got a connection from %s ",inet_ntoa(client.sin_addr) );
send(connectfd,"welcome to my server. ",22,0);
close(connectfd);
close(listenfd);
}
客戶端程序:
/* client */
#include <stdio.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
#define PORT 8848
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
int fd, numbytes;
char buf[MAXDATASIZE];
struct hostent *he;
int errno;
struct sockaddr_in server;
if(argc != 2) {
printf("usage: %s <ip address> ",argv[0]);
return 0;
}
printf("gethostbynem.... ");
if ((he = gethostbyname(argv[1]))==NULL){
printf("gethostbyname() error ");
return 0;
}
if((fd=socket(AF_INET,SOCK_STREAM, 0)) == -1){
printf("socket() error ");
return 0;
}
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = *((in_addr*)he->h_addr);
printf("connecting... ");
if(connect(fd, (struct sockaddr *)&server,sizeof(struct sockaddr)) == -1){
// printf("connect() error ");
perror("error");
return 0;
}
if(( numbytes = recv(fd,buf,MAXDATASIZE,0)) == -1){
printf("recv() error ");
return 0;
}
buf[numbytes] = '
操作:
[root@localhost tcpsocket]# ls
help tcpsocketC.c tcpsocketS.c
[root@localhost tcpsocket]# g++ -g -o myserver tcpsocketS.c -lc -lnsl
[root@localhost tcpsocket]# g++ -g -o myclient tcpsocketC.c -lc -lnsl
[root@localhost tcpsocket]# ls
help myclient myserver tcpsocketC.c tcpsocketS.c
[root@localhost tcpsocket]# ./myserver
binding...
listen...
accept....
[root@localhost tcpsocket]# netstat -a | grep 8848
tcp 0 0 *:8848 *:* LISTEN
[root@localhost tcpsocket]# ifconfig
eth0 Link encap:Ethernet HWaddr 00:13:D4:3E:9F:89
inet addr:125.221.160.241 Bcast:125.221.160.255 Mask:255.255.255.0
inet6 addr: fe80::213:d4ff:fe3e:9f89/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:39108 errors:0 dropped:0 overruns:0 frame:0
TX packets:46 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2359238 (2.2 MiB) TX bytes:4040 (3.9 KiB)
Interrupt:17 Base address:0xa000
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:2005 errors:0 dropped:0 overruns:0 frame:0
TX packets:2005 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:2078171 (1.9 MiB) TX bytes:2078171 (1.9 MiB)
[root@localhost tcpsocket]# ./myclient 127.0.0.1