5.1socket地址API
5.1.1主机字节序和网络字节序
(32位机)cpu累加器一次装载(至少)4字节,即一个整数。字节在内存中的排序就是字节序,分为大端字节序(一个整数的高位字节(23~31)存储在内存的低地址,低位字节(0~7bit)存储在内存的高地址)和小端字节序(与大端相反)。
判断机器字节序:
~/c/tcp-udp/codes/5$ cat 5-1byteorder.cpp
#include <stdio.h>
void byteorder()
{
union
{
short value;
char union_bytes[ sizeof( short ) ];
} test;
test.value = 0x0102;
if ( ( test.union_bytes[ 0 ] == 1 ) && ( test.union_bytes[ 1 ] == 2 ) )
{
printf( "big endian\n" );
}
else if ( ( test.union_bytes[ 0 ] == 2 ) && ( test.union_bytes[ 1 ] == 1 ) )
{
printf( "little endian\n" );
}
else
{
printf( "unknown...\n" );
}
}
int main (){
byteorder();
return 0;
}
现代PC大多采用小端字节序,因此小端字节序又被称为小端字节序。
当格式化数据(如32和16bit短整型数)在两台不同字节的主机之间直接传递时,接受端必然错误地解释之。解决问题的方法是:发送端总把要发送的数据转化成大端字节序数据后再发送,而接受端知道对方传来的数据一定是大端字节序数据,所以可以根据自身采用的字节序决定是否对接受数据进行转换(小端机转换,大端机不转换)。
因此,大端字节序被称为网络字节序。
注意:即使同一台机器上的两个进程(c程序和java程序)通信,也要考虑字节序问题(JAVA虚拟机采用大端字节序)。
linux提供4个转换函数:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); # host to network long
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
这4个函数,长整型通常用来转换IP地址,短整型用来转换端口。
5.1.2 通用socket地址
socket网络编程接口中,表示socket地址的是结构体sockaaddr,其定义如下:
#include <bits/socket.h>
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
}
sa_family成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。常见的协议族(protocol family,也称domain)和对应的协议族如表
表5-1 协议族和地址族的关系
协议族 | 地址族 | 描述 |
PF_UNIX | AF_UNIX | UNIX本地域协议族 |
PF_INET | AF_INET | TCP/IPv4协议族 |
PF_INET6 | AF_INET6 | TCP/IPv6协议族 |
宏PF_*和AP_*都定义在bits/socket.h头文件中,且后者与前者有完全相同的值,所以两者通常混用。
sa_data成员用于存放socket地址值,但是,但是不同的协议族的地址具有不同的含义和长度。如表
表5-2 协议族及其地址值
协议族 | 地址值含义和长度 |
PF_UNIX | 文件的路径名,长度可达108字节 |
PF_INET | 16bit端口号和32bitIPv4地址,共6字节 |
PF_INET6 | 16bit端口号,32bit流标识,128bit IPv6地址,32bit范围ID,共26字节 |
#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成员的作用)。
5.1.3 专用socket地址
上面两个通用socket地址结构体显然不好用,比如设置与获取IP地址和端口号需要执行频繁的位操作。所以linux为各协议族提供了专门的socket地址结构体。
UNIX本地域协议族使用如下结构体:
#include <sys/un.h>
struct sockaddr_un
{
sa_family_t sun_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_int116_t sin_port; // 端口号,要用网络字节序表示
struct in_addr sin_addr; // IPv4地址结构体,见下面
};
struct in_addr
{
u_int32_t s_addr; // IPv4地址,要用网络字节序表示
}
struct sockaddr_in6
{
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。
5.1.4 IP地址转换函数
通常人们习惯用可读性好的字符串表示IP地址,比如用点分十进制字符串表示IPv4地址,用十六进制表示IPv6。但编程中我们需要先把他们转化成整数(二进制)方便使用。而记录日志时相反,下面3个函数用于转换IPv4的点分十进制字符串地址值和和网络字节序的地址值:
#include <arpa/inet.h>
in_addr_t inet_addr(const char * strptr); //点分十进制表示网络字节序,失败返回INADDR_NONE
int inet_aton(const char *cp, struct in_addr *inp); //作用同inet_addr,结果存于inp指向的地址结构中。成功返回1,失败返回0
char *inet_ntoa(struct in_addr in); //将网络字节序整数转化为点分十进制。函数内部用一个静态变量存储转化结果,返回值指向该静态内存,因此inet_ntoa是不可重入的。
char* szValus1=inet_ntoa(<span style="font-family: Arial, Helvetica, sans-serif;">"1.2.3.4"</span>);
char* szValus2=inet_ntoa(<span style="font-family: Arial, Helvetica, sans-serif;">"192.168.7.117"</span><span style="font-family: Arial, Helvetica, sans-serif;">);</span>
printf("addrss1 : %s\n", szValus1);
printf("addrss2 : %s\n", szValus2);
$addrss1 : 192.168.7.117
$addrss2 : 192.168.7.117
下面这对更新函数也能完成和前面三个函数同样的功能,并且他们同样适用于IPv4和IPv6地址:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst); //用字符串表示的IP地址src(用点分十进制表示的IPv4地址或者用十六进制表示的IPv6)转换成网络字节序,并存于dst指向的内存。af:地址族(AF_INET或者AF_INET6)。成功1,失败0,并设置error。
int inet_pton(int af, const char *src, void *dst); //用字符串表示的IP地址src(用点分十进制表示的IPv4地址或者用十六进制表示的IPv6)转换成网络字节序,并存于dst指向的内存。af:地址族(AF_INET或者AF_INET6)。成功1,失败0,并设置error。
const char *inet_ntop(int af, const void *src, //与inet_pton相反,成功返回目标存储单元,失败返回NULL,并设置errno
char *dst, socklen_t size);
char *dst, socklen_t size);
参数size指定目标存储单元大小。下面两个宏帮助指定这个大小:
#include <netinet.h>
#define INET_ADDRSTRLEN 16 // IPv4
#define INET6_ADDRSTRLEN 46 //IPv6
--------------------------------------------------------------------------
5.2创建socket
5.3命名socket
5.4监听socket
5.5接受连接
5.6发起连接
5.7关闭连接
5.8数据读写
5.8.1 TCP数据读写
5.8.2 UDP数据读写
5.8.3 通用数据读写函数
5.9带外标记
5.10地址信息函数
5.11socket选项
------------------------------------------------