《Unix网络编程》卷1:套接字联网API(第3版):名字与地址互换、IPv4和IPv6互操作性

时间:2022-04-27 22:10:39
全书共31章+附录。

计划安排
:吃透这本书,一天三章+源码,并实测代码做当天笔记,CSDN见。
时间安排:计划时间1.5个月 == 6个周末 ==  12天。
2017.08.05    第01-03章:TCP/IP简介、传输层、套接字编程简介
2017.08.06    第04-06章:基本TCP编程、TCP客户端/服务器程序、I/O复用
2017.08.12    第07-09章:套接字选项、基本UDP编程 、基本SCTP编程(略)
2017.08.13    第10-12章: SCTP客户端/服务器程序例子(略)、 名字与地址互换、IPv4和IPv6互操作性
2017.08.19    第13-15章:守护进程和inetd超级服务器、高级I/O、Unix域协议
2017.08.20    第16-18章:非阻塞I/O、ioctl操作、路由套接字
2017.08.26    第19-21章:密钥管理套接字、广播、多播
2017.08.27    第22-24章:高级UDP编程、高级SCTP编程、带外数据
2017.09.02    第25-27章:信号驱动I/O、线程、IP选项
2017.09.03    第28-30章:原始套接字、数据链路访问、客户端/服务器程序设计范式
2017.09.09    第31章-附录:流。附录:IPv4/6协议、调试技术
2017.09.10    整理、总结:思维导图。

>>第10章丶SCTP内容(略)

>>第11章丶名字与地址互换

DNS域名系统

主要用于 主机名字与IP地址之间的映射
DNS中的条目称为资源记录(resource record, RR)。
1. A        A记录把一个主机名映射成一个 32位的IPv4地址
2. AAAA     四A记录把一个主机名映射成一个 128位的IPv6地址
3. PTR      指针记录把IP地址映射成 主机名
4. MX       MX记录把一个主机指定作为给定主机的邮件交换器。
5. CNAME    规范名字是为常用的服务指派CNAME记录。

解析器和名字服务器


每个组织机构运行一个或多个名字服务器,通常就是所谓的BIND程序。
解析器代码通常包含在系统函数库中,在构造应用程序时被链编到应用程序中。
《Unix网络编程》卷1:套接字联网API(第3版):名字与地址互换、IPv4和IPv6互操作性

本地名字服务器主机的IP地址,在配置文件  /etc/resolv.conf 中。
不适用DNS也可以获取名字和地址信息,常用的替代方法是:
1. 静态主机文件: /etc/hosts
2. 网络信息系统: NIS
3. 轻权目录访问协议: LDAP

gethostbyname


查找主机名最基本的函数是gethostbyname,如果调用成功,就返回一个指向hostent结构的指针,该结构中含有所查找主机的所有IPv4地址。即: 执行的是A记录的查询,只能返回IPv4地址。
该函数不是可重入函数。
#include <netdb.h>
extern int h_errno;
struct hostent * gethostbyname(const char *name);
返回:若成功则为非空指针,若出错则为NULL切设置h_errno
struct hostent {
   char  *h_name;            /* official name of host */
   char **h_aliases;         /* alias list */
   int    h_addrtype;        /* host address type */
   int    h_length;          /* length of address */
   char **h_addr_list;       /* list of addresses */
}

h_errno常值:
    HOST_NOT_FOUND;
    TRY_AGAIN;
    NO_RECOVERY;
    NO_DATA; // 等同于 NO_ADDRESS

#include <netdb.h>
const char * hstrerror(int err); // @err, h_errno为唯一参数,返回错误信息字符串指针
/* 返回主机名字信息 - 代码演示 */#include	"unp.h"

int main(int argc, char **argv)
{
char *ptr, **pptr;
char str[INET_ADDRSTRLEN];
struct hostent *hptr;

while (--argc > 0) {
ptr = *++argv;
if ( (hptr = gethostbyname(ptr)) == NULL) {
printf ("gethostbyname error for host: %s: %s", ptr, hstrerror(h_errno));
continue;
}
printf("official hostname: %s\n", hptr->h_name);

for (pptr = hptr->h_aliases; *pptr != NULL; pptr++)
printf("\talias: %s\n", *pptr);

switch (hptr->h_addrtype) {
case AF_INET:
pptr = hptr->h_addr_list;
for ( ; *pptr != NULL; pptr++)
printf("\taddress: %s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
break;
default:
printf ("unknown address type");
break;
}
}
exit(0);
}
《Unix网络编程》卷1:套接字联网API(第3版):名字与地址互换、IPv4和IPv6互操作性

gethostbyaddr


该函数试图由一个二进制的IP地址找到相应的主机名,与gethostbyname刚好相反。同样返回一个hostent结构体指针,存放主机名的成员是h_name。
#include <netdb.h>
extern int h_errno;
#include <sys/socket.h>       /* for AF_INET */
struct hostent * gethostbyaddr(const void *addr, socklen_t len, int type);

getservbyname和getservbyport


服务也通常靠名字来认知,如果我们再程序代码中通过其名字而不是其端口号来指代一个服务,而且从名字到端口号的映射关系保存在一个文件中,通常是 /etc/services,那么即使端口号发生变动,我们需修改的仅仅是/etc/services文件中的某一行,而不必重新编译应用程序。
#include <netdb.h>
struct servent * getservbyname(const char *name, const char *proto); // 给定名字查找相应服务
struct servent * getservbyport(int port, const char *proto); // 给定端口号和可选协议查找相应服务
struct servent {
   char  *s_name;       /* official service name */
   char **s_aliases;    /* alias list */
   int    s_port;       /* port number */
   char  *s_proto;      /* protocol to use */
}
/* gethostbyname和getservbyname时间获取客户端程序 - 代码演示 */#include	"unp.h"

char * sock_ntop(const struct sockaddr *sa, socklen_t salen)
{
char portstr[8];
static char str[128]; /* Unix domain is largest */

switch (sa->sa_family) {
case AF_INET:
{
struct sockaddr_in *sin = (struct sockaddr_in *) sa;

if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
return(NULL);
if (ntohs(sin->sin_port) != 0) {
snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
strcat(str, portstr);
}
return(str);
}
default:
snprintf(str, sizeof(str), "sock_ntop: unknown AF_xxx: %d, len %d",
sa->sa_family, salen);
return(str);
}
return NULL;
}

int main(int argc, char **argv)
{
int sockfd, n;
char recvline[MAXLINE + 1];
struct sockaddr_in servaddr;
struct in_addr **pptr;
struct in_addr *inetaddrp[2];
struct in_addr inetaddr;
struct hostent *hp;
struct servent *sp;

if (argc != 3)
printf("usage: daytimetcpcli1 <hostname> <service>");

if ( (hp = gethostbyname(argv[1])) == NULL) {
if (inet_aton(argv[1], &inetaddr) == 0) {
printf("hostname error for %s: %s", argv[1], hstrerror(h_errno));
} else {
inetaddrp[0] = &inetaddr;
inetaddrp[1] = NULL;
pptr = inetaddrp;
}
} else {
pptr = (struct in_addr **) hp->h_addr_list;
}

if ( (sp = getservbyname(argv[2], "tcp")) == NULL)
printf("getservbyname error for %s", argv[2]);

for ( ; *pptr != NULL; pptr++) {
sockfd = socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = sp->s_port;
memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
printf("trying %s\n", sock_ntop((SA *) &servaddr, sizeof(servaddr)));

if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) == 0)
break; /* success */
printf ("connect error");
close(sockfd);
}
if (*pptr == NULL)
printf("unable to connect");

while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; /* null terminate */
fputs(recvline, stdout);
}
exit(0);
}

getaddrinfo和freeaddrinfo


getaddrinfo能够处理 名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构而不是一个地址列表。
#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); // 释放getaddrinfo获取的内存空间
const char * gai_strerror(int errcode); // 返回指向错误描述消息字符串的指针
struct addrinfo {
   int              ai_flags;
   int              ai_family;
   int              ai_socktype;
   int              ai_protocol;
   size_t           ai_addrlen;
   struct sockaddr *ai_addr;
   char            *ai_canonname;
   struct addrinfo *ai_next;
};

getnameinfo


以一个套接字赋值为参数,返回描述其中的主机的一个字符串和描述其中的服务的另一个字符串。
#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);

host_serv () / tcp_connect () / tcp_listen () / udp_client () / udp_connect () / udp_server () 函数根据实际使用时更新man手册查看。

>>第12章丶IPv4与IPv6的互操作性


双栈主机:运行IPv4协议栈和IPv6协议栈。
IPv6地址测试宏,在<netinet/in.h>中,有12个宏用于测试。

双栈主机上的IPv6服务器既能服务于IPv4客户,又能服务于IPv6客户。IPv4客户发送给郑重服务器的仍然是IPv4数据报,不过服务器的协议栈会把客户主机的地址转换成一个IPv4映射的IPv6地址,因为IPv6服务器仅仅处理IPv6套接字地址结构。
《Unix网络编程》卷1:套接字联网API(第3版):名字与地址互换、IPv4和IPv6互操作性






2017.08.13
10-12章基本完成...