Socket Host and Service

一条Socket通过一个四元组{Remote IP, Remote Port, Local IP, Local Port}来标识。 其中IP和Port都是数字,不利于人们记忆。所以要把它们映射成容易记忆的字符名:

  • IP => 主机名
  • Port => 服务名

因此Socket接口必需支持两种映射。

IP <==> 主机名

Socket中描述主机名的结构是:

1
2
3
4
5
6
7
8
9
10
11
12
struct hostent {
    char  *h_name;              /* Official (canonical) name of host */
    char **h_aliases;           /* NULL-terminated array of pointers
                                   to alias strings */
    int    h_addrtype;          /* Address type (AF_INET or AF_INET6) */
    int    h_length;            /* Length (in bytes) of addresses pointed
                                   to by h_addr_list (4 bytes for AF_INET,
                                   16 bytes for AF_INET6) */
    char **h_addr_list;         /* NULL-terminated array of pointers to
                                   host IP addresses (in_addr or in6_addr
                                   structures) in network byte order */
};

获得目标的struct hostent函数是:

1
2
3
4
5
6
7
8
// 主机名 => IP
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);

// IP => 主机名
#include <sys/socket.h>       /* for AF_INET */
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);

通过查看函数接口可以看出,使用目标服务名或者目标IP地址可以获得其struct hostent。 函数首先查询本地的静态主机文件(/etc/hosts),如果没有取到则使用DNS协议访问/etc/resolv.conf中配置的名字服务器,查询目标主机信息。这些过程对于函数使用者是透明的。

Port <==> 服务名

描述服务名的结构是:

1
2
3
4
5
6
struct servent {
    char  *s_name;          /* Official service name */
    char **s_aliases;       /* Pointers to aliases (NULL-terminated) */
    int    s_port;          /* Port number (in network byte order) */
    char  *s_proto;         /* Protocol (TCP/UDP)*/
};

相关的函数是:

1
2
3
4
5
#include <netdb.h>
// 服务名 => Port
struct servent *getservbyname(const char *name, const char *proto);
// Port => 服务名
struct servent *getservbyport(int port, const char *proto);

这些函数仅仅通过查询本地的(/etc/services文件)获得相应服务名。

通用型

以上函数在早期都不支持IPv6, 并且是不可重入的,因为它们都返回指向同一个静态结构的指针(还有错误号变量的问题)。 下面两个函数更早地支持IPv6和可重入。(P294)

getaddrinfo函数能够处理主机名到IP地址的转换:

1
2
3
4
5
6
7
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *node, const char *service,
	            const struct addrinfo *hints,
				struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);

getnameinfo能够处理服务名到端口的转换:

1
2
3
4
5
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
                char *host, size_t hostlen,
                char *serv, size_t servlen, int flags);