《Unix 网络编程》11:名字和地址转换
名字和地址转换
系列文章导航:《Unix 网络编程》笔记
域名系统
简介
域名系统主要用于主机名字和 IP 地址之间的映射。主机名可以是:
- 简单名字,如:centos01
- 全限定域名(FQDN[1]),如:xxx.com
资源记录
记录 | 作用 |
---|---|
A | 指向IPv4 |
AAAA | 指向IPv6 |
PTR | 把IP地址映射为主机名 |
MX | 邮件记录 |
CNAME | 为二级域名指定域名或IP |
解析器和名字服务器
DNS
DNS替代方法
如果使用 DNS 查找主机名,则使用 /etc/resolv.conf
指定的 DNS
有如下替代方法:
- 静态主机文件,如
/etc/hosts
- 网络信息系统(NIS)
- 轻权目录访问协议(LDAP)
所有这些差异对应用开发人员是透明的,我们只需调用相关的解析器函数即可
IPv4 函数学习
域名和地址转换
gethostbyname
执行对 A 记录的查询,返回 IPv4 地址:
#include <netdb.h>
struct hostent *gethostbyname(const char * hostname);
// hostent:
struct hostent {
char *h_name; // 正式主机名
char **h_aliases; // 别名s
int h_addrtype; // AF_INET
int h_length; // 4 (32位IP地址)
char **h_addr_list; // IP地址s
}
错误情况
发生错误时,不设置 errno 变量,而是将全局整型变量 h_errno 设置为在头文件 netdb.h 中定义的如下常量之一:
- HOST_NOT_FOUND
- TRY_AGAIN
- NO_RECOVERY
- NO_DATA(等同于 NO_ADDRESS):表明主机在,但是没有 A 记录
多数解析器提供名为 hstrerror 函数,可以将某个 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)
{
// 错误信息
err_msg("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:
err_ret("unknown address type");
break;
}
}
exit(0);
}
[root@centos-5610 names]# ./hostent ethy.cn www.ethy.cn smtp.ethy.cn mail.ethy.cn
official hostname: ym.163.com
alias: ethy.cn
address: 117.147.199.37
gethostbyname error for host: www.ethy.cn: Unknown host
official hostname: cli.ym.ntes53.netease.com
alias: smtp.ethy.cn
alias: smtp.ym.163.com
address: 101.71.155.42
gethostbyaddr
与上一个的功能正好相反,查询 PTR 记录
#inlcude <netdh.h>
struct hostent *gethostbyaddr(const char *addr,
socklen_t len, // 对于 IPv4 为4
int family); // AF_INET
服务和端口转换
/etc/services
文件中保存了许多知名服务的端口和服务名称的映射,如下:
time 37/tcp timserver
time 37/udp timserver
rlp 39/tcp resource # resource location
rlp 39/udp resource # resource location
nameserver 42/tcp name # IEN 116
nameserver 42/udp name # IEN 116
nicname 43/tcp whois
nicname 43/udp whois
tacacs 49/tcp # Login Host Protocol (TACACS)
tacacs 49/udp # Login Host Protocol (TACACS)
re-mail-ck 50/tcp # Remote Mail Checking Protocol
re-mail-ck 50/udp # Remote Mail Checking Protocol
domain 53/tcp # name-domain server
domain 53/udp
whois++ 63/tcp whoispp
whois++ 63/udp whoispp
getservbyname
#include <netdb.h>
struct servent *getservbyname(const char* servname, const char *protoname);
// servent
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
}
几个案例:
getservbyname("domain", "udp");
getservbyname("ftp", "tcp");
getservbyname("ftp", NULL);
getesrvbyname("ftp", "udp");
如果没有指定协议,则会自动匹配(一般来说同一服务的 TCP 和 UDP 端口是相同的),但是如果指定的协议没有,则会报错
getservbyport
#include <netdb.h>
struct servent *getservbyport(int port, const char *protoname);
其中 port 参数必须为网络字节序,例如:
getservbyport(htons(53), "udp");
相同的端口上,不同的协议可能有不同的服务!
时间服务客户端改进
可以通过上面所学对时间服务的客户端进行改进:
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)
err_quit("usage: daytimetcpcli1 <hostname> <service>");
printf("%s:%s\n", argv[1], argv[2]);
// 获取域名对应的地址
if ((hp = gethostbyname(argv[1])) == NULL) {
// 获取失败, 猜测可能是用户输入了IP地址,所以进行转换
// 将 IP 地址从点分十进制转换为32位二进制
if (inet_aton(argv[1], &inetaddr) == 0) {
// 失败了
err_quit("hostname error for %s: %s", argv[1], hstrerror(h_errno));
} else {
// 保存
inetaddrp[0] = &inetaddr;
inetaddrp[1] = NULL;
pptr = inetaddrp;
}
} else {
// 直接转换出来的就是32位二进制
pptr = (struct in_addr**)hp->h_addr_list;
}
// 服务转换
if ((sp = getservbyname(argv[2], "tcp")) == NULL)
err_quit("getservbyname error for %s", argv[2]);
// 循环,对查询得到的所有IP进行访问
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 */
err_ret("connect error");
close(sockfd);
}
if (*pptr == NULL)
err_quit("unable to connect");
// 输出
while ((n = Read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; /* null terminate */
Fputs(recvline, stdout);
}
exit(0);
}
getaddrinfo 函数
getaddrinfo 介绍
优势:
- 这个函数可以处理域名和地址的转换,以及服务和端口的转换
- 返回一个 sockaddr 列表,而不是上面的结构,这些 sockaddr 可以直接使用
- 把协议相关的内容隐藏了起来
#include <netdb.h>
int getaddrinfo(const char *hostname, // 主机名或地址串
const char *service, // 服务名或十进制端口号数串
const struct addrinfo *hints, // 填写对期望结果的暗示
struct addrinfo **result); // 返回的信息保存在这里
参数解释
hints
- hints 可以是一个空指针,也可以是一个指向某个 addrinfo 结构的指针
- 调用者在其中可以填写关于期望返回信息类型的暗示,前四个属性都可以设置
result
- 如果函数返回0,则会更新 result 对应的 addrinfo 链表
- 链表可能有多个项,这取决于 hostname 关联了几个地址,以及是否有不同的协议类型
- 链表是无序的,也就是说 TCP 未必会放在前面
- 返回的 addrinfo 中的信息可以用于 socket 的相关操作,如 connect、sendto、bind
如果 ai_flags 设置了
AI_CANONNAME
,那么返回的第一个 addrinfo 结构的 ai_canonname 指向所查找主机的规范名字
addrinfo 结构
// addrinfo
struct addrinfo {
int ai_flags; // 一些标志位,用来进行特殊的设置
int ai_family; // AF_XXX 如 AF_INET、AF_INET6
int ai_socktype; // SOCK_XXX 如 SOCK_STREAM SOCK_DGRAM
int ai_protocol; // 协议名称,如 IPPROTO_TCP,如果前面两项可以唯一确认,则此项可为0
socklen_t ai_addrlen;
char *ai_canonname;
struct sockaddr *ai_addr;
struct addrinfo *ai_next;
}
下面对各个结构进行详细的解释:
ai_flags
用来设置一些标志位可用的标志值和含义如下:请忽略点符号,这里只是为了方便阅读
标志值 作用 AI_PASSIVE 套接字将用于被动打开(如服务器端) AI_CAN.ON.NAME 返回主机的规范名称,保存在返回的链表的第一项的 ai_canonname
中AI_NUMERIC.HOST 防止任何类型的名字到地址映射,hostname 必须是一个地址串 AI_NUMERIC.SERV 放置任何类型的服务到端口映射,service 必须是十进制端口号数串 AI_V4.MAPPED 如果同时指定 ai_family 为 AF_INET6,又没有 AAAA 记录,就返回 IPv4 对应的 IPv6 地址 AI_ALL 如果同时指定上一项,则返回 IPv6 和 IPv4 对应的 IPv6 地址 AI_ADDR.CONFIG 按照所在主机的配置选择返回地址类型 family、socktype、protocol
已经在注释上说的比较清楚了,他们可以直接被使用来操作 socketai_addrlen
ai_addr 套接字结构的大小ai_canonname
在上表格中提到了,在需要的时候是主机的规范名称ai_addr
指向套接字地址的指针,已经被函数填充好了,且类型自适应ai_next
指向下一位
案例
一个调用的案例:
struct addrinfo hints, *res;
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_INET;
getaddrinfo("freebsd4", "domain", &hints, &res);
一个可能的结果:
最佳实践
在调用 getaddrinfo
时,共有 6 个可以选择的参数组合:
- hostname、service
- ai_flags、ai_family、ai_socktype、ai_protocol
常见的组合方式如下所述
客户端
TCP
- 指定 hostname 和 service
- 返回后,针对返回的所有 IP 地址,逐一调用 socket 和 connect,直到有一个连接成功,或全部失败
UDP
返回后,调用 sendto 或 connect
如果客户能够判定第一个地址看起来不工作,则尝试其余的地址
两种判断方式:
- 已连接,获取到错误信息
- 未连接,等待消息超时
如果客户端清楚套接字的类型,则应该设置 hints 为合适的值
服务器
TCP
- 只指定 service,不指定hostname
- 在 hints 结构中指定 AI_PASSIVE 标志
- TCP 服务器随后调用 socket、bind、listen
UDP
- 调用 socket、bind、recvfrom
如果服务器清楚套接字的类型,则应该设置 hints 为合适的值
返回的 addrinfo 结构的数目
返回的 addrinfo 的数目和暗示信息中 ai_socktype 的对应关系:
gai_strerror
#include <netdb.h>
const char *gai_strerror(int error);
作用:对于 getaddrinfo 的非 0 错误码,将该数值作为参数,输出其对应的错误信息
freeaddrinfo
作用
getaddrinfo
返回的所有存储空间都是动态获取的(比如 malloc),包括:
- addrinfo 结构
- ai_addr 结构
- ai_canonname 字符串
这些存储空间通过 freeaddrinfo
返还给系统
#include <netdb.h>
void freeaddrinfo(struct addrinfo *ai);
注意
- 在头节点上调用,会释放整个 addrinfo 链表
- 注意如果你采用浅拷贝使用了某些属性,则再访问时可能会因为该地址已经被释放而出错
自己封装函数
host_serv
作用:简化 getaddrinfo 的步骤
struct addrinfo* host_serv(const char* host,
const char* serv,
int family,
int socktype) {
int n;
struct addrinfo hints, *res;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_flags = AI_CANONNAME; /* always return canonical name */
hints.ai_family = family; /* AF_UNSPEC, AF_INET, AF_INET6, etc. */
hints.ai_socktype = socktype; /* 0, SOCK_STREAM, SOCK_DGRAM, etc. */
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
return (NULL);
return (res); /* return pointer to first on linked list */
}
tcp_connect
作用:创建一个 TCP 套接字并连接到一个服务器
其步骤和上文最佳实践部分基本一致
int tcp_connect(const char* host, const char* serv) {
int sockfd, n;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("tcp_connect error for %s, %s: %s", host, serv,
gai_strerror(n));
ressave = res;
do {
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd < 0)
continue; /* ignore this one */
if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break; /* success */
Close(sockfd); /* ignore this one */
} while ((res = res->ai_next) != NULL);
if (res == NULL) /* errno set from final connect() */
err_sys("tcp_connect error for %s, %s", host, serv);
freeaddrinfo(ressave);
return (sockfd);
}
时间程序客户端改进
这个类似于前面部分的,只不过把部分步骤封装在 tcp_connect 中了!
int main(int argc, char** argv) {
int sockfd, n;
char recvline[MAXLINE + 1];
socklen_t len;
struct sockaddr_storage ss;
if (argc != 3)
err_quit("usage: daytimetcpcli <hostname/IPaddress> <service/port#>");
sockfd = Tcp_connect(argv[1], argv[2]);
len = sizeof(ss);
Getpeername(sockfd, (SA*)&ss, &len);
printf("connected to %s\n", Sock_ntop_host((SA*)&ss, len));
while ((n = Read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; /* null terminate */
Fputs(recvline, stdout);
}
exit(0);
}
tcp_listen
int tcp_listen(const char* host, const char* serv, socklen_t* addrlenp) {
int listenfd, n;
const int on = 1;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("tcp_listen error for %s, %s: %s", host, serv,
gai_strerror(n));
ressave = res;
do {
listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (listenfd < 0)
continue; /* error, try next one */
Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
break; /* success */
Close(listenfd); /* bind error, close and try next one */
} while ((res = res->ai_next) != NULL);
if (res == NULL) /* errno from final socket() or bind() */
err_sys("tcp_listen error for %s, %s", host, serv);
Listen(listenfd, LISTENQ);
if (addrlenp)
*addrlenp = res->ai_addrlen; /* return size of protocol address */
freeaddrinfo(ressave);
return (listenfd);
}
时间程序服务器端改进
用 tcp_listen 代替部分步骤
int main(int argc, char** argv) {
int listenfd, connfd;
socklen_t len;
char buff[MAXLINE];
time_t ticks;
struct sockaddr_storage cliaddr;
if (argc != 2)
err_quit("usage: daytimetcpsrv1 <service or port#>");
listenfd = Tcp_listen(NULL, argv[1], NULL);
for (;;) {
len = sizeof(cliaddr);
connfd = Accept(listenfd, (SA*)&cliaddr, &len);
printf("connection from %s\n", Sock_ntop((SA*)&cliaddr, len));
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
Close(connfd);
}
}
再次改进
上述代码有一个问题:
- tcp_listen 的第一个参数是 NULL
- 而且 tcp_listen 内部指定的地址族为 AF_UNSPEC
- 两者结合可能导致 getaddrinfo 返回非期望地址族的套接字地址结构
用一个小技巧,可以指定,使用 IPv6 还是 IPv4:
int main(int argc, char** argv) {
int listenfd, connfd;
socklen_t len, addrlen;
char buff[MAXLINE];
time_t ticks;
struct sockaddr_storage cliaddr;
if (argc == 2)
listenfd = Tcp_listen(NULL, argv[1], &addrlen);
else if (argc == 3)
listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
else
err_quit("usage: daytimetcpsrv2 [ <host> ] <service or port>");
for (;;) {
len = sizeof(cliaddr);
connfd = Accept(listenfd, (SA*)&cliaddr, &len);
printf("connection from %s\n", Sock_ntop((SA*)&cliaddr, len));
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
Close(connfd);
}
}
测试案例:
[root@centos-5610 names]# ./daytimetcpsrv2 0::0 daytime
connection from [fe80::5054:ff:fe4d:77d3]:37428
[root@centos-5610 names]# ./daytimetcpsrv2 0.0.0.0 daytime
connection from 10.0.2.15:52178
udp_client
这个套接字地址结构的大小在 lenp 中返回,不允许是一个空指针(而TCP允许),因为 sendto 和 recvfrom 调用都需要直到套接字地址结构的长度
int udp_client(const char* host,
const char* serv,
SA** saptr,
socklen_t* lenp) {
int sockfd, n;
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("udp_client error for %s, %s: %s", host, serv,
gai_strerror(n));
ressave = res;
do {
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd >= 0)
break; /* success */
} while ((res = res->ai_next) != NULL);
if (res == NULL) /* errno set from final socket() */
err_sys("udp_client error for %s, %s", host, serv);
*saptr = Malloc(res->ai_addrlen);
memcpy(*saptr, res->ai_addr, res->ai_addrlen);
*lenp = res->ai_addrlen;
freeaddrinfo(ressave);
return (sockfd);
}
协议无关时间获取客户程序(UDP)
这里协议无关指的是 IPv4 or IPv6
int main(int argc, char** argv) {
int sockfd, n;
char recvline[MAXLINE + 1];
socklen_t salen;
struct sockaddr* sa;
if (argc != 3)
err_quit("usage: daytimeudpcli1 <hostname/IPaddress> <service/port#>");
sockfd = Udp_client(argv[1], argv[2], (void**)&sa, &salen);
printf("sending to %s\n", Sock_ntop_host(sa, salen));
Sendto(sockfd, "", 1, 0, sa, salen); /* send 1-byte datagram */
n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = '\0'; /* null terminate */
Fputs(recvline, stdout);
exit(0);
}
udp_connect
- 由于 connect 会保存相关的端口和端口信息,所以我们只需要知道返回的描述符就可以了
- 和 tcp_connect 不同,UDP 的错误在发送一个数据报才能知晓(没有三次握手的过程)
int main(int argc, char** argv) {
int sockfd, n;
char recvline[MAXLINE + 1];
socklen_t salen;
struct sockaddr* sa;
if (argc != 3)
err_quit("usage: daytimeudpcli1 <hostname/IPaddress> <service/port#>");
sockfd = Udp_client(argv[1], argv[2], (void**)&sa, &salen);
printf("sending to %s\n", Sock_ntop_host(sa, salen));
Sendto(sockfd, "", 1, 0, sa, salen); /* send 1-byte datagram */
n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = '\0'; /* null terminate */
Fputs(recvline, stdout);
exit(0);
}
udp_server
int main(int argc, char** argv) {
int sockfd, n;
char recvline[MAXLINE + 1];
socklen_t salen;
struct sockaddr* sa;
if (argc != 3)
err_quit("usage: daytimeudpcli1 <hostname/IPaddress> <service/port#>");
sockfd = Udp_client(argv[1], argv[2], (void**)&sa, &salen);
printf("sending to %s\n", Sock_ntop_host(sa, salen));
Sendto(sockfd, "", 1, 0, sa, salen); /* send 1-byte datagram */
n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = '\0'; /* null terminate */
Fputs(recvline, stdout);
exit(0);
}
和上面 tcp_server 一样,可以通过那个小技巧来决定使用 IPv4 还是 IPv6
getnameinfo
- 是
getaddrinfo
的互补函数 - 以一个套接字地址为参数,返回描述其中的主机的一个字符串和描述其中的服务的另一个字符串
- 协议无关,函数内部自行处理
#include <netdb.h>
int getnameinfo(const struct sockaddr *sockaddr, socklen_t addrlen,
char *host, sockelne_t hostlen, // 调用者预先分配
char *serv, socklent_t servlen, // 调用者预先分配
int flags);
flags:
常值 | 说明 | 备注 |
---|---|---|
NI_DGRAM | 数据报服务 | 如果知道是UDP,则应设置,以免部分服务端口的冲突 |
NI_NAME.REQD | 若不能从地址解析出名字则返回错误 | |
NI_NO.FQDN | 只返回FQDN的主机名部分 | 如a.foo.com,将截断为a |
NI_NUMERIC.HOST | 以数串格式返回主机字符串 | 不要调用 DNS, |
NI_NUMERIC.SCOPE | 以数串格式返回范围标识字符串 | |
NI_NUMERIC.SERV | 以数串格式返回服务字符串 | 服务器通常应该设置这个标识 |
可重入函数
定义
可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入 OS 调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如 全局变量区, 中断向量表 等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
在使用时需要注意:
gethostbyname
、gethostbyaddr
、getservbyname
、getservbyport
都是不可重入的,因为它们都返回指向同一个静态结构的指针inet_pton
、inet_ntop
总是可重入的因为历史原因,
inet_ntoa
是不可重入的,不过部分实现提供了使用线程特定数据的可重入版本getaddrinfo
可重入的前提是由它调用的函数都可重入,这就是说,它应该调用可重入版本的 gethostbyname 和 getservbynamegetnameinfo
可重入的前提是由它调用的函数都可重入,这就是说,它应该调用可重入版本的 gethostbyaddr 和 getservbyporterrno
在每一个进程各有一个副本,但是多线程下也会发生被其他线程修改的情况
解决方案
不使用函数的不可重入版本
就 errno 例子而言,可以使用类似如下的代码进行避免:
void sig_alrm(int signo) {
int errno_save;
errno_save = errno;
if (write( ... ) != nbytes) {
fprintf(stderr, "Errno = %d\n", errno);
}
errno = errno_save;
}
可重入版本
gethostbyname_r
gethostbyaddr_r
具体描述暂略
本文没有提到的
- 作废的IPv6地址解析函数
- 其他网络相关信息
由于此两章暂时用不到,故略
Full Qualified Domain Name ︎
《Unix 网络编程》11:名字和地址转换的更多相关文章
- UNIX网络编程读书笔记:地址操纵函数
地址格式转换函数:它们在ASCII字符串(人们比较喜欢用的格式)与网络字节序的二进制值(此值存于套接口地址结构中)间转换地址. 1.inet_aton.inet_addr.inet_ntoa inet ...
- UNP总结 Chapter 11 名字与地址转换
本章讲述在名字和数值地址间进行转换的函数:gethostbyname和gethostbyaddr在主机名字与IP地址间进行转换,getservbyname和getservbyport在服务器名字和端口 ...
- 【UNIX网络编程(一)】套接字地址结构、网络字节顺序和地址转换功能
介绍:应该用在网络编程实现每个套接字地址结构.所以主套接字地址结构后前提网络计划编制,地址结构可以在两个方向上发送:从工艺到内核和内核处理.构中的二进制值之间进行转换. 大多数套接字函数都须要一个指向 ...
- UNIX网络编程——套接字选项(心跳检测、绑定地址复用)
/* 设置套接字选项周期性消息检测连通性 心跳包. 心博.主要用于长连接. * 参数:套接字, 1或0开启, 首次间隔时间, 两次间隔时间, 断开次数 */ void setKeepAlive( in ...
- UNIX网络编程-基本API介绍(二)
参考链接:http://www.cnblogs.com/riky/archive/2006/11/24/570713.aspx 1.getsockname和getpeername getsocknam ...
- Unix网络编程--卷一:套接字联网API
UNIX网络编程--卷一:套接字联网API 本书面对的读者是那些希望自己编写的程序能够使用成为套接字(socket)的API进行彼此通信的人. 目录: 0.准备环境 1.简介 2.传输层:TCP.UD ...
- 《Unix网络编程卷1:套接字联网API》读书笔记
第一部分:简介和TCP/IP 第1章:简介 第2章:传输层:TCP.UDP和SCTP TCP:传输控制协议,复杂.可靠.面向连接协议 UDP:用户数据报协议,简单.不可靠.无连接协议 SCTP:流控制 ...
- 【LINUX/UNIX网络编程】之使用消息队列,信号量和命名管道实现的多进程服务器(多人群聊系统)
RT,使用消息队列,信号量和命名管道实现的多人群聊系统. 本学期Linux.unix网络编程的第三个作业. 先上实验要求: 实验三 多进程服务器 [实验目的] 1.熟练掌握进程的创建与终止方法: 2 ...
- UNIX网络编程---传输层:TCP、UDP、SCTP(二)
UNIX网络编程----传输层:TCP.UDP.SCTP 一.概述 本章的焦点是传输层:包括TCP.UDP.和SCTP(流控制传输协议).SCTP是一个较新的协议,最初设计用于跨因特网传输电话信令. ...
随机推荐
- 一个很好用的 vue-picker组件
vue-picker a picker componemt for vue2.0 走了一圈 github 都没有找到自己想要的移动端的 vue-picker的组件,于是自己就下手,撸了一个出来,感受下 ...
- 论文阅读-Clustering temporal disease networks to assist clinical decision support systems in visual analytics of comorbidity progression
一.问题描述: 二.相关工作: 三.方法描述: 四.实验及结果
- Android:Unable to find explicit activity class报错
错误:Unable to find explicit activity class 原因:没有给activity在AndroidManifest.xml中注册 解决办法: 在AndroidManife ...
- mysql基本操作1
数据库的分类 --1.关系型数据库-----用"表"保存数据,相关数据存入一张表中 --2.非关系型数据库-----键值数据库-----对象数据库 ###主流关系型数据库-Or ...
- webpack打包学习
从上图我们可以看出,webpack 可以将多种静态资源 js.css.sass文件等转换成一个静态文件,以此可以减少页面的请求,从而提高浏览器响应速度 1.安装开发依赖包 npm install we ...
- datasets数据读取器
#切分数据集 img_dir = train_parameters['img_dir'] file_name = train_parameters['file_name'] df = pd.read_ ...
- SpringMVC-自定义转换器
1.定义转换器类实现Converter接口 import org.springframework.core.convert.converter.Converter; import java.text. ...
- 制作Unity中的单位血条
本文章用于记录Unity的学习过程,如有疑问,欢迎交流. 1.血条的显示 在Unity场景中创建空物体,然后新建两个Image(图片),当然只用一个也行,一个作为填充来显示血量,一个作为血条的外框. ...
- 省掉80%配置时间,这款Mock神器免费又好用
前端的痛苦 作为前端,最痛苦的是什么时候? 每个迭代,需求文档跟设计稿都出来了,静态页面唰唰两天就做完了.可是做前端又不是简单地把后端吐出来的数据放到页面上就完了,还有各种前端处理逻辑啊. 后端接口还 ...
- Vue 生产环境(production) 打开 调试工具的方法
总所周知,在production生产环境下,点击vue插件会显示, 并且按F12是没办法使用调试工具的,这样非常不方便 其实是有一个办法的,那就是复制下面的js,然后在控制台执行 代码中已经写清楚执行 ...