主机名与网络名查询在这一章,我们将会了解以下内容:
如何确定我们的本地主机名
如何将主机名解析为IP地址
如何将IP地址解析为主机名
一旦我们学完这一章,我们就可以在我们的客户端与服务器程序中使用主机名或是IP地址。
理解名字的需要人们喜欢使用和记住名字,而不是IP地址。在网络世界中,名字实际上为我们解决了许多问题:
他们为一个网站提供了人类友好的引用
他们可以允许IP地址改变,而名字保持不变
他们允许为同一个主机或是服务指定多个IP地址
我们已经理解比起IP地址来,名字提供了更为简单的引用。然而,另外一点就是名字可以保持不变,而允许主机的IP地址发生变化。IP地址的变化通常是因为网络的变化,ISP的变化,设备的变化等。只要我们记住了网络站点的名字,我们就可以不必关心其实际的IP地址。
最后一点是简单的被轻视了。查看ftp.redhat.com,我们会得到下面的两个IP地址:
•208.178.165.228
•206.132.41.212
我们不必在意这两个IP地址是指向同一个ftp主机或是为负载平衡的目的而设的两个不同的镜像站点。事实是,通过使用任何一个IP地址,我们可以得到我们希望的同一个文件。
这就引入了这一章名字解析的主题。首先,我们将会学到如何查看本地系统的信息。然而我们将会学到如何使用远程主机名,如何查询, 如何将其转换为IP地址。
使用uname(2)函数我们要知道的一个有用的函数就是uname(2)函数。这个函数会告诉我们运行我们程序的系统的相关信息。这个函数的原型如下:
#include <sys/utsname.h>int uname(struct utsname *buf);这个函数将返回信息存放在结构buf中。当函数成功时会返回0,当发生错误时会返回-1。外部变量errno将会包含错误号。
struct utsname的定义如下所示:#include <sys/utsname.h> /* defines the following structure */struct utsname { char sysname[SYS_NMLN]; char nodename[SYS_NMLN]; char release[SYS_NMLN]; char version[SYS_NMLN]; char machine[SYS_NMLN]; char domainname[SYS_NMLN];};结构成员描述如下:
成员 描述
sysname 代表正在使用的操作系统。对于Linux而言,这个值为C字符串"Linux"。
nodename 代表机器的网络节点主机名。
release 操作系统发布号。
version 操作系统版本号。对Linux而言,这代表内核构建的版本号,日期以及时间戳。
machine 代表主机的硬件类型。例如"i686"代表一个奔腾CPU。
domainname 返回主机的NIS/YP域名。
下面的这个例子程序允许我们测试由uname返回的信息。这个程序调用uname函数并且显示在结构utsname中返回的信息。
/*uname.c
*
* Example of uname(2):
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/utsname.h>
int main(int argc,char **argv)
{
int z;
struct utsname u_name;
z = uname(&u_name);
if(z==-1)
{
fprintf(stderr,"%s:uname(2)\n",strerror(errno));
exit(1);
}
printf(" sysname[] = '%s';\n",u_name.sysname);
printf(" nodename[] = '%s';\n",u_name.nodename);
printf(" release[] = '%s';\n",u_name.release);
printf(" version[] = '%s';\n",u_name.version);
printf(" machine[] = '%s';\n",u_name.machine);
printf(" domainname[] = '%s';\n",u_name.domainname);
return 0;
}
这个函数的运行结果如下:
@tux
$ ./uname
sysname[] = 'Linux';
nodename[] = 'tux';
release[] = '2.2.10';
version[] = #1 Sun Jul 4 00:28:57 EDT 1999';
machine[] = 'i686';
domainname[] = '';
取得主机名与域名函数gethostname(2)与getdomainname(2)是另外两个可以用来得到当前系统的函数。
使用gethostname(2)函数
gethostname函数可以用来确定当前的主机名。这个函数的概要如下:
#include <unistd.h>
int gethostname(char *name, size_t len);
这个函数需要两个参数:
接收缓冲区name,其长度必须为len字节或是更长
接收缓冲区name的最大长度
如果函数成功,则返回0。如果发生错误则返回-1。错误号存放在外部变量errno中。
使用getdomainname(2)函数
getdomainname是中一个方便的函数,可以允许程序获得程序正运行的主机的NIS域名。函数概要如下:
#include <unistd.h>
int getdomainname(char *name,size_t len);
这个函数的用法也gethostname相同。
Linux手册页表明getdomainname函数内部使用e函数来得到并返回NIS域名。
测试getdomainname与gethostname函数下面这个程序演示了这两个函数的用法。这个程序只是简单的调用这两个函数并报告其结果。
/*gethostn.c
*
* Example of gethostname(2):
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main(int argc,char **argv)
{
int z;
char buf[32];
z = gethostname(buf,sizeof buf);
if(z==-1)
{
fprintf(stderr,"%s:gethostname(2)\n",strerror(errno));
exit(1);
}
printf("host name = '%s'\n",buf);
z = getdomainname(buf,sizeof buf);
if(z==-1)
{
fprintf(stderr,"%s:getdomainname(2)\n",strerror(errno));
exit(1);
}
printf("domain name = '%s'\n",buf);
return 0;
}
这个程序的运行结果如下:
$ ./gethostn
host name = 'tux'
domain name = ''
在了解了如何获得本地系统的信息以后,现在我们就可以将我们的注意力转移到解析远程主机名上了。
解析远程地址将一个名字转换为IP地址的过程是相当复杂的。他涉及到我们本地系统上/etc目录中的许多文件,包括/etc/resolv.conf,/etc/hosts,/etc/nsswitch.conf文件。依据我们的系统是如何配置的,同时也会涉及到其他的文件以及守护进程。例如,在查询这些文件以后,会查询一个名字服务器,这个名字服务器会向前查询其他的名字服务器。所有这些复杂的细节是我们编写程序时不想考虑的内容。
幸运的时,程序的编写者可以像驼鸟那样将头伸在沙中。如果系统进行正确的配置,一些系统函数调用就将是程序员所需要的全部内容。下面的内容就是一些相关的函数集合,这些函数为我们隐藏了远程名字查询的复杂。
错误报告
我们将会描述的这些函数使用一个不同的变量来进行错误报告。在通常的C库函数中,错误代码将会存放在变量errno中。然而这一部分的函数将错误代码存放在变量h_errno中。其概要如下:
#include <netdb.h>
extern int h_errno;
h_errno是一个外部整型变量。错误号会由下列函数传送到变量h_errno中:
gethostbyname(3)
gethostbyaddr(3)
下列函数使用h_errno的值作为输入:
herror(3)
hstrerror(3)
报告一个h_errno错误
正如我们已经知道的,strerror函数方便的将一个errno值转换为一个人类可读的错误信息。相类似的,也存在两个函数用来报告h_errno值:
#include <netdb.h>
extern int h_errno;
void herror(const char *msg);
const char *hstrerror(int err);
函数herror(3)与函数perror(3)相类似。现在认为herror函数是陈旧的,但是我们会在已存在的代码中发现他。他们打印出消息msg,然后是错误原因。这些会被写入标准错误输出流中。
hstrerror(3)函数模仿相似的strerror(3)函数的功能。接受h_errno作为输入值,他会返回一个指向错误信息的指针。返回的指针直到下次调用这个函数之前都是可用的。
理解错误代码
h_errno变量所用的C宏本质上与errno的值不同。下表列出当调用gethostbyname(3)和gethostbyaddr(3)函数时可能会遇到的错误代码。
错误宏 描述
HOST_NOT_FOUND 指定的主机名未知
NO_ADDRESS 指定的主机名可用,但是没有IP地址
NO_DATA 与NO_ADDRESS相同
NO_RECOVERY 发生了一个无法恢复的名字服务器错误
TRY_AGAIN 稍后重试此次操作
在这里要注意,TRY_AGAIN错误代码代表一个也许会被重试努力覆盖的条件;NO_RECOVERY错误代码代表一个不可重试的名字服务器错误,因为在这种条件下不可进行修复;NO_RECOVERY(NO_DATA)错误代码表明已知查询的主机,但是却没有为其定义IP地址;最后,HOST_NOT_FOUND错误代码表明查询的名字不可知。
使用gethostbyname(3)函数这是我们在这一章将会学习的重要的一个函数。这个函数接受我们希望解析的主机名,然后返回一个以各种方式标识的结构。这个函数的概要如下:
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);
函数gethostbyname接受一个代表我们希望解析为地址的主机名的C字符串作为输入参数。如果函数调用成功会返回一个指向hostent结构的指针。如果函数失败,则会返回一个NULL指针,而错误原因将会存放在变量h_errno中。
hostent结构如下:
struct hostent {
char *h_name; /* official name of host */
char **h_aliases; /* alias list */
int h_addrtype; /* host address type */
int h_length; /* length of address */
char **h_addr_list; /* list of addresses */
};
/* for backward compatibility */
#define h_addr h_addr_list[0]
当我们进行套接口编程时,经常使用这个结构就会对其逐渐熟悉起来了。
结构成员描述
h_name
hostent结构中的h_name成员是我们正在查询的主机的官方名字。也就是主机的权威名字。如果我们提供了一个别名,或是不带域名的主机名,那么这个成员就会描述我们要查询的正确名字。这个成员对于显示或是将结果记入日志文件是相当有用的。
h_aliases成员
返回结构的h_aliases成员是我们查询的主机名的别名数组。这个列表的结尾被标记为NULL指针。作为例子,www.lwn.net的别名列表如下所示:
struct hostent *ptr;
int x;
ptr = gethostbyname("www.lwn.net");
for ( x=0; ptr->h_aliases[x] != NULL; ++x )
printf ("alias = '%s'\n", ptr->h_aliases[x]);
在上面的例子中并没有错误检查。如果ptr为NULL,就表明没有可用的信息。
h_addrtype成员
在成员h_addrtype中返回的值为AF_INET。然而,因为IPv6已经完全实现,名字服务器也会返回IPv6地址。当这种情况发生时,h_addrtype就会在合适的时候返回AF_INET6。
h_addrtype值的上的就是表明在列表h_addr_list中的地址格式。
h_length成员
这个值与h_addrtype成员相关。对于当前的TCP/IP协议版本(IPv4),这个成员的值总是为4,表明4个字节的IP地址。然而,当IPv6实现时,这个值将会是16,并且返回IPv6地址。
h_addr_list成员
当执行一个名字到IP地址的转换时,这个成员就会成为我们最重要的信息。当h_addrtype成员包含AF_INET时,这个数组中的每一个指针指向一个4字节的IP地址。这个列表的结尾被标记为NULL指针。
使用gethostbyname(3)函数
在下面的例子程序中演示了gethostbyname的用法。这个程序会在命令行接收多个主机名,然后分别查询每一个。所有可用的信息都会发送到标准输出,如果名字没有解析将会报告错误。
/*
* lookup.c
*
* Example of gethostbyname(3):
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
extern int h_errno;
int main(int argc,char **argv)
{
int x,x2;
struct hostent *hp;
for (x=1;x<argc;++x)
{
/*
* Look up the hostname:
*/
hp = gethostbyname(argv[x]);
if(!hp)
{
/* Report lookup failure */
fprintf(stderr,"%s:host '%s'\n",
hstrerror(h_errno),argv[x]);
continue;
}
/*
* Report the findings:
*/
printf("Host %s: \n",argv[x]);
printf("\tOfficially:\t%s\n",
hp->h_name);
fputs("\tAliases:\t",stdout);
for(x2=0;hp->h_aliases[x2];++x2)
{
if(x2)
fputs(", ",stdout);
fputs(hp->h_aliases[x2],stdout);
}
fputc('\n',stdout);
printf("\tType:\t\t%s\n",
hp->h_addrtype == AF_INET
?"AF_INET"
:"AF_INET6");
if(hp->h_addrtype == AF_INET)
{
for(x2=0;hp->h_addr_list[x2];++x2)
printf("\tAddress:\t%s\n",
inet_ntoa(*(struct in_addr *)hp->h_addr_list[x2]));
}
putchar('\n');
}
return 0;
}
这个函数的执行结果如下:
$ ./lookup www.lwn.net sunsite.unc.edu ftp.redhat.com
Host www.lwn.net :
Officially: lwn.net
Aliases: www.lwn.net
Type: AF_INET
Address: 206.168.112.90
Host sunsite.unc.edu :
Officially: sunsite.unc.edu
Aliases:
Type: AF_INET
Address: 152.2.254.81
Host ftp.redhat.com :
Officially: ftp.redhat.com
Aliases:
Type: AF_INET
Address: 206.132.41.212
Address: 208.178.165.228
gethostbyaddr(3)函数有时我们知道一个IP地址,但是我们报告主机,而不是IP地址。一个服务器也许需要记录与其连接的客户端主机名,而不仅仅是IP地址。gethostbyaddr函数概要如下:
#include <sys/socket.h> /* for AF_INET */
struct hostent *gethostbyaddr(
const char *addr, /* Input address */
int len, /* Address length */
int type); /* Address type */
gethostbyaddr函数接受三个输入参数:
1 要转换为主机名的输入地址(addr)。对于AF_INET地址类型,这是指向地址结构中的sin_addr成员。
2 输入地址的长度。对于AF_INET类型,这个值为4;而对于AF_INET6类型,这个值为16。
3 输入地址的类型,这个值为AF_INET或是AF_INET6。
在这里要注意,第一个参数为一个字符指针,实质上允许接受多种格式的地址。我们需要将我们的地址指针转换为(char *)来满足编译。第二个参数指明了所提供的地址的长度。
第三个参数为所传递的地址的类型。对IPv4的网络为AF_INET,也许在将来,这个值将会是IPv6地址格式的AF_INET6。
下面的例子程序是前一章的所演示的服务器程序的修改版本。这个服务器会在当前目录打开一个名为srvr2.log的日志文件,并且记录每一个连接。这个服务会同时记录IP地址以及主机名。
/*srvr2.c
*
* Example daytime server,
* with gethostbyaddr(3):
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
/*
* This function report the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
if(errno != 0)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
}
fputs(on_what,stderr);
fputc('\n',stderr);
exit(1);
}
int main(int argc,char **argv)
{
int z;
char *srvr_addr = NULL;
char *srvr_port = "9099";
struct sockaddr_in adr_srvr; /* AF_INET */
struct sockaddr_in adr_clnt; /* AF_INET */
int len_inet; /* length */
int s; /* Socket */
int c; /* Client socket */
int n; /* bytes */
time_t td; /* current date&time */
char dtbuf[128]; /* Date/Time info */
FILE *logf; /* Log file for the server */
struct hostent *hp; /* Host entry ptr */
/*
* Open the log file:
*/
if(!(logf = fopen("srvr2.log","w")))
bail("fopen(3)");
/*
* Use a server adderss from the command
* line,if one has been provided.
* Otherwise,this program will default
* to using the arbitrary address
* 127.0.0.1:
*/
if(argc>=2)
{
/* Addr on cmdline: */
srvr_addr = argv[1];
}
else
{
/* Use default address :*/
srvr_addr = "127.0.0.1";
}
/*
* If there is a second argument on the
* command line,use it as the port #:
*/
if(argc>=3)
srvr_port = argv[2];
/*
* Create a TCP/IP socket to use:
*/
s = socket(PF_INET,SOCK_STREAM,0);
if(s==-1)
bail("socket()");
/*
* Create a server socket address:
*/
memset(&adr_srvr,0,sizeof adr_srvr);
adr_srvr.sin_family = AF_INET;
adr_srvr.sin_port = htons(atoi(srvr_port));
if(strcmp(srvr_addr,"*") != 0)
{
/* Normal Address */
adr_srvr.sin_addr.s_addr =
inet_addr(srvr_addr);
if(adr_srvr.sin_addr.s_addr == INADDR_NONE)
bail("bad address.");
}
else
{
/* wild address */
adr_srvr.sin_addr.s_addr = INADDR_ANY;
}
/*
* bind the server address:
*/
len_inet = sizeof adr_srvr;
z = bind(s,(struct sockaddr *)&adr_srvr,len_inet);
if(z==-1)
bail("bind(2)");
/*
* Make it a listening socket:
*/
z = listen(s,10);
if(z==-1)
bail("listen(2)");
/*
* Start the server loop:
*/
for(;;)
{
/*
* wailt for a connect:
*/
len_inet = sizeof adr_clnt;
c = accept(s,(struct sockaddr *)&adr_clnt,&len_inet);
if(c==-1)
bail("accept(2)");
/*
* log the address of the client
* who connected to us:
*/
fprintf(logf,
"Client %s:",
inet_ntoa(adr_clnt.sin_addr));
hp = gethostbyaddr((char *)&adr_clnt.sin_addr,
sizeof adr_clnt.sin_addr,
adr_clnt.sin_family);
if(!hp)
fprintf(logf,"Error:%s\n",
hstrerror(h_errno));
else
fprintf(logf,"%s\n",
hp->h_name);
fflush(logf);
/*
* Generate a time stamp:
*/
time(&td);
n = (int)strftime(dtbuf,sizeof dtbuf,
"%A %b %d %H:%M:%S %Y\n",
localtime(&td));
/*
* Write result back the client:
*/
z = write(c,dtbuf,n);
if(z==-1)
bail("write(2)");
/*
* Close this client's connection:
*/
close(c);
}
return 0;
}
使用sethostent(3)函数sethostent函数可以允许作为程序设计者的我们控制如果何执行名字服务器查询。这个函数可以改善我们程序的网络性能。这个函数的概要如下:
#include <netdb.h>
void sethostent(int stayopen);
sethostent函数只有一个输入参数。参数stayopen是一个布尔值输入参数。
当为真(非零)时,使用TCP/IP套接口执行查询,并且保持名字服务器处于打开状态。
当为假(零)时,使用UDP数据报进行名字服务器查询。
当我们的程序要执行频繁的名字服务器请求时,第一种情况是很有用的。对于许多查询来说,这是一种高性能的选择。然而,如果我们的程序只是在启动时执行一次查询,设置为FALSE是比较合适的,因为UDP有较少的网络负担。
在前面的例子中我们显示了如何使用gethostbyname函数来执行名字服务器查询。要使得这个程序使用连接的TCP套接口而不是UDP数据报,我们可以在程序中添加一个sethostent函数调用。
使用endhostent(3)函数在使用TRUE调用sethostent函数之后,我们的程序就会进入一个处理的过程中,而程序却知道不再需要额外的名字查询。为了以一种节省的方式使用资源,我们需要一个方法来结束与名字服务器的连接,从而可以释放正在使用的TCP/IP套接口。这就是endhostent函数的目的。这个函数的概要如下:
#include <netdb.h>
void endhostent(void);
正如我们所看到的,这个函数没有参数,也没有返回值。
endhostent函数对于服务器,尤其是Web服务器而言是十分重要的,因为文件描述符是有限的。我们也许会记起一个套接口使用一个文件描述符,而每一个连接的客户端需要一个套接口。服务器的性能通常受到服务器所打开的文件描述符的数量的限制。这样,当服务器不再需要文件描述符时,关闭这些文件描述符就是十分重要的。
分享到:
相关推荐
Linux Socket学习(十六).txt和Linux Socket学习(十七).txt可能涉及到了更高级的主题,如多路复用I/O,如select()、poll()或epoll(),这些工具可以帮助程序同时处理多个Socket连接。 Linux Socket学习(十四)....
《实战Linux Socket编程》是...总之,《实战Linux Socket编程》的配套源代码是一个宝贵的资源,它使学习者能够动手实践,从而更好地掌握网络编程的核心概念和技术,对于提升Linux系统下的网络编程能力具有重要意义。
在IT行业中,Linux Socket是进行网络通信的重要工具,尤其对于系统和网络程序员来说,理解和掌握Linux Socket编程至关重要。本示例代码提供了客户端(client)和服务器端(server)的实现,帮助初学者深入理解如何在...
在IT领域,Linux Socket编程是网络通信的核心技术之一,它为开发者提供了在Linux操作系统上实现进程间通信(IPC)和网络通信的接口。本实战指南将深入探讨这一主题,帮助你掌握如何在Linux环境中构建高效的网络应用...
在Linux操作系统中,Socket是一种进程间通信机制,它允许不同进程或者不同计算机之间的通信。本教程将深入探讨Linux下的socket编程,包括服务器和客户端的实现。我们主要关注以下几个知识点: 1. **Socket基本概念*...
本压缩包“linuxsocket.zip”包含了基于TCP/IP协议的socket通信测试代码,是学习Linux应用编程的一个实用资源。这里我们将深入探讨Linux TCP/IP socket编程的相关知识点。 1. **TCP/IP协议栈**:TCP/IP协议栈是...
本篇文章将根据给定文件“linux socket学习.pdf”的内容进行展开,重点讨论 socket 的定义、如何创建 socket 以及 socket 与其他常见 I/O 操作的区别。 #### 二、理解 Socket ##### 2.1 定义 Socket Socket 可以...
通过本书的学习,读者可以掌握如何在Linux环境中使用Socket进行网络通信,构建高性能、稳定的网络应用。下面将根据提供的压缩包文件名“实战 Linux Socket编程代码”来解析其中可能包含的知识点。 1. **Socket基础...
Linux Socket编程是网络编程的重要组成部分,它为开发者提供...通过这个实战教程,开发者将学习如何在Linux环境中创建和管理Socket,实现网络通信功能,这对于开发服务器应用、网络工具或分布式系统是至关重要的技能。
在Linux操作系统中,Socket接口是应用程序与网络协议交互的主要接口,它允许程序通过网络发送和接收数据。在本文中,我们将深入探讨“Linux Socket网络驱动深度分析”这一主题,特别是当应用尝试创建一个用于捕获...
Linux Socket是Linux操作系统中用于进程间通信(IPC)的一种接口,它允许程序通过网络协议进行数据传输。在本文中,我们将深入探讨Linux ...通过IBM技术论坛的文章,你可以更深入地了解和学习Linux Socket的各种细节。
### Linux Socket Programming (Linux 套接字编程) #### 知识点概览: 1. **Socket编程基础** ...通过以上知识点的学习,读者可以深入了解Linux环境下的Socket编程,并能够掌握如何设计和实现网络通信程序。
Linux Socket实战编程是深入理解网络通信机制的...总的来说,这份资源对于想要深入理解和精通Linux Socket编程的IT从业者来说是一份宝贵的资料,通过学习和实践,可以显著提升网络编程能力,为职业发展打下坚实基础。
在学习和实践Linux+Socket编程时,需要不断的实验和调试。常用工具包括tcpdump、wireshark等用于捕获和分析网络数据包,strace用于跟踪系统调用,以及gdb用于调试C/C++编写的网络程序。 Linux+Socket编程不仅是一门...
Gay撰写,为读者提供了丰富的实例和深入的理论知识,是学习Linux Socket编程的绝佳资源。 **二、基本Socket概念** ### 1. Socket简介 在Linux环境下,Socket是一种进程间通信(IPC)的方式,它提供了一种在两个...
在IT行业中,网络通信是至关重要的部分,而Linux Socket编程是实现这一目标的关键技术。Socket是一种接口,允许应用程序通过网络发送和接收数据。本篇将深入探讨标题为"Linux Socket两则示例"的资源,其中包括`echo_...
10. **学习资源**:学习Socket编程,可以参考《UNIX Network Programming》等经典书籍,同时网上有许多关于Linux Socket编程的教程和示例代码可供学习。 总之,"Linux下的socket聊天室程序"是一个很好的实践项目,...
本资源“实战Linux Socket编程”旨在帮助你深入理解并掌握这一关键技能,尤其对于那些希望在嵌入式领域有所建树的开发者来说,它是不可或缺的学习资料。 在Linux系统中,Socket编程主要涉及以下几大知识点: 1. **...
《Linux Socket编程示例》...以上仅为《Linux Socket编程示例》部分章节的知识点总结,实际书中还包含了更多深入的技术细节和实战案例,对于希望掌握Linux环境下网络编程技术的学习者而言,是一本不可多得的参考书籍。