7.6 辅助函数
FreeBSD C 语言库包含了许多套接字编程的辅助函数。 例如,在样例客户端中,我们硬性指定了 time.nist.gov 的IP地址。但是我们并非总是知道 IP地址。甚至即使我们知道, 允许用户输入IP地址甚至域名 将使用我们的软件更有弹性。
7.6.1 gethostbyname
域名是不能直接传送给任何套接字函数的, FreeBSD C 语言库携带了函数 gethostbyname(3)和gethostbyname2(3), 声明在netdb.h中。
struct hostent * gethostbyname(const char *name); struct hostent * gethostbyname2(const char *name, int af);
这两个函数都返回hostent
结构指针, 内含有关域的许多信息。对于我们的情况,结构体中的域 h_addr_list[0]
指向长度 h_length
字节的地址, 也按网络字节顺序存储。
这允许我们建立一个要有弹性得多的──也要有用得多的 ──版本的daytime程序:
/* * daytime.c * * G. Adam Stanislav 编程 * 2001年6月19日 */ #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> int main(int argc, char *argv[]) { register int s; register int bytes; struct sockaddr_in sa; struct hostent *he; char buf[BUFSIZ+1]; char *host; if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); return 1; } bzero(&sa, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(13); host = (argc > 1) ? (char *)argv[1] : "time.nist.gov"; if ((he = gethostbyname(host)) == NULL) { herror(host); return 2; } bcopy(he->h_addr_list[0],&sa.sin_addr, he->h_length); if (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) { perror("connect"); return 3; } while ((bytes = read(s, buf, BUFSIZ)) > 0) write(1, buf, bytes); close(s); return 0; }
现在我们可以在命令行打一个域名(或者一个IP地址, 两种方式都可以),程序将尝试连接daytime服务器。 否则,将仍然缺省为time.nist.gov。然后, 即使在使用缺省值的情形中我们将使用gethostbyname
而不是硬性指定192.43.244.18。这样,即使将来 IP地址变更,我们也能找到。
由于从我们的本地计算机获取时间几乎不需要时间, 你可以一并运行daytime两次: 第一次从time.nist.gov取得时间, 第二次从你自己的系统取得时间。然后你可以比较结果, 看看你的系统时钟到底怎么样:
% daytime ; daytime localhost 52080 01-06-20 04:02:33 50 0 0 390.2 UTC(NIST) * 2001-06-20T04:02:35Z %
正如你看见的,我的系统比NIST时间快两秒钟。
7.6.2 getservbyname
有时你不能确定某种服务该用什么端口。 函数getservbyname(3),也声明在 netdb.h中,此时就很上手:
struct servent * getservbyname(const char *name, const char *proto);
结构体servent
包含 s_port
,这是正确的端口号, 已经按照网络字节顺序存储。
假如我们不知道daytime服务的正确端口, 我们可以这样找到:
struct servent *se; ... if ((se = getservbyname("daytime", "tcp")) == NULL { fprintf(stderr, "Cannot determine which port to use.\n"); return 7; } sa.sin_port = se->s_port;
你通常知道端口。但是如何你正开发一个新协议, 你可能正在一个非正式端口上测试。 有一天,你要注册那个协议和端口 (如果不在别处,至少要在你的 /etc/services里,那是 getservbyname
查找的地方)。 上面的代码就不再会返回错误,你就可以使用临时端口号。 一旦你已经将协议列入/etc/services, 你的软件不必重写代码也可以找到端口。