本文主要给大家分享网络概念中的DNS,前边的章节已经给大家讲述了链路层、物理层、网络层、应用层、传输层,以及套接字socket等,欢迎学习嵌入式网络编程的朋友关注、转载和发表评论! (绝对的好文,建议先收藏和转载!)
本次分享的主要内容为:(本文通过函数例程讲解DNS,较抽象,一时没看懂的朋友建议先收藏!)
1.1理解 DNS
1.2和 DNS 有关的函数和结构
1.3DNS 例程
2.1套接字的 Client/Server 结构
2.2简单的流服务器
2.3简单的流式套接字客户端程序
2.4数据报套接字例程(DatagramSockets)
8.6.1 理解 DNS
你应该知道 DNS 吧?DNS 是"Domain Name Service" (域名服务)的缩写。有了它,你就可以通过一个可读性非常强的因特网名字得到这个名字所代表的 IP 地址。转换为 IP 地址后,你就可以使用标准的套接字函数(bind(),connect(),sendto(),或是其他任何需要使用的函数)。
在这里,如果你输入命令:
$ telnet bbs.tsinghua.edu.cn
Telnet 可以知道它需要连往 202.112.58.200。这就是通过 DNS 来实现的。
8.6.2 和 DNS 有关的函数和结构
DNS 是怎样工作的呢?你可以使用 gethostbyname() 函数。它的声明如下:
#include
struct hostent *gethostbyname(const char *name);
正如你所看见的,它返回了一个指向 struct hostent 的指针.Struct hostent 是这样定义的: struct hostent {
char *h_name;
char **h_aliases; int h_addrtype; int h_length;
char **h_addr_list;
};
#define h_addr h_addr_list[0] 下面是上面各个域代表含义的解释:
· h_name 是这个主机的正式名称。
· h_aliases 是一个以 NULL(空字符)结尾的数组,里面存储了主机的备用名称。
· h_addrtype 是返回地址的类型,一般来说是"AF_INET"。
· h_length 是地址的字节长度。
· h_addr_list 是一个以 0 结尾的数组,存储了主机的网络地址。注意:网络地址是以网络字节顺序存储的。
· h_addr - h_addr_list 数组的第一个成员。
gethostbyname() 返回的指针指向结构 struct hostent, 如果发生错误, 它将会返回 NULL
(但是 errno 并不代表错误代码, h_errno 中存储的才识错误代码。 参考下面的 herror()函数)。
应该如何使用这个函数呢?它看起来有一点点吓人。相信我,它使用起来远远要比它看起来容易。
8.6.3 DNS 例程
下面我们来看一段例程:
#include
#include
#include
#include
#include
#include
int main (int argc, char *argv[])
{
struct hostent *h;
/* 检测命令行中的参数是否存在 */ if (argc != 2)
{
/* 如果没有参数,给出使用方法 */
fprintf (stderr " usage: getip address" );
/* 然后退出 */ exit(1);
}
/* 取得主机信息 */
if((h=gethostbyname(argv[1])) == NULL)
{
/* 如果 gethostbyname 失败,则给出错误信息 */ herror(" gethostbyname" );
/* 然后退出 */ exit(1);
}
/* 列印程序取得的信息 */
printf("Host name : %s" , h->h_name); printf(" IP Address : %s" ,
inet_ntoa (*((struct in_addr *)h->h_addr)));
/* 返回 */ return 0;
}
使用 gethostbyname() 函数,你不能使用 perror() 来输出错误信息(因为错误代码存储在
h_errno 中而不是 errno 中。所以,你需要调用 herror() 函数。
上 面 的 程 序 是 不 是 很 神 奇 呢 ? 你 简 单 的 传 给 gethostbyname() 一 个 机 器 名
("bbs.tsinghua.edu.cn"),然后就从返回的结构 struct hostent 中得到了 IP 等其他信息。程序中输出 IP 地址的程序需要解释一下:
h->h_addr 是一个 char*,但是 inet_ntoa()函数需要传递的是一个 struct in_addr 结构。所以上面将 h->h_addr 强制转换为 struct in_addr*,然后通过它得到了所有数据。
8.7 套接字的 Client/Server 结构
现在是一个服务器/客户端的世界。几乎网络上的所有工作都是由客户端向服务器端发送请求来实 现的。比如 Telnet ,当你向一个远程主机的 23 端口发出连接请求的时候,远程主机上的服务程序
(Telnetd)就会接受这个远程连接请求。允许你进行 login 操作,等等。
服务器和客户机之间可以使用任何方式通讯,包括 SOCK_STREAM,SOCK_DGRAM,或是其他任何方式
(只要他们使用相同的方法)。
一些服务器/客户机的例子是 telnet/telnetd,ftp/ftpd,bootp/bootpd。每次你使用 ftp,你同时都使用了远程主机上的 ftpd 服务。一般来说,服务器上有一个程序等待连接。当接收到一个连接的时候,服务器程序调用系统函数 fork()来得到一个子进程,专门处理这个连接的操作。
下面我们来看一个简单的流服务器:
8.7.1 简单的流服务器
这个服务器所有的工作就是给远程的终端发送一个字符串: "Hello,World!"你所需要做的就是在命令行上启动这个服务器,然后在另外一台机器上使用 telnet 连接到这台我们自己写的服务器上:
$ telnet remotehostname 4000
remotehostname 就是你运行我们自己写的服务器的那台机器名。服务器代码:
#include
#include
#include
#include
#include
#include
#include
#include
/* 服务器要监听的本地端口 */
#define MYPORT 4000
/* 能够同时接受多少没有 accept 的连接 */
#define BACKLOG 10
main()
{
/* 在 sock_fd 上进行监听,new_fd 接受新的连接 */ int sock_fd, new_fd ;
/* 自己的地址信息 */ struct sockaddr_in my_addr;
/* 连接者的地址信息*/
struct sockaddr_in their_addr; int sin_size;
/* 这里就是我们一直强调的错误检查.如果调用 socket() 出错,则返回 */ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
/* 输出错误提示并退出 */ perror(" socket" ); exit(1);
}
/* 主机字节顺序 */ my_addr.sin_family = AF_INET;
/* 网络字节顺序,短整型 */ my_addr.sin_port = htons(MYPORT);
/* 将运行程序机器的 IP 填充入 s_addr */ my_addr.sin_addr.s_addr = INADDR_ANY;
/* 将此结构的其余空间清零 */ bzero(&(my_addr.sin_zero), 8);
/* 这里是我们一直强调的错误检查! */
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1)
{
/* 如果调用 bind()失败,则给出错误提示,退出 */ perror(" bind" );
exit(1);
}
/* 这里是我们一直强调的错误检查! */ if (listen(sockfd, BACKLOG) == -1)
{
/* 如果调用 listen 失败,则给出错误提示,退出 */ perror(" listen" );
exit(1);
}
while(1)
{
/* 这里是主 accept()循环 */
sin_size = sizeof(struct sockaddr_in);
/* 这里是我们一直强调的错误检查! */
if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1)
{
/* 如果调用 accept()出现错误,则给出错误提示,进入下一个循环 */ perror(" accept" );
continue;
}
/* 服务器给出出现连接的信息 */
printf(" server: got connection from %s" , inet_ntoa(their_addr.sin_addr));
/* 这里将建立一个子进程来和刚刚建立的套接字进行通讯 */ if (!fork())
{
/* 这里是子进程 */
/* 这里就是我们说的错误检查! */
if (send(new_fd, "Hello, world!" , 14, 0) == -1)
{
/* 如果错误,则给出错误提示,然后关闭这个新连接,退出 */ perror(" send" );
close(new_fd); exit(0);
}
/* 关闭 new_fd 代表的这个套接字连接 */ close(new_fd);
}
}
/* 等待所有的的进程都退出 */ while(waitpid(-1,NULL,WNOHANG) > 0);
}
为了更清楚的描述这个套接字服务器的运行过程,我把所有的代码都写在了这个大大的 main()主函数中。如果你觉得分成几个子程序会清楚一些,你可以自己试着把这个程序改成几个小函数。
你可以使用下面这个套接字客户端来得到 "Hello, World!"这个字符串。
8.7.2 简单的流式套接字客户端程序
这个程序比起服务器端程序要简单一些。它所做的工作就是 connect() 到服务器的 4000 端口, 然后把服务器发送的字符串给显示出来。
客户端程序:
#include
#include
#include
#include
#include
#include
#include
#include
/* 服务器程序监听的端口号 */
#define PORT 4000
/* 我们一次所能够接收的最大字节数 */
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
/* 套接字描述符 */ int sockfd, numbytes; char buf[MAXDATASIZE]; struct hostent *he;
/* 连接者的主机信息 */ struct sockaddr_in their_addr;
/* 检查参数信息 */ if (argc != 2)
{
/* 如果没有参数,则给出使用方法后退出 */ fprintf(stderr," usage: client hostname" ); exit(1);
}
/* 取得主机信息 */
if ((he=gethostbyname(argv[1])) == NULL)
{
/* 如果 gethostbyname()发生错误,则显示错误信息并退出 */ herror(" gethostbyname" );
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
/* 如果 socket()调用出现错误则显示错误信息并退出 */ perror(" socket" );
exit(1);
}
/* 主机字节顺序 */ their_addr.sin_family = AF_INET;
/* 网络字节顺序,短整型 */ their_addr.sin_port = htons(PORT);
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
/* 将结构剩下的部分清零*/ bzero(&(their_addr.sin_zero), 8); if(connect(sockfd, (struct sockaddr *)&their_addr,
sizeof(struct sockaddr)) == -1)
{
/* 如果 connect()建立连接错误,则显示出错误信息,退出 */ perror(" connect" );
exit(1);
}
if((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1)
{
/* 如果接收数据错误,则显示错误信息并退出 */ perror(" recv" );
exit(1);
}
buf[numbytes] = ' 0'; printf(" Received: %s" ,buf); close(sockfd);
return 0;
}
注意:显然,你必须在运行 client 之前先启动 server。否则 client 的执行会出错(显示"Connection refused" ) 。
8.7.3 数据报套接字例程(DatagramSockets)
在这里我不对数据报做过多的描述,下面你将看见另外一对例程(使用数据报): talker.c 和
listener.c。
listener 在一台机器上作为服务器程序运行,它监听端口 5000 。
talker 发送 UDP 数据包到服务器的 5000 端口,传送使用用户据。下面是 listener.c 的源码:
#include
#include
#include
#include
#include
#include
#include
#include
/* 要连接到的端口号 */
#define MYPORT 5000
/* 能够接收的最长数据 */
#define MAXBUFLEN 100
int main()
{
int sockfd;
/* 本机的地址信息 */ struct sockaddr_in my_addr;
/* 连接这的地址信息 */ struct sockaddr_in their_addr; int addr_len, numbytes;
char buf[MAXBUFLEN];
/* 取得一个套接字描述符 */
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
/* 如果取得套接字描述符失败,则给出错误信息,退出 */ perror(" socket" );
exit(1);
}
/* 主机字节顺序 */ my_addr.sin_family = AF_INET;
/* 网络字节顺序,短整型 */ my_addr.sin_port = htons(MYPORT);
/* 自动设置为自己的 IP */ my_addr.sin_addr.s_addr = INADDR_ANY;
/* 将结构的其余空间清零 */ bzero(&(my_addr.sin_zero), 8);
/* 绑定端口 */
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1)
{
/* 如果绑定端口出错,则显示错误信息然后退出 */
perror(" bind" ); exit(1);
}
addr_len = sizeof(struct sockaddr);
/* 接收数据 */
if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN, 0,(struct sockaddr *)&their_addr, &addr_len)) == -1)
{
/* 如果 recvfrom()调用出错,则显示错误信息后退出 */ perror(" recvfrom" );
exit(1);
}
/* 显示接收到的数据 */
printf(" got packet from %s",
inet_ntoa(their_addr.sin_addr)); printf(" packet is %d bytes long" ,numbytes); buf[numbytes] = ' 0';
printf(" packet contains " %s" " ,buf);
/* 关闭套接字连接 */ close(sockfd);
}
注意我们调用 socket() 函数的时候使用的是 SOCK_DGRAM 为参数。而且,我们并不需要 listen() 或是 accept()。这是因为我们使用了无连接的用户数据报套接字!
下面的是 talker.c 的源码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 要连接的端口 */
#define MYPORT 5000
int main(int argc, char *argv[])
{
int sockfd;
/* 连接者的地址信息 */ struct sockaddr_in their_addr; struct hostent *he;
int numbytes; if (argc != 3)
{
/* 检测是否有所须参数,如没有,则显示使用方法后退出 */
fprintf(stderr," usage: talker hostname message" ); exit(1);
}
if ((he=gethostbyname(argv[1])) == NULL)
{
/* 取得主机的信息,如果失败则显示错误信息后退出 */ herror(" gethostbyname" );
exit(1);
}
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
/* 申请一个数据报套接字描述符,失败则退出 */ perror (" socket" );
exit(1);
}
/* 主机字节顺序 */ their_addr.sin_family = AF_INET;
/* 网络字节顺序,短整型 */ their_addr.sin_port = htons(MYPORT);
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
/* 将结构中未用的部分清零 */ bzero(&(their_addr.sin_zero), 8);
if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0,(struct sockaddr
*)&their_addr, sizeof(struct sockaddr))) == -1)
{
/* 把信息发送到指定的主机指定端口,如出错则提示退出 */ perror(" recvfrom" );
exit(1);
}
printf(" sent %d bytes to %s" ,numbytes, inet_ntoa(their_addr.sin_addr));
/* 关闭套接字描述符后退出 */ close(sockfd);
return 0;
}
上面这两个程序,你需要在一台主机上首先运行 listener,然后在另外一台主机上运行 talker。现在看到它们之间的通讯了吗?
最后,我们要注意一点:使用连接的数据报套接字。因为我们在讲使用数据报,所以我们需要了解 它。如果我们的 talker 程序使用了 connect() 函数来连接 listener 的地址,那么 talker 程序就能够使用 sent() 和 recv() 来处理数据了。因为 talker 程序在 connect() 函数中已经知道了远程主机的地址和端口号。