; 6.5 客户机程序4—在运行时获取连接参数 ; 现在我们有了容易修改的防止出现错误的连接代码,我们要了解一些如何做某些比使用NULL 连接参数更灵巧的事情,如在运行时允许用户指定一些值。客户机程序3由于固定连接参数方面的缺陷,要想更改那些值中的任何一个,都必须编辑源文件并重新编译。这十分不方便,特别是想使程序用于其他人时。在运行时指定连接参数的一个通用的方法是使用命令行选项。MySQL分发包中的程序接受两种形式的连接参数,如表6 - 1所示。 与标准的MySQL客户机程序一致,客户机程序将接受同样的格式。这很容易,那是因为客户机库包括了实现选项分析的函数。 ; 除此之外,客户机程序具有从选项文件中抽取信息的能力。这允许将连接参数放在-/.my. c n f(也就是主目录中的.my.cnf 文件)中,以便不用在命令行中指定它们。客户机库使检查MySQL选项文件和从它们中抽取任何相关的值变得非常容易。只在程序中增加几行代码,就可以使选项文件识别它,并且通过编写自己的代码而不必重新改造这个框架来进行操作。附录E “MySQL程序参考”中说明了选项文件的语法。 ; 6.5.1访问选项文件内容 ; 使用load_default() 函数为连接参数值读取选项文件, load_default() 寻找选项文件、分析任何感兴趣的可选组的内容,以及重新编写程序的参数向量( a rgv[] 数组),以便把来自于那些组的信息以命令行选项的形式放置在argv[] 的开头。这就是说,在命令行指定出现的选项。因此,当分析命令选项时,就得到了作为常规选项分析循环部分的连接参数。选项加到argv[] 的开头而不是加到末尾,所以,如果连接参数真的在命令行指定,它们要比load_defaults() 增加的任何选项晚一些出现(因而忽略)。面的小程序show _ argv 显示了如何使用load _ defaults ( ),并举例说明了对参数向量如何做出这样的修改: 该处理选项文件的代码包括: ; ■ groups[] 是一个字符串数组,表示所感兴趣的选项文件组。对于客户机程序,始终至少指定“client” ([client] 组)。数组的最后一个元素必须是NULL。 ; ■ my_init() 是load_defaults() 所需的执行一些设置操作的初始化例程。 ; ■ load_defaults() 有四个参数:选项文件的前缀(这里应该始终是“ my”),列出感兴趣的可选组的数组、程序参数的数目和向量的地址。不传数目和向量的值,而是传地址,因为load_defaults() 需要改变它们的值。特别注意的是,虽然a rgv 是一个指针,但还是要传& argv ,它是指针的地址。 ; show _ argv打印参数两次,第一次是在命令行指定它们的时候,第二次是在load _ defaults( )修改它们的时候。为了查看load_defaults() 的运行效果,应确信在主目录中有一个具有[client] 组指定设置的. my.cnf 文件。假设. my.cnf 文件如下: 有可能会从不在命令行或~ /.my.cnf 文件中的s h o w _ a rgv 所产生的输出结果中看到一些选项。如果是这样,它们或许是在系统范围的选项文件中指定的。在主目录中读取.my.cnf 之前,load_defaults() 实际上是在MySQL数据目录中寻找/ e t c / my.cnf 和my.cnf 文件(在Windows中, load_defaults() 在Windows 系统目录中寻找文件C : my. c n f、C : mysql d a t a my.cnf 和my.ini )。 ; 使用load_defaults() 的客户机程序几乎始终是在选项组列表中指定“ c l i e n t”(以便从选项文件中获取任何通用的客户机设置),但是也可以为请求自己的程序请求特定值。可将下列代码: 修改为: 然后将[ show _ argv] 组加到~ / . my.cnf 文件中: 有了这些改变,再次调用show _ argv 就得到了一个不同的结果,如下所示: 参数数组中选项值出现的顺序取决于它们在选项文件中列出的顺序,而不是选项组在group[] 数组中列出的顺序。这意味着将可能在选项文件的[client] 组之后指定程序专有的组。即如果在两个组中都指定了一个选项,程序专有的值将有更高的优先权。在这个例子中可以看到: 在组[client] 和[ show _ argv] 中都指定了host 选项,但是因为组[ show _ argv] 在选项文件的最后出现,所以host 值将在参数向量中出现并取得优先权。 ; load_defaults() 不是从环境设置中提取值, 如果想使用环境变量的值, 例如MYSQL _ TCP _ PORT 或者MYSQL _ UNIX _ PORT ,就必须使用getenv() 来自己管理。我不想把这个管理能力增加到客户机中,但这里有一个例子,介绍了如何检查几个标准的与MySQL有关的环境变量值: 在标准MySQL客户机中,环境变量值的优先权比在选项文件或命令行指定值的优先权要低。如果检查环境变量的值,并要与约定保持一致,那么就要在调用load_default() 或者处理命令行选项之前(不是之后)检查环境。 ; 6.5.2 分析命令行参数 ; 现在我们可以把所有的连接参数都放入参数向量,但需要一个分析该向量的方法。getopt_long() 函数就是为此目的设计的。getopt_long() 设在MySQL客户机库的内部,因此,无论什么时候与库连接都可以访问它。源文件中要包含getopt.h 头文件,可以把这个头文件从MySQL源分发包的include 目录拷贝到正在开发的客户机程序所在的目录中。 ; load_defaults() 与安全 ; 因为有些程序(如p s)可以显示任何过程的参数列表, load_defaults() 将口令的文本放在参数列表中,所以您可能对它处理窥探的含意表示惊异。这没有问题,因为ps 显示原始的a rgv[] 内容,由load_defaults() 创建的任何口令参数都指向为它自己分配的区域,这个区域并不是原始区域的一部分,所以ps 看不见它。另一方面,除非故意清除,否则在命令行指定的口令会在ps 中出现。6 . 5 . 2节“分析命令行参数”介绍了如何去做。下面的程序show_param 使用load_defaults() 读取选项文件,然后调用getopt_long() 来分析参数向量。show_param 举例说明了通过执行以下操作参数处理的每个阶段发生了什么: ; 1) 建立主机名称、用户名称和口令的缺省值。 ; 2) 打印原始连接参数和参数向量值。 ; 3) 调用load_defaults() 重新编写参数向量,反映选项文件内容,然后打印结果向量。 ; 4) 调用getopt_long() 处理参数向量,然后打印结果参数值和参数向量中的剩余部分。 ; show_param 允许使用各种指定的连接参数的方法进行试验(无论是在选项文件中还是在命令行中),并通过显示使用什么值进行连接来查看结果。当实际上我们把参数处理代码与连接函数do_connect() 连到一起时,show_param 对于预知下一个客户机程序将要发生什么是很有用的。 ; 以下是show_param.c 的代码: 为了处理参数向量, show _ argv() 使用getopt_long() ,它在循环中调用: getopt_long() 的前两个参数是程序的计数参数和向量参数,第三个参数列出了要识别的选项字符。这些是程序选项的短名称形式。选项字符后可以有冒号、双冒号或者无冒号,表示选项值必须跟在选项后面、可以跟在选项后面或者不能跟在选项后面。第四个参数long_options 是一个指向可选结构数组的指针,每个可选结构为程序需要支持的选项指定信息。它的目标与第三个参数的可选字符串相类似。每个long_options[] 结构有四个元素,其描述如下: ; ■ 选项的长名称。 ; ■ 选项值。这个值可以是required _ argument、optional _ argument 或者no _ argument,表明选项值是必须跟在选项后面、可以跟在选项后面,还是不能跟在选项后面(它们与第三个参数选项字符串中的冒号、双冒号或无冒号的作用相同)。 ; ■ 标记参数。可用它存储变量指针。如果找到这个选项, getopt_long() 则把第四个参数指定的值存储到变量中去。如果标记是NULL,getopt_long() 就把optarg 变量指向下一个选项的任何值,并返回选项的短名称。long_options[] 数组为所有的选项指定了NULL。那就是说,如果遇到getopt _ long( ),就返回每个参数,以便我们可以在switch语句中来处理它。 ; ■ 选项的短(单个字符)名称。在long_options[] 数组中指定的短名称必须与作为第三个参数传递给getopt_long() 的选项字符串所使用的字母相匹配,否则程序将不能正确处理命令行参数。long_options[] 数组必须由一个所有元素都设为0 的结构所终止。getopt_long() 的第五个参数是一个指向int 变量的指针。getopt_long() 把与最后遇到的选项相符合的long_options[] 结构索引存储到变量中( show_param 不用这个值做任何事情)。 ; 请注意,口令选项(指定为--password 或者-p )可以获得一个选项值,那就是说,如果使用长选项形式可指定为--password 或者--password = your_pass,如果使用短选项形式则指定为-p 或者- p your _ pass。可选字符串中“ p” 后面的双冒号和long_options[] 数组中的optional _ argument 表示了口令值的可选特性。特别是, MySQL客户机允许在命令行省略口令值,然后提示输入。这样避免了在命令行给出口令,它防止其他人通过偷窃看到口令。在写下一个客户机程序(客户机程序4)时,将把口令检查性能添加进去。下面是show_param 的调用示例和结果输出(假设~ /.my.cnf 一直与show _ argv 示例有相同的内容): 输出结果说明从命令行得到主机名(忽略选项文件中的这个值),从选项文件中得到用户名和口令。getopt_long() 正确分析了选项是在短选项形式( -h host_name )中指定还是在长选项形式( --user = paul ,--password = secret )中指定。 ; 现在让我们去掉纯粹说明选项处理例程是如何工作的这一部分,把剩余部分作为根据选项文件或命令行提供的任何选项而连接到服务器的客户机的基础。源文件client4.c 的代码如下: 与前面开发的客户机程序1、客户机程序2和客户机程序3比较一下,客户机程序4具有一些以前没有的内容: ; ■ 允许在命令行指定数据库名称,它紧跟在由getopt_long() 分析的选项的后面。这与MySQL分发包中标准客户机的行为是一致的。 ; ■ 对口令值做了备份之后,删除参数向量中的任何口令值。这使时间窗口最小化,在时间窗口中命令行所指定的口令对于ps 或其他系统状态程序是可见的(窗口缩到最小,但并没有删除。命令行指定的口令仍然不太安全)。 ; ■ 如果给出没有值的口令选项,则客户机程序提示用户用get_tty_password() 输入口令。在客户机库中,这是一个实用程序,它提示输入口令而不在显示器上回应(客户机库充满了这样吸引人的东西。因为找到了相关的例程和使用它们的方法,所以有助于从MySQL客户机程序的源文件中的读取)。您可能会问:“为什么不只调用getpass( )呢?”回答是,并不是所有的系统都有这个函数,如Windows。get_tty_password() 可以在系统间移植,因为它被配置为适应各种不同系统。 ; 客户机程序4按照指定的选项来响应。假设没有使事件复杂化的选项文件。如果无参数调用客户机程序4,则连接到localhost,并把UNIX 注册名和无口令传递到服务器中。相反,如果像介绍的那样调用客户机程序4,则提示输入口令(没有直接以-p 开头的口令值),连接到some _ host,并将用户名some_user 和键入的口令都传递到服务器: 客户机程序4也把数据库名some_db 传递给do _ connect( ),成为当前数据库。如果没有选项文件,则处理它的内容并用来改变参数连接。 ; 早期,我们曾热衷于封装代码,创建包装函数,目的是断开与服务器的连接和从服务器的连接断开。询问是否把分析选项部分放置到包装函数中也是合理的。我想这是可能的,但并不想去做。选项分析代码与连接代码在程序间并不一致:程序经常支持除了标准选项之外的其他选项,不同的程序很可能支持其他选项的不同设置。这就使选项处理循环标准化的函数很难编写。而且,与连接的建立不同,在它的执行过程中程序可以希望进行多次(因而是好的封装候选者),而选项分析只在程序开始时执行一次。 ; 迄今为止,我们所做的工作完成了每个MySQL客户机程序所必须做的事情:用适当的参数与服务器相连接。当然应该知道如何连接,现在知道怎么做了,并且处理的细节由客户机程序框架( client4.c )来实现,因此就不必再去考虑了。这就是说可以集中精力干真正感兴趣的事情—访问数据库的内容。应用程序中所有的真正功能将在do_connect() 调用和do_disconnect() 调用之间发生,但是我们现在所拥有的是用于建立可为许多不同客户机程序使用的基本框架。编写一个新程序,要做到以下几点: ; 1) 制作一个client4.c 的备份。 ; 2) 如果接受其他选项而不是client4.c 支持的标准选项,那么修改处理选项循环。 ; 3) 在连接和断开调用之间加上自己的应用程序代码。 ; 这样就算完成了。 ; 构造客户机程序框架的目的是,很容易地建立和断开连接,以便集中精力干真正想做的事情。