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

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

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

有可能會從不在命令行或~ /.my.cnf 文件中的s h o w _ a rgv 所產(chǎn)生的輸出結(jié)果中看到一些選項。如果是這樣,它們或許是在系統(tǒng)范圍的選項文件中指定的。在主目錄中讀取. m y.cnf 之前,load_defaults() 實際上是在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() 的客戶機程序幾乎始終是在選項組列表中指定“ c l i e n t”(以便從選項文件中獲取任何通用的客戶機設(shè)置),但是也可以為請求自己的程序請求特定值??蓪⑾铝写a:

修改為:

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

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

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

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




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

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

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




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

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