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字节

由上表可见,14字节的sa_data根本无法完全容纳多数协议地址值。因此linux定义了下面新的地址结构体
#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。
       const char *inet_ntop(int af, const void *src,          //与inet_pton相反,成功返回目标存储单元,失败返回NULL,并设置errno
                             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选项

------------------------------------------------

5.12网络信息API

5.12.1 gethostname和gethostbyaddr

5.12.2 getserv和getservbyport

5.12.3 getaddrinfo

5.12.4 getnameinfo

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐