|
输出主机字节序:
#include "unp.h"
int main(int argc, char **argv)
{
union {
short s;
char c[sizeof(short)];
} un;
un.s = 0x0102;
printf("%s: ", CPU_VENDOR_OS);
if (sizeof(short) == 2) {
if (un.c[0] == 1 && un.c[1] == 2)
printf("big-endian\n");
else if (un.c[0] == 2 && un.c[1] == 1)
printf("little-endian\n");
else printf("unknown\n");
}
else printf("sizeof(short) = %ld\n", sizeof(short));
exit(0);
}
在每个TCP分节中都有16位的端口号和32位的IPv4地址。发送协议栈和接收协议栈必须就这些多字节字段各个字节的传送顺序达成一致。网际协议使用大端字节序来传达这些多字节整数。
在主机字节序和网络字节序(大端)之间使用以下四个函数进行转换:
#include <netinet/in.h>
//返回:网络字节序的值
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
//返回:主机字节序的值
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);
其中,h 代表 host(主机),也就是主机字节序;n 代表 network ,也就是网络字节序;s 代表 short,一般用来转换端口(端口是 2 个字节);l 代表 long,一般用来转换 IP(IP 是 4 个字节)。
示例:
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
//htons
printf(&#34;htons\n&#34;);
unsigned short a = 0x0102;
printf(&#34;a: %x\n&#34;, a);
unsigned short b = htons(a);
printf(&#34;b: %x\n&#34;, b);
//htonl
printf(&#34;htonl\n&#34;);
char buf[4] = {192, 168, 1, 100};
int num = *(int *)buf;
num = htonl(num);
unsigned char *p = (char *)&num;
printf(&#34;%d.%d.%d.%d\n&#34;, *p, *(p + 1), *(p + 2), *(p + 3));
return 0;
}

一些字节操纵的函数:
#include <string.h>
//将目标字节串中指定数目的字节置为0
void bzero(void *dest, size_t nbytes);
//将指定数目的字节从源字节串复制到目标字节串
void bcopy(const void *src, void *dest, size_t nbytes);
//比较两个任意字节串,相同返回0,不同返回非0
int bcmp(const void *ptr1, const void *ptr2,size_t nbytes);
inet_aton、inet_addr和inet_ntoa在点分十进制数串(例如“206.168.112.96”)与它长度为32位的网络字节序二进制值间转换IPv4地址:
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
char *inet_ntoa(struct in_addr inaddr);
第一个函数inet_aton将strptr所指C字符串转换成一个32位的网络字节序二进制值,并通过指针addrptr来存储。若成功则返回1,否则返回0。
inet_addr进行相同的转换,返回值为32位的网络字节序二进制值。该函数存在一个问题:所有 2^{32} 个可能的二进制值都是有效的IP地址(从0.0.0.0到255.255.255.255),但是当出错时,该函数返回INADDR_NONE常值(通常是一个32位均为1的值)。这意味着点分十进制数串255.255.255.255(这是IPv4的有限广播地址)不能由该函数处理,因为它的二进制值被用来指示该函数失败。
inet_ntoa函数将一个32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串。
上面的那三个函数一般不会使用。
inet_pton和inet_ntop函数对于IPv4和IPv6地址都适用:
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, socklen_t len);
函数名中p和n分别代表表达(presentation)即点分十进制数和数值(numeric)即网络字节序。其中 family 参数既可以是 AF_INET,也可以是 AF_INET6。如果以不被支持的地址族作为 family 参数,这两个函数都返回一个错误,并将 error 置为 EAFNOSUPPORT。
第一个函数尝试转换由 strptr 指针所指的字符串,并通过 addrptr 指针存放二进制结果。若成功则返回值为1,否则如果对所指定的family而言输入的字符串不是有效的表达格式,那么返回值为0。
inet_ntop 进行相反的转换,从数值格式(addrptr)转换到表达式格式(strptr)。len 参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区。如果 len 太小,不足以容纳表达格式结果(包括结尾的空字符),那么返回一个空指针,并置 error 为 ENOSPC。其中strptr参数不可以是一个空指针。返回转换后的数据的地址(字符串),和 strptr 是同一个值。
sock_ntop和相关函数
inet_ntop的一个基本问题是:它要求调用者传递一个指向某个二进制地址的指针,而改地址通常包含在一个套接字地址结构中,这就要求调用者必须知道这个结构的格式和地址族。为了使用这个函数,我们必须为IPv4编写如下代码:
struct sockaddr_in addr;
inet_ntop(AF_INET, &addr.sin_addr, str, sizeof(str));
或为IPv6编写如下代码:
struct sockaddr_in6 addr6;
inet_ntop(AF_INET6, &addr6.sin6_addr, str, sizeof(str));
因此我们将自行编写一个名为sock_ntop的函数,它以指向某个套接字地址结构的指针为参数,查看该结构的内部,然后调用适当的函数返回改地址的表达格式:
#include &#34;unp.h&#34;
//返回:若成功则为非空指针,若出错则为NULL
char *sock_ntop(const struct sockaddr *saddr, socklen_t addrlen);
saddr指向一个长度为sddrlen的套接字地址结构。
我们还为操作套接字地址结构定义了其他几个函数,它们将简化我们的代码在IPv4与IPv6之间的移植。
#include &#34;unp.h&#34;
//将通配地址和一个临时端口绑定到一个套接字
//返回:若成功则为0,否则-1
int sock_bind_wild(int sockfd, int family);
//比较两个套接字地址结构的地址部分
//返回:若地址为同一协议族且相同则为0,否则为非0
int sock_cmp_addr(const struct sockaddr *sockaddr1, const struct sockaddr *sockaddr2,
socklen_t addrlen);
//比较两个套接字地址结构的端口部分
//返回:若地址为同一协议族且端口相同则为0,否则为非0
int sock_cmp_port(const struct sockaddr *sockaddr1, const struct sockaddr *sockaddr2,
socklen_t addrlen);
//返回端口号
//返回:若为IPv4或IPv6地址则为非负端口号,否则为-1
int sock_get_port(const struct sockaddr *sockaddr1, socklen_t addrlen);
//把一个套接字地址结构中的主机部分转换成表达格式(不包括端口号)。
//返回:若成功则为非空指针,若出错则为NULL
char *sock_ntop_host(const struct sockaddr *sockaddr1, socklen_t addrlen);
//把一个套接字地址结构中的地址部分置为ptr指针所指的值
void sock_set_addr(const struct sockaddr *sockaddr1, socklen_t addrlen, void *ptr);
//只设置一个套接字地址结构中的端口部分
void sock_set_port(const struct sockaddr *sockaddr1, socklen_t addrlen, int port);
//把一个套接字地址结构中的地址部分置为通配地址
void sock_set_wild(struct sockaddr *sockaddr1, socklen_t addrlen); |
|