谈谈socket地址API

主机字节序和网络字节序

大端字节序是指整数的高位字节(23-31bit)存储在内存的低地址处,低位字节(0-7bit)存储在内存的高地址处。
小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。
现代PC大多采用小端字节序,因此小端字节序又称为主机字节序。
发送端总是把要发送的数据转化为大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换。因此,大端字节序又称为网络字节序。
即使是同一台机器上的两个进程通信,也需要考虑字节序的问题。
Linux提供四个函数来完成主机字节序和网络字节序的转换:

#include<netint/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort;
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int nettshort;

长整型函数通常用来转换IP地址,短整型函数用来转换端口号。

通用socket地址

socket网络编程接口中表示socket地址的是结构体sockaddr,定义如下

#include<bits/socket.h>
struct sockaddr
{
	sa_family_t sa_family;
	char sa_data[14];
}

sa_family成员是地址族类型(sa_family_t)的变量,地址族类型与协议族类型对应。

协议族地址族
PF_UNIXAF_UNIX
PF_INETAF_INET
PF_INET6AF_INET6

分别代表UNIX本地域协议族、TCP/IPv4协议族、TCP/IPv6协议族。
宏PF_*和AF_*都定义在bits/socket.h头文件中,且具有相同的值,所以二者通常混用。
sa_data成员用于存放socket地址值。但是不同的协议族的地址值具有不同的含义和长度。

PFPF_UNIX文件的路径名,长度可达108字节
PF_INET16bit端口号和32 bit IPv4地址,共6字节
PF_INET616bit端口号,32bit流标识,128bit IPv6地址,32bit 范围ID,共26字节

可见14字节的sa_data根本无法完全容纳多数协议族的地址值。因此,Linux定义了下面这个新的通用socket地址结构体:

#include<bits/socket.h>
struct sockaddr_storage
{
	sa_family_t sa_family;
	unsigned long int __ss_align;
	char __ss_padding[128-sizeof(__ss_align)];
}

这个结构体提供了足够大的空间去存放地址值,而且是内存对齐的(成员__ss_align的作用)。

专用socket地址

上述两个socket地址结构体不好用,需要进行繁琐的位操作。
UNIX本地域协议族使用如下专用socket地址结构体:

#include<sys/un.h>
struct sockaddr_un
{
	sa_family_t sin_family; //地址族,AF_UNIX
	char sun_path[108];//文件路径名
}

TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,它们分别用于IPv4和IPv6:

struct sockaddr_in
{
	sa_family_t sin_family; //地址族,AF_INET
	u_int16_t sin_port;//端口号,用网络字节序表示
	struct in_addr sin_addr; //IPv4地址结构体
}
struct in_addr
{
	u_int32_t s_addr;//IPv4,要用网络字节序表示
}
struct socket_int6
{
	sa_family_t sin6_family;//地址族,AF_INET6
	u_int16_t sin6_port;//端口号,用网络字节序
	u_int32_t sin6_flowinfo; //流信息,应设置为0
	struct in6_addr sin6_addr; //IPv6地址结构体
	u_int32_t sin6_scope_id;//scope ID,尚处于实验阶段
}
struct in6_addr
{
	unsigned char sa_addr[16];//IPv6地址,要用网络字节序表示
}

所有专用socket地址(以及sockaddr_storage)类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制转换即可),因为所有socket编程接口使用的地址参数的类型都是sockaddr。

IP地址转换函数

下面三个函数可用于用点分十进制字符串表示的IPv4地址和用网络字节序整数表示的IPv4地址之间的转换:

#include<arpa/inet.h>
in_addr_t inet_addr(const char * strptr);
int inet_aton(const char* cp,struct in_addr* inp);
char* inet_ntoa(struct in_addr in);

inet_addr函数将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。它失败时返回INADDR_NONE。
inet_aton函数完成和inet_addr同样的功能,但是将转化结果存储于参数inp指向的地址结构中。它成功时返回1,失败则返回0。
inet_ntoa函数用于将网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。该函数内部使用一个静态变量存储转化结果,函数的返回值指向该静态内存,因此inet_ntoa是不可重入的。
下面这对根本更新的函数也能完成前面三个函数同样的功能,并且它们同时适用于IPv4地址和IPv6地址:

#include<arpa/inet.h>
int inet_pton(int af,const char* src,void* dst);
const char* inet_ntop(int af,const void* src,char* dst,socklen_t cnt);

inet_pton函数将用字符串表示的IP地址src(用点分十进制字符串表示的IPv4地址或用十六进制字符串表示的IPv6地址)转换成用网络字节序整数表示的IP地址,并把转换结果存储于dst指向的内存中。其中,af参数指定地址族,可以是AF_INET或者AF_INET6。inet_pton成功时返回1,失败时返回0并设置errno。
inet_ntop函数进行相反的抓换,前三个参数的含义与inet_pton的参数相同,最后一个参数cnt指定目标存储单元的大小。下面两个宏能帮助我们指定这个大小:

#include<netinet/in.h>
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46

inet_ntop成功时返回目标存储单元的地址,失败则返回NULL并设置errno。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值