6.5 客戶機(jī)程序4—在運(yùn)行時(shí)獲取連接參數(shù)
現(xiàn)在我們有了容易修改的防止出現(xiàn)錯(cuò)誤的連接代碼,我們要了解一些如何做某些比使用NULL 連接參數(shù)更靈巧的事情,如在運(yùn)行時(shí)允許用戶指定一些值??蛻魴C(jī)程序3由于固定連接參數(shù)方面的缺陷,要想更改那些值中的任何一個(gè),都必須編輯源文件并重新編譯。這十分不方便,特別是想使程序用于其他人時(shí)。在運(yùn)行時(shí)指定連接參數(shù)的一個(gè)通用的方法是使用命令行選項(xiàng)。MySQL 分發(fā)包中的程序接受兩種形式的連接參數(shù),如表6 - 1所示。

與標(biāo)準(zhǔn)的MySQL 客戶機(jī)程序一致,客戶機(jī)程序?qū)⒔邮芡瑯拥母袷?。這很容易,那是因?yàn)榭蛻魴C(jī)庫包括了實(shí)現(xiàn)選項(xiàng)分析的函數(shù)。
除此之外,客戶機(jī)程序具有從選項(xiàng)文件中抽取信息的能力。這允許將連接參數(shù)放在-/. m y. c n f(也就是主目錄中的. m y.cnf 文件)中,以便不用在命令行中指定它們。客戶機(jī)庫使檢查MySQL 選項(xiàng)文件和從它們中抽取任何相關(guān)的值變得非常容易。只在程序中增加幾行代碼,就可以使選項(xiàng)文件識(shí)別它,并且通過編寫自己的代碼而不必重新改造這個(gè)框架來進(jìn)行操作。附錄E “MySQL 程序參考”中說明了選項(xiàng)文件的語法。
6.5.1 訪問選項(xiàng)文件內(nèi)容
使用load_default() 函數(shù)為連接參數(shù)值讀取選項(xiàng)文件, load_default() 尋找選項(xiàng)文件、分析任何感興趣的可選組的內(nèi)容,以及重新編寫程序的參數(shù)向量( a rgv[] 數(shù)組),以便把來自于那些組的信息以命令行選項(xiàng)的形式放置在argv[] 的開頭。這就是說,在命令行指定出現(xiàn)的選項(xiàng)。因此,當(dāng)分析命令選項(xiàng)時(shí),就得到了作為常規(guī)選項(xiàng)分析循環(huán)部分的連接參數(shù)。選項(xiàng)加到argv[] 的開頭而不是加到末尾,所以,如果連接參數(shù)真的在命令行指定,它們要比load_defaults() 增加的任何選項(xiàng)晚一些出現(xiàn)(因而忽略)。面的小程序show _ argv 顯示了如何使用load _ defaults ( ),并舉例說明了對(duì)參數(shù)向量如何做出這樣的修改:

該處理選項(xiàng)文件的代碼包括:
■ groups[] 是一個(gè)字符串?dāng)?shù)組,表示所感興趣的選項(xiàng)文件組。對(duì)于客戶機(jī)程序,始終至少指定“client” ([client] 組)。數(shù)組的最后一個(gè)元素必須是NULL。
■ my_init() 是load_defaults() 所需的執(zhí)行一些設(shè)置操作的初始化例程。
■ load_defaults() 有四個(gè)參數(shù):選項(xiàng)文件的前綴(這里應(yīng)該始終是“ m y”),列出感興趣的可選組的數(shù)組、程序參數(shù)的數(shù)目和向量的地址。不傳數(shù)目和向量的值,而是傳地址,因?yàn)閘oad_defaults() 需要改變它們的值。特別注意的是,雖然a rgv 是一個(gè)指針,但還是要傳& argv ,它是指針的地址。
show _ argv打印參數(shù)兩次,第一次是在命令行指定它們的時(shí)候,第二次是在load _ defaults( )修改它們的時(shí)候。為了查看load_defaults() 的運(yùn)行效果,應(yīng)確信在主目錄中有一個(gè)具有[client] 組指定設(shè)置的. my.cnf 文件。假設(shè). my.cnf 文件如下:

有可能會(huì)從不在命令行或~ /.my.cnf 文件中的s h o w _ a rgv 所產(chǎn)生的輸出結(jié)果中看到一些選項(xiàng)。如果是這樣,它們或許是在系統(tǒng)范圍的選項(xiàng)文件中指定的。在主目錄中讀取. m y.cnf 之前,load_defaults() 實(shí)際上是在MySQL 數(shù)據(jù)目錄中尋找/ e t c / m y.cnf 和m y.cnf 文件(在Windows中, load_defaults() 在Windows 系統(tǒng)目錄中尋找文件C : my. c n f、C : \ m y s q l \ d a t a \ m y.cnf 和m y.ini )。
使用load_defaults() 的客戶機(jī)程序幾乎始終是在選項(xiàng)組列表中指定“ c l i e n t”(以便從選項(xiàng)文件中獲取任何通用的客戶機(jī)設(shè)置),但是也可以為請(qǐng)求自己的程序請(qǐng)求特定值。可將下列代碼:

修改為:

然后將[ show _ argv] 組加到~ / . my.cnf 文件中:

有了這些改變,再次調(diào)用show _ argv 就得到了一個(gè)不同的結(jié)果,如下所示:

參數(shù)數(shù)組中選項(xiàng)值出現(xiàn)的順序取決于它們?cè)谶x項(xiàng)文件中列出的順序,而不是選項(xiàng)組在group[] 數(shù)組中列出的順序。這意味著將可能在選項(xiàng)文件的[client] 組之后指定程序?qū)S械慕M。即如果在兩個(gè)組中都指定了一個(gè)選項(xiàng),程序?qū)S械闹祵⒂懈叩膬?yōu)先權(quán)。在這個(gè)例子中可以看到: 在組[client] 和[ show _ argv] 中都指定了host 選項(xiàng),但是因?yàn)榻M[ show _ argv] 在選項(xiàng)文件的最后出現(xiàn),所以host 值將在參數(shù)向量中出現(xiàn)并取得優(yōu)先權(quán)。
load_defaults() 不是從環(huán)境設(shè)置中提取值, 如果想使用環(huán)境變量的值, 例如MYSQL _ TCP _ PORT 或者M(jìn)YSQL _ UNIX _ PORT ,就必須使用getenv() 來自己管理。我不想把這個(gè)管理能力增加到客戶機(jī)中,但這里有一個(gè)例子,介紹了如何檢查幾個(gè)標(biāo)準(zhǔn)的與M y S Q L
有關(guān)的環(huán)境變量值:

在標(biāo)準(zhǔn)MySQL 客戶機(jī)中,環(huán)境變量值的優(yōu)先權(quán)比在選項(xiàng)文件或命令行指定值的優(yōu)先權(quán)要低。如果檢查環(huán)境變量的值,并要與約定保持一致,那么就要在調(diào)用load_default() 或者處理命令行選項(xiàng)之前(不是之后)檢查環(huán)境。
6.5.2 分析命令行參數(shù)
現(xiàn)在我們可以把所有的連接參數(shù)都放入?yún)?shù)向量,但需要一個(gè)分析該向量的方法。getopt_long() 函數(shù)就是為此目的設(shè)計(jì)的。getopt_long() 設(shè)在MySQL 客戶機(jī)庫的內(nèi)部,因此,無論什么時(shí)候與庫連接都可以訪問它。源文件中要包含getopt.h 頭文件,可以把這個(gè)頭文件從MySQL 源分發(fā)包的include 目錄拷貝到正在開發(fā)的客戶機(jī)程序所在的目錄中。
load_defaults() 與安全
因?yàn)橛行┏绦颍ㄈ鏿 s)可以顯示任何過程的參數(shù)列表, load_defaults() 將口令的文本放在參數(shù)列表中,所以您可能對(duì)它處理窺探的含意表示驚異。這沒有問題,因?yàn)閜s 顯示原始的a rgv[] 內(nèi)容,由load_defaults() 創(chuàng)建的任何口令參數(shù)都指向?yàn)樗约悍峙涞膮^(qū)域,這個(gè)區(qū)域并不是原始區(qū)域的一部分,所以ps 看不見它。另一方面,除非故意清除,否則在命令行指定的口令會(huì)在ps 中出現(xiàn)。6 . 5 . 2節(jié)“分析命令行參數(shù)”介紹了如何去做。下面的程序show_param 使用load_defaults() 讀取選項(xiàng)文件,然后調(diào)用getopt_long() 來分
析參數(shù)向量。show_param 舉例說明了通過執(zhí)行以下操作參數(shù)處理的每個(gè)階段發(fā)生了什么:
1) 建立主機(jī)名稱、用戶名稱和口令的缺省值。
2) 打印原始連接參數(shù)和參數(shù)向量值。
3) 調(diào)用load_defaults() 重新編寫參數(shù)向量,反映選項(xiàng)文件內(nèi)容,然后打印結(jié)果向量。
4) 調(diào)用getopt_long() 處理參數(shù)向量,然后打印結(jié)果參數(shù)值和參數(shù)向量中的剩余部分。
show_param 允許使用各種指定的連接參數(shù)的方法進(jìn)行試驗(yàn)(無論是在選項(xiàng)文件中還是在命令行中),并通過顯示使用什么值進(jìn)行連接來查看結(jié)果。當(dāng)實(shí)際上我們把參數(shù)處理代碼與連接函數(shù)do_connect() 連到一起時(shí),show_param 對(duì)于預(yù)知下一個(gè)客戶機(jī)程序?qū)⒁l(fā)生什么是很有用的。
以下是show_param.c 的代碼:




為了處理參數(shù)向量, show _ argv() 使用getopt_long() ,它在循環(huán)中調(diào)用:

getopt_long() 的前兩個(gè)參數(shù)是程序的計(jì)數(shù)參數(shù)和向量參數(shù),第三個(gè)參數(shù)列出了要識(shí)別的選項(xiàng)字符。這些是程序選項(xiàng)的短名稱形式。選項(xiàng)字符后可以有冒號(hào)、雙冒號(hào)或者無冒號(hào),表示選項(xiàng)值必須跟在選項(xiàng)后面、可以跟在選項(xiàng)后面或者不能跟在選項(xiàng)后面。第四個(gè)參數(shù)long_options 是一個(gè)指向可選結(jié)構(gòu)數(shù)組的指針,每個(gè)可選結(jié)構(gòu)為程序需要支持的選項(xiàng)指定信息。它的目標(biāo)與第三個(gè)參數(shù)的可選字符串相類似。每個(gè)long_options[] 結(jié)構(gòu)有四個(gè)元素,其描述如下:
■ 選項(xiàng)的長(zhǎng)名稱。
■ 選項(xiàng)值。這個(gè)值可以是required _ argument、optional _ argument 或者no _ argument,表明選項(xiàng)值是必須跟在選項(xiàng)后面、可以跟在選項(xiàng)后面,還是不能跟在選項(xiàng)后面(它們與第三個(gè)參數(shù)選項(xiàng)字符串中的冒號(hào)、雙冒號(hào)或無冒號(hào)的作用相同)。
■ 標(biāo)記參數(shù)??捎盟鎯?chǔ)變量指針。如果找到這個(gè)選項(xiàng), getopt_long() 則把第四個(gè)參數(shù)指定的值存儲(chǔ)到變量中去。如果標(biāo)記是NULL,getopt_long() 就把optarg 變量指向下一個(gè)選項(xiàng)的任何值,并返回選項(xiàng)的短名稱。long_options[] 數(shù)組為所有的選項(xiàng)指定了NULL。那就是說,如果遇到getopt _ long( ),就返回每個(gè)參數(shù),以便我們可以在switch語句中來處理它。
■ 選項(xiàng)的短(單個(gè)字符)名稱。在long_options[] 數(shù)組中指定的短名稱必須與作為第三個(gè)參數(shù)傳遞給getopt_long() 的選項(xiàng)字符串所使用的字母相匹配,否則程序?qū)⒉荒苷_處理命令行參數(shù)。long_options[] 數(shù)組必須由一個(gè)所有元素都設(shè)為0 的結(jié)構(gòu)所終止。getopt_long() 的第五個(gè)參數(shù)是一個(gè)指向int 變量的指針。getopt_long() 把與最后遇到的選項(xiàng)相符合的long_options[] 結(jié)構(gòu)索引存儲(chǔ)到變量中( show_param 不用這個(gè)值做任何事情)。
請(qǐng)注意,口令選項(xiàng)(指定為--password 或者-p )可以獲得一個(gè)選項(xiàng)值,那就是說,如果使用長(zhǎng)選項(xiàng)形式可指定為--password 或者--password = your_pass,如果使用短選項(xiàng)形式則指定為-p 或者- p your _ pass??蛇x字符串中“ p” 后面的雙冒號(hào)和long_options[] 數(shù)組中的optional _ argument 表示了口令值的可選特性。特別是, MySQL 客戶機(jī)允許在命令行省略口令值,然后提示輸入。這樣避免了在命令行給出口令,它防止其他人通過偷竊看到口令。在寫下一個(gè)客戶機(jī)程序(客戶機(jī)程序4)時(shí),將把口令檢查性能添加進(jìn)去。下面是show_param 的調(diào)用示例和結(jié)果輸出(假設(shè)~ /.my.cnf 一直與show _ argv 示例有相同的內(nèi)容):

輸出結(jié)果說明從命令行得到主機(jī)名(忽略選項(xiàng)文件中的這個(gè)值),從選項(xiàng)文件中得到用戶名和口令。getopt_long() 正確分析了選項(xiàng)是在短選項(xiàng)形式( -h host_name )中指定還是在長(zhǎng)選項(xiàng)形式( --user = paul ,--password = secret )中指定。
現(xiàn)在讓我們?nèi)サ艏兇庹f明選項(xiàng)處理例程是如何工作的這一部分,把剩余部分作為根據(jù)選項(xiàng)文件或命令行提供的任何選項(xiàng)而連接到服務(wù)器的客戶機(jī)的基礎(chǔ)。源文件client4.c 的代碼如下:




與前面開發(fā)的客戶機(jī)程序1、客戶機(jī)程序2和客戶機(jī)程序3比較一下,客戶機(jī)程序4具有一些以前沒有的內(nèi)容:
■ 允許在命令行指定數(shù)據(jù)庫名稱,它緊跟在由getopt_long() 分析的選項(xiàng)的后面。這與MySQL 分發(fā)包中標(biāo)準(zhǔn)客戶機(jī)的行為是一致的。
■ 對(duì)口令值做了備份之后,刪除參數(shù)向量中的任何口令值。這使時(shí)間窗口最小化,在時(shí)間窗口中命令行所指定的口令對(duì)于ps 或其他系統(tǒng)狀態(tài)程序是可見的(窗口縮到最小,但并沒有刪除。命令行指定的口令仍然不太安全)。
■ 如果給出沒有值的口令選項(xiàng),則客戶機(jī)程序提示用戶用get_tty_password() 輸入口令。在客戶機(jī)庫中,這是一個(gè)實(shí)用程序,它提示輸入口令而不在顯示器上回應(yīng)(客戶機(jī)庫充滿了這樣吸引人的東西。因?yàn)檎业搅讼嚓P(guān)的例程和使用它們的方法,所以有助于從MySQL 客戶機(jī)程序的源文件中的讀?。?。您可能會(huì)問:“為什么不只調(diào)用getpass( )呢?”回答是,并不是所有的系統(tǒng)都有這個(gè)函數(shù),如Windows。get_tty_password() 可以在系統(tǒng)間移植,因?yàn)樗慌渲脼檫m應(yīng)各種不同系統(tǒng)。
客戶機(jī)程序4按照指定的選項(xiàng)來響應(yīng)。假設(shè)沒有使事件復(fù)雜化的選項(xiàng)文件。如果無參數(shù)調(diào)用客戶機(jī)程序4,則連接到localhost,并把UNIX 注冊(cè)名和無口令傳遞到服務(wù)器中。相反,如果像介紹的那樣調(diào)用客戶機(jī)程序4,則提示輸入口令(沒有直接以-p 開頭的口令值),連接到some _ host,并將用戶名some_user 和鍵入的口令都傳遞到服務(wù)器:

客戶機(jī)程序4也把數(shù)據(jù)庫名some_db 傳遞給do _ connect( ),成為當(dāng)前數(shù)據(jù)庫。如果沒有選項(xiàng)文件,則處理它的內(nèi)容并用來改變參數(shù)連接。
早期,我們?cè)鵁嶂杂诜庋b代碼,創(chuàng)建包裝函數(shù),目的是斷開與服務(wù)器的連接和從服務(wù)器的連接斷開。詢問是否把分析選項(xiàng)部分放置到包裝函數(shù)中也是合理的。我想這是可能的,但并不想去做。選項(xiàng)分析代碼與連接代碼在程序間并不一致:程序經(jīng)常支持除了標(biāo)準(zhǔn)選項(xiàng)之外
的其他選項(xiàng),不同的程序很可能支持其他選項(xiàng)的不同設(shè)置。這就使選項(xiàng)處理循環(huán)標(biāo)準(zhǔn)化的函數(shù)很難編寫。而且,與連接的建立不同,在它的執(zhí)行過程中程序可以希望進(jìn)行多次(因而是好的封裝候選者),而選項(xiàng)分析只在程序開始時(shí)執(zhí)行一次。
迄今為止,我們所做的工作完成了每個(gè)MySQL 客戶機(jī)程序所必須做的事情:用適當(dāng)?shù)膮?shù)與服務(wù)器相連接。當(dāng)然應(yīng)該知道如何連接,現(xiàn)在知道怎么做了,并且處理的細(xì)節(jié)由客戶機(jī)程序框架( client4.c )來實(shí)現(xiàn),因此就不必再去考慮了。這就是說可以集中精力干真正感興趣的事情—訪問數(shù)據(jù)庫的內(nèi)容。應(yīng)用程序中所有的真正功能將在do_connect() 調(diào)用和do_disconnect() 調(diào)用之間發(fā)生,但是我們現(xiàn)在所擁有的是用于建立可為許多不同客戶機(jī)程序使用的基本框架。編寫一個(gè)新程序,要做到以下幾點(diǎn):
1) 制作一個(gè)client4.c 的備份。
2) 如果接受其他選項(xiàng)而不是client4.c 支持的標(biāo)準(zhǔn)選項(xiàng),那么修改處理選項(xiàng)循環(huán)。
3) 在連接和斷開調(diào)用之間加上自己的應(yīng)用程序代碼。
這樣就算完成了。
構(gòu)造客戶機(jī)程序框架的目的是,很容易地建立和斷開連接,以便集中精力干真正想做的事情。