15.3 网络信息
到目前为止,我们的客户和服务器程序一直是把地址和端口号编译到它们自己的内部。对于一个更通用的服务器和客户程序来说,我们可以通过网络信息函数来决定应该使用的地址和端口。
如果你有足够的权限,也可以将自己的服务添加到/etc/services文件中的已知服务列表中,并在这个文件中为端口号分配一个名字,使用户可以使用符号化的服务名而不是端口号的数字。
类似地,如果给定一个计算机的名字,你可以通过调用解析地址的主机数据库函数来确定它的IP地址。这些函数通过查询网络配置文件来完成这一工作,如/etc/hosts文件或网络信息服务。常用的网络信息服务有NIS(Network Information Service,网络信息服务,以前称为Yellow Pages,黄页服务)和DNS(Domain Name Service,域名服务)。
主机数据库函数在接口头文件netdb.h中声明,如下所示:
这些函数返回的结构中至少会包含以下几个成员:
如果没有与我们查询的主机或地址相关的数据项,这些信息函数将返回一个空指针。
类似地,与服务及其关联端口号有关的信息也可以通过一些服务信息函数来获取。如下所示:
proto参数指定用于连接该服务的协议,它的两个取值是tcp和udp,前者用于SOCK_STREAM类型的TCP连接,后者用于SOCK_DGRAM类型的UPD数据报。
结构servent至少包含以下几个成员:
如果想获得某台计算机的主机数据库信息,可以调用gethostbyname函数并且将结果打印出来。注意,要把返回的地址列表转换为正确的地址类型,并用函数inet_ntoa将它们从网络字节序转换为可打印的字符串。函数inet_ntoa的定义如下所示:
这个函数的作用是,将一个因特网主机地址转换为一个点分四元组格式的字符串。它在失败时返回-1,但POSIX规范并未定义任何错误。其他可用的新函数还有gethostname,它的定义如下所示:
这个函数的作用是,将当前主机的名字写入name指向的字符串中。主机名将以null结尾。参数namelength指定了字符串name的长度,如果返回的主机名太长,它就会被截断。gethostname在成功时返回0,失败时返回-1,但POSIX规范中没有定义任何错误。
实 验 网络信息
下面这个程序getname.c用来获取一台主机的有关信息。
(1)与往常一样,包含必要的头文件并声明变量:
(2)把host变量设置为getname程序所提供的命令行参数,或默认设置为用户主机的主机名:
(3)调用gethostbyname,如果未找到相应的信息就报告一条错误:
(4)显示主机名和它可能有的所有别名:
(5)如果查询的主机不是一个IP主机,就发出警告并退出:
(6)否则,显示它的所有IP地址:
此外,你也可以用gethostbyaddr函数来查出哪个主机拥有给定的IP地址。你可以在服务器上用这个函数来查找连接客户的来源。
实验解析
getname程序通过调用gethostbyname从主机数据库中提取出主机的信息。它打印出主机名、它的别名(这台计算机的其他名字)和该主机在它的网络接口上使用的IP地址。运行这个示例程序并指定主机名tilde时,程序给出了以太网和调制解调器两个网络接口的信息。如下所示:
当我们使用主机名localhost时,程序只给出了回路网络的信息。如下所示:
现在可以改进我们的客户程序,使它可以连接到任何有名字的主机。这次不是连接到我们的示例服务器,而是连接到一个标准服务,这样就可以演示端口号的提取操作了。
大多数UNIX和一些Linux系统都有一项标准服务daytime,它提供系统的日期和时间。客户可以连接到这个服务来查看服务器的当前日期和时间。下面就是完成这一工作的客户程序getdate.c。
实 验 连接到标准服务
(1)包含必要的头文件和变量声明:
(2)查找主机的地址,如果找不到,就报告一条错误:
(3)检查主机上是否有daytime服务:
(4)创建一个套接字:
(5)构造connect调用要使用的地址:
(6)然后建立连接并取得有关信息:
你可以用getdate获取任一已知主机的日期和时间。
如果你看到如下所示的一条错误信息:
或是:
这可能是因为你正在连接的计算机没有启用daytime服务。最新版本的Linux系统在默认情况下都没有启用该服务。在下一节中,你将学习如何启用这项服务以及其他一些服务。
实验解析
运行这个程序时,你可以指定要连接的主机。daytime服务的端口号是通过网络数据库函数getservbyname来确定的,该函数以与返回主机信息类似的方法返回和网络服务相关的信息。程序getdate尝试连接到指定主机返回的地址列表中的第一个地址,如果成功,它就读取daytime服务返回的信息——一个表示UNIX日期和时间的字符串。
15.3.1 因特网守护进程(xinetd/inetd)
UNIX系统通常以超级服务器的方式来提供多项网络服务。超级服务器程序(因特网守护进程xinetd或inetd)同时监听许多端口地址上的连接。当有客户连接到某项服务时,守护程序就运行相应的服务器。这使得针对各项网络服务的服务器不需要一直运行着,它们可以在需要时启动。
因特网守护进程在现代Linux系统中是通过xinetd来实现的。xinetd实现方式取代了原来的UNIX程序inetd,尽管你仍然会在一些较老的Linux系统中以及其他的类UNIX系统中看到inetd的应用。
我们通常是通过一个图形用户界面来配置xinetd以管理网络服务,但我们也可以直接修改它的配置文件。它的配置文件通常是/etc/xinetd.conf和/etc/xinetd.d目录中的文件。
每一个由xinetd提供的服务都在/etc/xinetd.d目录中有一个对应的配置文件。xinetd将在其启动时或被要求的情况下读取所有这些配置文件。
下面是一些xinetd配置文件的例子,首先是daytime服务的配置:
然后是文件传输服务的配置:
我们的getdate程序连接的daytime服务实际上就是由xinetd自身负责处理的(它被标记为internal,即内部),它同时支持SOCK_STREAM(tcp)和SOCK_DGRAM(udp)套接字。
ftp文件传输服务只支持SOCK_STREAM套接字,并且是由一个外部程序来提供服务的。在本例中这个程序是vsftpd,当有客户连接到ftp的端口时,守护进程就会启动它。
为了激活服务配置的修改,你需要编辑xinetd的配置文件,然后发送一个挂起信号给守护进程,但我们建议你使用一种更加友好的方式来配置服务。为了允许time-of-day客户进行连接,你可以使用Linux系统提供的工具来启用daytime服务。对于SUSE和openSUSE系统来说,你可以通过SUSE控制中心来配置服务,如图15-1所示。Red Hat的版本(包括企业版Linux和Fedora)也有一个类似的配置界面。在图15-1中,daytime服务同时针对TCP和UDP查询进行了启用。
图 15-1
对于使用inetd而不是xinetd的系统来说,下面是从inetd的配置文件/etc/inetd.conf中提取的完成相同功能的配置,inetd使用该配置文件来决定运行哪些服务器:
注意,在本例中,ftp服务是由外部程序wu.ftpd提供的。如果你的系统运行着inetd进程,你可以通过编辑文件/etc/inetd.conf(一行开头的#号表示这是一个注释行)再重新启动inetd进程的方法来改变提供的服务。你可以用kill命令向inetd进程发送一个挂起信号来重启该进程。为了方便执行这个操作,有的系统会配置成让inetd将它的进程号写入一个文件中。此外,你还可以使用killall命令,如下所示:
# killall -HUP inetd
15.3.2 套接字选项
你可以用许多选项来控制套接字连接的行为,这些选项的数目众多,我们不可能在这里对它们一一解释。setsockopt函数用于控制这些选项,它的定义如下所示:
你可以在协议层次的不同级别对选项进行设置。如果想要在套接字级别设置选项,就必须将level参数设置为SOL_SOCKET。如果想要在底层协议级别(如TCP、UDP等)设置选项,就必须将level参数设置为该协议的编号(1)(可以通过头文件netinet/in.h或函数getprotobyname来获得)。
option_name参数指定要设置的选项;option_value参数的长度为option_len字节,它用于设置选项的新值,它被传递给底层协议的处理函数,并且不能被修改。
在头文件sys/socket.h中定义的套接字级别选项,如表15-5所示。
表 15-5
SO_DEBUG和SO_KEEPALIVE用一个整数的option_value值来设置该选项的开(1)或关(0)。SO_LINGER需要使用一个在头文件sys/socket.h中定义的linger结构,来定义该选项的状态以及套接字关闭之前的拖延时间。
setsockopt在成功时返回0,失败时返回-1。它的手册页介绍了更多的选项和错误。