目录
一,字节序
字节序是由于不同的主处理器和操作系统,对大于一个字节的变量在内存中的存放顺序不同而产生的。字节序通常有大端字节序和小端字节序两种分类
1,大端字节序和小端字节序
字节序是由于CPU和OS对多字节变量的内存存储顺序不同而产生的。
1.1 字节序介绍
例如一个16位的整数,它由两个字节构成,在有的系统上会将高字节放在内存的低地址上,而有的系统上则将高字节放在内存的高地址上,所以存在字节序的问题。
- 小端字节序(Little Endian,LE):在表示变量的内存的起始地址存放低字节,高字节顺序存放。低字节在低地址,高字节在高地址。
- 大端字节序(Big Endian,BE):在表示变量的内存地址的起始地址存放高字节,低字节顺序存放。低字节在高地址,高字节在低地址。
1.2 字节序的例子
#include <stdio.h>
typedef union
{
unsigned short value;
unsigned char byte[2];
}to;
int main()
{
to typeorder;
typeorder.value = 0xabcd;
//小端字节序检查
if ((typeorder.byte[0] == 0xcd) && (typeorder.byte[1] == 0xab))
{
printf("Little Eadian byte order, byte[0]:0x%x, byte[1]:0x%x\n",
typeorder.byte[0], typeorder.byte[1]);
}
//大端字节序检查
if ((typeorder.byte[0] == 0xab) && (typeorder.byte[1] == 0xcd))
{
printf("Big Eadian byte order, byte[0]:0x%x, byte[1]:0x%x\n",
typeorder.byte[0], typeorder.byte[1]);
}
return 0;
}
2,字节序转换函数
网络字节序是指多字节变量在网络传输时的表示方法,网络字节序采用大端字节序的表示方法。这样小端字节序的系统通过网络传输变量的时候,需要进行字节序的转换,大端字节序的变量则不需要进行转换。
2.1 字节序转换函数
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //主机字节序到网络字节序的长整型转换
uint16_t htons(uint16_t hostshort); //主机字节序到网络字节序的短整型转换
uint32_t ntohl(uint32_t netlong); //网络字节序到主机字节序的长整型转换
uint16_t ntohs(uint16_t netshort); //网络字节序到主机字节序的短整型转换
函数传入的变量位需要转换的变量,返回值为转换后的值
3,字节序转换的例子
该例子是对16位数值和32位数值进行自己序转换,每种类型的数值进行两次转换,最后打印结果。
#include <stdio.h>
#include <arpa/inet.h>
#define BITS16 16
#define BITS32 32
//16位字节序转换的结构
typedef union {
unsigned short value;
unsigned char byte[2];
}to16;
//32位字节序转换的结构
typedef union{
unsigned int value;
unsigned char byte[4];
}to32;
void showValue(unsigned char *begin, int flag)
{
int num = 0;
int i = 0;
if (flag == BITS16)
{
num = 2;
}
else if(flag == BITS32)
{
num = 4;
}
for (i=0; i<num; i++)
{
printf("%x ", *(begin+i));
}
printf("\n");
}
int main(int argc, char *argv[])
{
to16 v16_orig, v16_turn1, v16_turn2;
to32 v32_orig, v32_turn1, v32_turn2;
v16_orig.value = 0xabcd;
v16_turn1.value = htons(v16_orig.value); //第一次转换
v16_turn2.value = htons(v16_turn1.value); //第二次转换
v32_orig.value = 0x12345678;
v32_turn1.value = htonl(v32_orig.value); //第一次转换
v32_turn2.value = htonl(v32_turn1.value); //第二次转换
printf("16 host to network byte order change\n");
printf("\torig\t");
showValue(v16_orig.byte, BITS16);
printf("\t1 times:");
showValue(v16_turn1.byte, BITS16);
printf("\t2 times:");
showValue(v16_turn2.byte, BITS16);
printf("32 host to network byte order change\n");
printf("\torig\t");
showValue(v32_orig.byte, BITS32);
printf("\t1 times:");
showValue(v32_turn1.byte, BITS32);
printf("\t2 times:");
showValue(v32_turn2.byte, BITS32);
return 0;
}
二,字符串IP地址和二进制IP地址的转换
1,inet_xxx()函数
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp); //将点分四段式的IP地址转为地址结构in_addr值
in_addr_t inet_addr(const char *cp); //将字符串转换为in_addr值
in_addr_t inet_network(const char *cp); //字符串地址的网络部分转为in_addr类型
char *inet_ntoa(struct in_addr in); //将in_addr结构地址转为字符串
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host); //将网络地址和主机地址合成为IP地址
in_addr_t inet_lnaof(struct in_addr in); //获得地址的主机部分
in_addr_t inet_netof(struct in_addr in); //获得地址的网络部分
1.1 inet_aton()函数
inet_aton()函数将在cp中存储的点分十进制字符串类型的IP地址,转换为二进制的IP地址,转换后的值保存在指针inp指向的结构struct in_addr中。当转换成功时返回值为非0,当传入的地址非法时,返回值为0。
1.2 inet_addr()函数
inet_addr()函数将cp中存储的点分十进制字符串类型的IP地址转换为二进制的IP地址,IP地址是以网络字节序表达的。如果输入的参数非法,返回值为INADDR_NONE(通常为-1),否则返回为转换后的IP地址。
不能使用该函数转换IP地址255.255.255.255
1.3 inet_network()函数
inet_network()函数将cp中存储的点分十进制字符串类型的IP地址,转换为二进制的IP地址,IP地址是以网络字节序表达的。当成功时返回32位表示IP地址,失败时返回值为-1。
1.4 inet_ntoa()函数
inet_ntoa()函数将一个参数in所表示的Internet地址结构转换为点分十进制的4段式字符串IP地址,其形式为a.b.c.d。返回值为转换后的字符串指针,此内存区域为静态的,有可能会被覆盖,因此函数并不是线程安全的。
1.5 inet_makeaddr()函数
一个主机的IP地址分为网络地址和主机地址,inet_makeaddr()函数将主机字节序的网络地址net和主机地址host合并成一个网络字节序的IP地址。
1.6 inet_lnaof()函数
inet_lnaof()函数返回IP地址的主机部分
1.7 inet_netof()函数
inet_netof()函数返回IP地址的网络部分
2,inet_pton和inet_ntop()函数
inet_pton和inet_ntop()函数是一套安全的协议无关的地址转换函数。所谓的“安全”是对于inet_aton()函数的不可重入性来说。这两个函数都是可以重入的,并且这些函数支持多种地址类型,包括IPv4和IPv6
2.1 inet_pton()函数
该函数将字符串类型的IP地址转换为二进制类型
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
- af:表示网络类型的协议族,在IPv4下的值为AF_INET
- src:表示需要转换的字符串
- dst:指向转换后的结果,在IPv4下,dst指向结构struct in_addr的指针
- 返回值:当函数的返回值为-1时,通常是由于af所指定的协议族不支持造成的,当函数的返回值为0时,表示src指向的值不是合法的IP地址,但函数的返回值为正值时,表示转换成功
2.2 inet_ntop()函数
该函数将二进制的网络IP地址转换为字符串
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
- af:表示网络类型的协议族,在IPv4下的值为AF_INET
- src:表示需要转换的二进制IP地址,在IPv4下,src指向结构struct in_addr的指针
- dst:指向保存结果缓冲区的指针
- cnt:dst缓冲区的大小
- 返回值:inet_ntop()函数返回一个指向dst的指针。当发生错误时,返回NULL。当af设定的协议族不支持时,errno设置为EAFNOSUPPORT;当dsf缓冲区过小的时候error的值为ENOSPC
3,inet_pton()和inet_ntop()的例子
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#define ADDRLEN 16
int main(int argc, char *argv[])
{
struct in_addr ip;
char IPSTR[] = "192.168.1.1"; //网络地址字符串
char addr[ADDRLEN]; //保存转换后的IP地址
const char* str = NULL;
int err = 0;
//测试函数inet_pton
err = inet_pton(AF_INET, IPSTR, &ip);
if (err > 0)
{
printf("inet_pton:ip, %s value is:0x%x\n", IPSTR, ip.s_addr);
}
//测试函数inet_ntop
ip.s_addr = htonl(192<<24|168<<16|12<<8|255); //192.168.12.255
str = (const char*)inet_ntop(AF_INET, (void*)&ip, (char*)&addr[0], ADDRLEN);
if (str)
{
printf("inet_ntop:ip, 0x%x is %s\n", ip.s_addr, str);
}
return 0;
}
三,套接字描述符判定函数issockettype()
套接字文件描述符从形式上与通用文件描述符没有区别,判断一个文件描述符是否是一个套接字描述符可以通过如下的方法实现:先调用函数fstat()获得文件描述符的模式,然后将模式的S_IFMT部分与标识符S_IFSOCK比较,就可以知道一个文件描述符是否为套接字描述符
1,套接字文件描述符判定的例子
issockettype()函数先获得描述符的状态,保存在变量st中,将st的成员st_mode与S_IFMT进行与运算后获取文件描述符的模式。判断上述值是否与S_IFSOCK相等,就可以知道文件描述符是否为套接字文件描述符
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
int issockettype(int fd)
{
struct stat st;
int err = fstat(fd, &st); //获得文件状态
if (err < 0)
{
return -1;
}
if ((st.st_mode & S_IFMT) == S_IFSOCK) //比较是否套接字描述符
{
return 1;
}
else
{
return 0;
}
}
int main(int argc, char *argv[])
{
int ret = issockettype(0); //标准输入
printf("value %d\n", ret);
int s = socket(AF_INET, SOCK_STREAM, 0); //套接字描述符
ret = issockettype(s);
printf("value %d\n", ret);
return 0;
}
四,IP地址与域名之间的相互转换
在实际的应用中,经常有只知道主机的域名而不知道主机名对应的IP地址的情况,而socket的API均为基于IP地址,所以如何进行主机域名和IP地址之间的转换是十分必要的。
1,DNS原理
DNS是"域名系统"的英文缩写,域名系统是一种树形结构,按照区域组成层次性的结构,表示计算机名称和IP地址的对应情况。DNS用于TCP/IP的网络,用比较形象话的友好命名来代替枯燥的IP地址,方便用户记忆,DNS的功能就是在主机的名称和IP地址之间担任翻译工作。
2,获取主机信息的函数
gethostbyname()函数和gethostbyaddr()函数都可以获得主机的信息。gethostbyname()函数通过主机的名称获得主机的信息,gethostbyaddr()函数通过IP地址获得主机的信息
2.1 gethostbyname()函数
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);
- name:要查询的主机名,通常是DNS的域名
- 返回值:一个指向结构struct hostname类型变量的指针,当为NULL时,表示发生错误
2.2 gethostbyaddr()函数
#include <netdb.h>
#include <sys/socket.h>
struct hostent *gethostbyaddr(const void *addr,socklen_t len, int type);
- addr:在IPv4的情况下指向一个struct in_addr的地址结构,用户需要查询主机的IP地址填入到这个参数中
- len:表示第一个参数所指区域的大小,在IPv4情况下为sizeof(struct in_addr),即32位
- type:指定需要查询主机IP地址的类型,在IPv4的情况下为AF_INET
- 返回值:和gethostbyname()相同
gethostbyname()和gethostbyaddr()函数都是不可重入的,函数返回后,要马上将结果取出,否则会被后面的函数调用过程覆盖
3,使用主机名获取主机信息的例子
查询www.baidu.com的信息,并将主机的信息打印出来
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
char host[] = "www.baidu.com";
struct hostent *ht = NULL;
char str[30];
ht = gethostbyname(host);
if (ht)
{
int i = 0;
printf("get the host:%s addr\n", host); //原始域名
printf("name:%s\n", ht->h_name); //名称
printf("type:%s\n", ht->h_addrtype==AF_INET?"AF_INET":"AF_INET6");
printf("length:%d\n", ht->h_length); //IP地址的长度
//打印IP地址
for(i=0;; i++)
{
if(ht->h_addr_list[i] != NULL)
{
printf("IP:%s\n", inet_ntop(ht->h_addrtype, ht->h_addr_list[i], str, 30));
}
else
{
break;
}
}
//打印域名地址
for(i=0;; i++)
{
if(ht->h_aliases[i] != NULL)
{
printf("alias %d:%s\n", i, ht->h_aliases[i]);
}
else
{
break;
}
}
}
return 0;
}
五,协议名称处理函数。
为了方便操作,Linux提供了一组用于查询协议的值及名称的函数
1,xxxprotoxxx()函数
#include <netdb.h>
struct protoent *getprotoent(void); //从协议文档中读取一行
struct protoent *getprotobyname(const char *name); //从协议文件中找到匹配项
struct protoent *getprotobynumber(int proto); //按照协议类型的值获取匹配项
void setprotoent(int stayopen); //设置协议文件打开状态
void endprotoent(void); //关闭协议文件
上面的函数对文件/etc/protocols中的记录进行操作,文件中记录了协议的名称,值和别名等值,与结构struct protoent的定义一致。
struct protoent
{
char* p_name; //协议的官方名称
char* p_aliases; //别名列表
short* p_proto; //协议的值
};
2,使用协议族函数的例子
该例子按照名称查询一组协议的项目,首先用setprotoent(1)打开文件/etc/protocols,然后使用函数getprotobyname()查询函数并显示出来,最后使用函数endprotoent()关闭文件/etc/protocols
#include <netdb.h>
#include <stdio.h>
//显示协议项目的函数
void display_protocol(struct protoent *pt)
{
int i = 0;
if (pt)
{
printf("protocol name:%s,", pt->p_name);
if (pt->p_aliases)
{
printf("alias name:");
while (pt->p_aliases[i])
{
printf("%s ", pt->p_aliases[i]);
i++;
}
}
printf(", value:%d\n", pt->p_proto);
}
}
int main(int argc, char *argv[])
{
int i = 0;
const char *const protocol_name[] = {
"ip",
"icmp",
"igmp",
"tcp",
"udp",
NULL
};
setprotoent(1); //设置协议文件打开状态
while (protocol_name[i] != NULL)
{
//查询协议
struct protoent *pt = getprotobyname((const char*)&protocol_name[i][0]);
if (pt)
{
display_protocol(pt);
}
i++;
}
endprotoent(); //关闭文件
return 0;
}