Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5634864
  • 博文数量: 922
  • 博客积分: 19333
  • 博客等级: 上将
  • 技术积分: 11226
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-27 14:33
文章分类

全部博文(922)

文章存档

2023年(1)

2020年(2)

2019年(1)

2017年(1)

2016年(3)

2015年(10)

2014年(17)

2013年(49)

2012年(291)

2011年(266)

2010年(95)

2009年(54)

2008年(132)

分类: LINUX

2012-02-16 21:49:28

++++++APUE读书笔记-16网络通信-03寻址(2)++++++

 

3、寻址(2)
================================================
 (3)地址查询
 理想情况,一个应用程序不用知道套接字地址的内部结构。如果一个应用程序简单地把套接字地址作为sockaddr结构传递并且不依赖任何协议相关特性,那么应用程序就可以在提供同样类型服务的不同协议上面工作。
 以前BSD网络软件提供了访问各种网络配置信息的接口。在前面我们简单讨论了网络数据文件以及用来访问它们的函数。在这个章节,我们用一个更详细的方式来讨论它们并且介绍一些新的查询地址信息的函数。
 这些函数返回的网络地址信息可以被保存在许多的地方。它们可以被保存在静态的文件中(例如/etc/hosts,/etc/services等);或者他们可以一个命名服务来管理,例如DNS(Domain Name System)或者NIS(Network Information Service)。我们不用考虑这些信息存放在哪里,我们可以使用同样的函数来访问它们。

 调用gethostent可以知道一个给定的计算机系统的hosts结构。
 #include
 struct hostent *gethostent(void);
 返回:如果成功,返回一个指针,如果错误返回NULL。
 void sethostent(int stayopen);
 void endhostent(void);
 如果主机数据库文件没有被打开,那么gethostent将会打开它。gethostent函数返回文件中的下一条entry。sethostent函数会打开文件,或者如果文件已经打开,那么就重新回到文件的开始。endhostent函数关闭文件。
 当gethostent返回的时候,我们获取到一个指向hostent结构的指针,这个指针可能会指向一个静态数据缓存,这个静态数据缓存在每次调用gethostent的时候都会被覆盖。hostent结构的定义至少有以下几个成员:
 struct hostent {
  char   *h_name;       /* name of host */
  char  **h_aliases;    /* pointer to alternate host name array */
  int     h_addrtype;   /* address type */
  int     h_length;     /* length in bytes of address */
  char  **h_addr_list;  /* pointer to array of network addresses */
  .
 };
 返回的地址使用网络字节次序表示。
 有两个额外的函数gethostbyname和gethostbyaddr以前被包含在hostent函数中,但是现在它们将会被作废。我们马上就会看到可以替代他们的东西了。

 我们可以使用一些类似的接口获得网络名称以及代号。
 #include
 struct netent *getnetbyaddr(uint32_t net, int type);
 struct netent *getnetbyname(const char *name);
 struct netent *getnetent(void);
 以上函数返回:如果成功返回指针,如果错误返回NULL。
 void setnetent(int stayopen);
 void endnetent(void);
 netent结构至少包含如下成员:
 struct netent {
  char     *n_name;      /* network name */
  char    **n_aliases;   /* alternate network name array pointer */
  int       n_addrtype;  /* address type */
  uint32_t  n_net;       /* network number */
  .
 };
 网络号码(n_net)以网络字节次序表示。地址类型是某一个地址常量(例如AF_INET)。

 我们可以通过如下的函数在协议名称和号码之间进行映射。
 #include
 struct protoent *getprotobyname(const char *name);
 struct protoent *getprotobynumber(int proto);
 struct protoent *getprotoent(void);
 以上函数返回:如果成功返回指针,如果错误返回NULL。
 void setprotoent(int stayopen);
 void endprotoent(void);
 通过POSIX.1定义的protoent结构至少应当包含如下成员:
 struct protoent {
  char   *p_name;     /* protocol name */
  char  **p_aliases;  /* pointer to alternate protocol name array */
  int     p_proto;    /* protocol number */
  .
 };

 服务通过地址部分的端口号码部分来表示。每个服务提供一个公共的端口号码。我们可以通过getservbyname函数将一个服务名称映射成一个端口,通过getservbyport函数将一个端口映射成一个服务名称,或者通过getservent函数顺序扫描服务数据库。
 #include
 struct servent *getservbyname(const char *name, const char *proto);
 struct servent *getservbyport(int port, const char *proto);
 struct servent *getservent(void);
 以上返回:如果成功返回指针,如果错误返回NULL。
 void setservent(int stayopen);
 void endservent(void);
 servent结构的定义至少包含如下的成员:
 struct servent {
  char   *s_name;      /* service name */
  char  **s_aliases;   /* pointer to alternate service name array */
  int     s_port;      /* port number */
  char   *s_proto;     /* name of protocol */
  .
 };

 POSIX.1定义了一些新的函数可以让一个应用程序将一个主机名称和服务名称映射成一个地址,以及进行相反的映射。这些函数替代了原来的gethostbyname和gethostbyaddr函数。
 getaddrinfo函数允许我们将一个主机名称和服务名称映射成一个地址。
 #include
 #include
 int getaddrinfo(const char *restrict host, const char *restrict service,
     const struct addrinfo *restrict hint, struct addrinfo **restrict res);
 返回值:如果成功返回0,如果错误返回非0的错误代码。
 void freeaddrinfo(struct addrinfo *ai);
 我们需要提供主机名称,服务名称,或者两者都提供。如果我们只提供一个名称,那么另外一个应该是空指针。主机名称可以是一个节点名称,或者点分十进制表示的主机地址。
 getaddrinfo函数返回一个addrinfo结构的链表。我们可以使用freeaddrinfo将一个或者多个这些结构释放,这取决于有多少个这个结构通过ai_next成员被链接。
 addrinfo结构的定义,至少包含如下的成员:
 struct addrinfo {
  int               ai_flags;       /* customize behavior */
  int               ai_family;      /* address family */
  int               ai_socktype;    /* socket type */
  int               ai_protocol;    /* protocol */
  socklen_t         ai_addrlen;     /* length in bytes of address */
  struct sockaddr  *ai_addr;        /* address */
  char             *ai_canonname;   /* canonical name of host */
  struct addrinfo  *ai_next;        /* next in list */
  .
 };
 我们可以提供一个可选的hint(参见getaddrinfo函数的参数)来符合特定的标准。这个hint是一个模板,它只使用ai_family,ai_flags,以及ai_socktype成员,用来过滤地址。剩下的整数成员必须被设置成0,并且指针成员被设置为空。下表列出了对于ai_flags成员我们可以使用的flags,以便定制地址和名称的处理方式。
 addrinfo结构的flags
+----------------------------------------------------------------------------------------+
|      Flag      |                              Description                              |
|----------------+-----------------------------------------------------------------------|
| AI_ADDRCONFIG  | 请求配置的是哪种地址类型(IPv4或者IPv6)。                             |
|----------------+-----------------------------------------------------------------------|
| AI_ALL         | 对IPv4和IPv6地址都进行查找(只和AI_V$MAPPED一块使用)。                |
|----------------+-----------------------------------------------------------------------|
| AI_CANONNAME   | 请求一个正式的名称(和别名相对)。                                    |
|----------------+-----------------------------------------------------------------------|
| AI_NUMERICHOST | 以数字形式返回主机地址。                                              |
|----------------+-----------------------------------------------------------------------|
| AI_NUMERICSERV | 将服务作为端口号码返回。                                              |
|----------------+-----------------------------------------------------------------------|
| AI_PASSIVE     | 绑定套接字地址以便侦听。                                              |
|----------------+-----------------------------------------------------------------------|
| AI_V4MAPPED    | 如果没有发现IPv6地址,那么返回以IPv6格式映射的IPv4地址。              |
+----------------------------------------------------------------------------------------+

 如果getaddrinfo失败,那么我们不能使用perror或者strerror来生成错误消息。相反,我们需要调用gai_strerror来将返回的错误号码转化成错误消息。
 #include
 const char *gai_strerror(int error);
 返回:一个描述错误的指向字符串的指针。

 getnameinfo函数会将一个地址转化成一个主机和服务名称。
 #include
 #include
 int getnameinfo(const struct sockaddr *restrict addr, socklen_t alen, char *restrict host,
                 socklen_t hostlen, char *restrict service, socklen_t servlen, unsigned int flags);
 返回:如果成功返回0,如果错误返回非0值。
 套接字地址(addr)被转化成一个主机名称和服务名称。如果host非空,那么它指向一个用来存放主机名称的长度为hostlen字节的缓存。类似地,如果service非空,那么它指向一个用来存放服务名称的长度为servlen字节的缓存。
 参数flags会给我们指定对这个转换进行的方式。下面的表格列举出来支持的flags。

 getnameinfo函数的标记
+--------------------------------------------------------------------------------------------------------------+
|      Flag      |                                         Description                                         |
|----------------+---------------------------------------------------------------------------------------------|
| NI_DGRAM       | 服务基于数据报而不是流。                                                                    |
|----------------+---------------------------------------------------------------------------------------------|
| NI_NAMEREQD    | 如果没有找到主机名称,那么将这个作为错误对待。                                              |
|----------------+---------------------------------------------------------------------------------------------|
| NI_NOFQDN      | 只返回本地主机整个域名称的节点名称部分。                                                    |
|----------------+---------------------------------------------------------------------------------------------|
| NI_NUMERICHOST | 返回数字形式表示的主机地址而不是名字形式。                                                  |
|----------------+---------------------------------------------------------------------------------------------|
| NI_NUMERICSERV | 返回数字形式的服务地址(例如端口号码),而不是名字的形式。                                  |
+--------------------------------------------------------------------------------------------------------------+

 举例:
 下面的程序列举getaddrinfo函数的使用。
 打印主机和服务信息的例子:
 #include "apue.h"//本书中一些预先定义好的自定义函数以及需要的头文件都在这里。
 #include
 #include
 #if defined(BSD) || defined(MACOS)
 #include
 #include
 #endif

 void print_family(struct addrinfo *aip)
 {
     printf(" family ");
     switch (aip->ai_family) {
     case AF_INET:
         printf("inet");
         break;
     case AF_INET6:
         printf("inet6");
         break;
     case AF_UNIX:
         printf("unix");
         break;
     case AF_UNSPEC:
         printf("unspecified");
         break;
     default:
         printf("unknown");
     }

 }
 void print_type(struct addrinfo *aip)
 {
     printf(" type ");
     switch (aip->ai_socktype) {
     case SOCK_STREAM:
         printf("stream");
         break;
     case SOCK_DGRAM:
         printf("datagram");
         break;
     case SOCK_SEQPACKET:
         printf("seqpacket");
         break;
     case SOCK_RAW:
         printf("raw");
         break;
     default:
         printf("unknown (%d)", aip->ai_socktype);
     }
 }

 void print_protocol(struct addrinfo *aip)
 {
     printf(" protocol ");
     switch (aip->ai_protocol) {
     case 0:
         printf("default");
         break;
     case IPPROTO_TCP:
         printf("TCP");
         break;
     case IPPROTO_UDP:
         printf("UDP");
         break;
     case IPPROTO_RAW:
         printf("raw");
         break;
     default:
         printf("unknown (%d)", aip->ai_protocol);
     }
 }

 void print_flags(struct addrinfo *aip)
 {
     printf("flags");
     if (aip->ai_flags == 0) {
         printf(" 0");

     } else {
         if (aip->ai_flags & AI_PASSIVE)
             printf(" passive");
         if (aip->ai_flags & AI_CANONNAME)
             printf(" canon");
         if (aip->ai_flags & AI_NUMERICHOST)
             printf(" numhost");
 #if defined(AI_NUMERICSERV)
         if (aip->ai_flags & AI_NUMERICSERV)
             printf(" numserv");
 #endif
 #if defined(AI_V4MAPPED)
         if (aip->ai_flags & AI_V4MAPPED)
             printf(" v4mapped");
 #endif
 #if defined(AI_ALL)
         if (aip->ai_flags & AI_ALL)
             printf(" all");
 #endif
     }
 }
 int main(int argc, char *argv[])
 {
     struct addrinfo     *ailist, *aip;
     struct addrinfo     hint;
     struct sockaddr_in  *sinp;
     const char          *addr;
     int                 err;
     char                abuf[INET_ADDRSTRLEN];

     if (argc != 3)
         err_quit("usage: %s nodename service", argv[0]);
     hint.ai_flags = AI_CANONNAME;
     hint.ai_family = 0;
     hint.ai_socktype = 0;
     hint.ai_protocol = 0;
     hint.ai_addrlen = 0;
     hint.ai_canonname = NULL;
     hint.ai_addr = NULL;
     hint.ai_next = NULL;
     if ((err = getaddrinfo(argv[1], argv[2], &hint, &ailist)) != 0)
         err_quit("getaddrinfo error: %s", gai_strerror(err));
     for (aip = ailist; aip != NULL; aip = aip->ai_next) {
         print_flags(aip);
         print_family(aip);
         print_type(aip);
         print_protocol(aip);
         printf("\n\thost %s", aip->ai_canonname?aip->ai_canonname:"-");
         if (aip->ai_family == AF_INET) {

            sinp = (struct sockaddr_in *)aip->ai_addr;
            addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf,
                INET_ADDRSTRLEN);
            printf(" address %s", addr?addr:"unknown");
            printf(" port %d", ntohs(sinp->sin_port));
         }
         printf("\n");
     }
     exit(0);
 }

 这个程序列举了getaddrinfo函数的使用。如果这个主机上面有多个协议支持给定的协议,那么程序将会打印更多的条目。在这个例子里,我们只打印工作在IPv4下的协议(ai_family等于AF_INET)的地址信息。如果我们想要限制输出为AF_INET协议族,那么我们应当设置hint中的ai_family成员。
 当我们在某一个系统下面运行这个程序的时候,我们得到如下类似的输出:
 $ ./a.out harry nfs
 flags canon family inet type stream protocol TCP
  host harry address 192.168.1.105 port 2049
 flags canon family inet type datagram protocol UDP
  host harry address 192.168.1.105 port 2049

 (4)将地址和套接字进行关联
 客户的套接字关联的地址并不是重点,我们可以让系统选择一个默认的地址。但是对于一个服务器,我们需要将一个公共的地址和服务器的套接字相关联,这样客户的请求将会到达。客户需要一个方式来发现这个地址,以便连接服务器,最简单的方式就是服务器保存一个地址并且将它注册到/etc/services中或者一个名字管理服务上。
 我们使用bind函数来将一个地址和一个socket相互关联。
 #include
 int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
 返回值:如果成功返回0,如果错误,返回1。
 对于我们能够使用的地址,有一些限制:
 a)我们指定的地址必须对于进程所运行的机器来说是合法的:我们不能指定一个属于其他机器的地址。
 b)地址必须和我们创建套接字时候使用的地址族的格式相匹配。
 c)地址中的端口号码,不能小于1024,除非进程具有特殊的权限(也就是,超级用户权限)。
 d)尽管有些协议支持多个绑定,一般来说也只有一个套接字端绑定到一个给定的地址上面。
 对于因特网域,如果我们指定特殊的IP地址(INADDR_ANY),套接字端将会被绑定到系统所有的网络接口上面。这也就是说,我们可以接收任何来自安装到系统上面的网络接口卡上面的包。我们在后面将会看到,如果我们调用connect或者listen的时候没有将地址绑定到socket上面,那么系统将会选择一个地址,并且把它绑定到我们的套接字上。

 我们可以使用getsockname函数来获取绑定到一个套接字上面的地址。
 #include
 int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
 返回:如果成功返回0,如果错误返回1。
 在调用getsockname之前,我们将alenp设置为指向一个整数,整数的内容包含sockaddr结构的缓存的大小。在返回的时候,整数被设置成返回的地址的大小。如果地址不适合提供的缓存,那么地址会被截断。如果当前没有地址绑定在套接字上面,那么结果是不确定的。

 如果套接字被连接到了一个对等的节点上面,我们可以通过调用getpeername函数来获得对等节点的地址。
 #include
 int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
 返回:如果成功返回0,如果错误返回1。
 除了返回一个对等节点的地址之外,getpeername函数和getsockname函数都一样。

参考:

 

 

阅读(890) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

vaqeteart2013-05-24 09:46:00

1,地址查询
host, net, protocol, service均有get,set,end三类函数,以及各自的getbyname和getbynumber之类的函数,另外getaddrinfo和getnameinfo完成将host+service转换成地址信息,以及将地址信息转换成host+service(这里的地址信息包括了family,flag,以及套接字地址等)。
2,套接字绑定
可以bind一个套接字地址到套接字描述符,或者直接connect和listen让系统自己选。可以通过getsockname获取一个套接字描述符相关的地址,或者getpeername获取套接字对端的地址。