18.4 网络通信
在18.3节的例子中,实现了在同一台计算机中通过套接字通信的方法。如果是在网络中,需要使客户端连接的地址为一个有效的IP地址,这样就能在两台计算机之间通信。除了IP地址以外,计算机名也可以用来代表一台网络中的计算机,例如在浏览器中使用的域名就是Internet中由DNS服务所提供的网络地址机制。
18.4.1 查询主机名称
查询主机名称是通过访问主机数据库实现的,服务器数据库接口函数在头文件netdb.h中定义。与此相关的函数有sethostbyaddr()和gethostbyname()两个,它们的一般形式如下:
- struct hostent *gethostbyaddr(const void
*addr, size_t len, int type); - struct hostent *gethostbyname(const char *name);
函数的返回值是指向hostent结构的指针,该结构用于保存主机名称等信息,hostent结构的定义如下:
- struct hostent {
- char *h_name; // 主机名
- char **h_aliases; // 别名列表
- int h_addrtype; // 地址类型
- int h_length; // 地址的字节长度
- char **h_addr_list // 地址列表
- };
gethostbyaddr()是通过IP地址查询主机信息,gethostbyname()是通过主机名查询主机信息。如果在主机数据库中没有查到相关主机或地址的项,这些函数会返回一个空指针。
与服务及其关联的端口号有关的信息可以通过getservbyname()函数和getservbyport()函数查询,它们的一般形式如下:
- struct servent *getservbyname(const char
*name, const char *proto); - struct servent *getservbyport(int port, const char *proto);
其中,proto参数指定了用来连接到该项服务的协议,SOCK_STREAM类型的TCP连接对应的是tcp,UDP连接对应的是udp。函数的返回值是servent结构指针,该结构的定义如下:
- struct servent {
- char *s_name; // 服务名
- char **s_aliases; // 服务别名列表
- int s_port; // 端口号
- char *s_proto; // 协议类型
- };
如果需要将地址信息转换为四分十进制法表示,可使用inet_ntoa()函数来完成。该函数被包含在头文件"arpa/inet.h"中,它的一般形式是:
- char *inet_ntoa(struct in_addr in);
如果执行成功,它将返回一个指向四分十进制法表示地址的字符串的指针,否则返回-1。查询当前主机的主机名的函数是gethostname(),该函数的一般形式是:
- int gethostname(char *name, int namelength);
如果执行成功,*name参数所指向的内存空间将被写入主机名,namelength参数限定了*name参数所指向内存空间的长度。如果主机名太长,会被截短到namelength限定的长度。函数执行成功时返回0,否则返回-1。下面用一个示例说明查询主机名称操作的方法:
- #include <sys/socket.h> // 包含套接字相关函数
- #include <netinet/in.h> // 包含AF_INET相关结构
- #include <netdb.h> // 包含读
取主机信息的相关函数 - #include <stdio.h>
- #include <unistd.h>
- int main(int argc, char *argv[])
- {
- char *host; // 用于保存主机名
- int sockfd; // 用于保存套接字标识符
- int len, result;
- struct sockaddr_in address; // 定义套接字地址
- struct hostent *hostinfo; // 定义主机信息结构
- struct servent *servinfo; // 定义服务信息结构
- char buffer[128];
- if (argc == 1)
- host = "localhost"; // 如果没有
指定主机名,则置为本机 - else
- host = argv[1];
- hostinfo = gethostbyname(host); // 获得主机信息
- if (!hostinfo) {
- fprintf(stderr, "找不到主机: %s\n", host);
- return 1;
- }
- servinfo = getservbyname("daytime", "tcp"); // 获得服务信息
- if (!servinfo) {
- fprintf(stderr, "无daytime服务\n");
- return 1;
- }
- printf("daytime服务端口是:%d\n", ntohs(servinfo -> s_port));
- // 输出端口信息
- sockfd = socket(AF_INET, SOCK_STREAM, 0); // 建立套接字
- address.sin_family = AF_INET; // 定
义套接字地址中的域 - address.sin_port = servinfo -> s_port; // 定义套接字端口
- address.sin_addr = *(struct in_addr *) *hostinfo -> h_addr_list;
- // 定义套接字地址
- len = sizeof(address);
- result = connect(sockfd, (struct sockaddr *) &address, len);
- // 请求连接
- if (result == -1) {
- perror("获得数据出错");
- return 1;
- }
- result = read(sockfd, buffer, sizeof(buffer)); // 接收数据
- buffer[result] = '\0';
- printf("读取%d字节:%s", result, buffer); // 输出数据
- close(sockfd); // 关闭连接
- return 0;
- }
运行程序时,将一个UNIX服务器地址作为该程序的运行参数。daytime服务的端口号是通过网络数据库函数getserverbyname()确定的,这个函数返回的是关于网络服务方面的资料,它们和主机资料差不多。程序会先尝试连接指定主机信息数据库里的地址,如果成功就读取daytime服务返回的信息,该信息是一个表示UNIX时间和日期的字符串。如果测试平台是Linux桌面操作系统,修改"/etc/xinetd.d/daytime"文件,将此文件中两个disable的值由yes改为no,再重启计算机即可运行daytime服务。