您当前的位置:首页 > 电脑百科 > 网络技术 > 网络知识

Linux网络API - socket地址API

时间:2020-07-28 12:46:26  来源:  作者:

socket最开始的含义是一个IP地址和端口队(ip,port)。它唯一地表示了使用TCP通信的一端。这就是socket地址。

主机字节序和网络字节序

现在CPU的累加器一次都能装载(至少)4字节(这里考虑32位机器,下同),即一个整数。那么这4个字节在内存中排列的顺序将影响它被累加器装载成的整数的值。这就是字节序的问题。

字节序分为大端字节序(big endian)和小端字节序(little endian)。大端字节序是指一个整数的高位字节(23 ~ 31 bit)存储在内存的地址处,低位字节(0~7 bit)存储在内存的高地址处。小端字节序则指整数的高位字节序存储在内存的高地址处,而低位字节序则存在在内存的低地址处。

下面的代码是检查机器的字节序:

#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 endiann");
    }
    else if((test.union_bytes[0] == 2) && (test.union_bytes[1] == 1)){
        printf("little endiann");
    }
    else{
        printf("unknownn");
    }

}

int main(int argc, char const *argv[])
{
    byteorder();
    return 0;
}

当格式化的数据(比如32bit整型数和16bit短型数)在两台使用不同字节序的主机之间传递时,接收端必然错误地解释之。

解决问题的方法是:发送端总是把要发送的数据转化成大端字节序再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。因此大端字节序也称为网络字节序,它给所有接受数据的主机提供了一个正确解释收到的格式化数据的保证。

需要指出的是,即使是同一台机器上的两个进程(比如一个由C语言,另一个JAVA编写)通信,也要考虑字节序的问题(JAVA虚拟机采用大端字节序)。

linux提供了4个函数来完成主机字节序和网络字节序之间的转换。

#include <netinet/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 netshort);

它们的含义很明确,比如htonl表示“host to network long",即将长整型(32bit)的主机字节序转换为网络字节序数据。这四个函数中,长整型函数通常用来转换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)的变量。地址族类型通常与协议族类型对应。

常见的协议族(protocol family,也称domain)和对应的地址族如下表:

Linux网络API - socket地址API

 

宏PF_*和AF_*都定义在bits/socket.h头文件中,且后者与前者有完全相同的值,所以二者通常混用。

sa_data成员用于存放socket地址值。但是不同的协议族的地址值具有不同的含义和长度。如下表所示:

Linux网络API - socket地址API

 

由此可以发现,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地址结构体显然很不好用,比如设置与获取IP地址和端口号就需要执行烦琐的位操作。所以Liunx为各个协议族提供了专门的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 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地址结构体各字段的含义很明确。

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

IP地址转换函数

通常,人们习惯用可读性好的字符串来表示IP地址,比如用点分十进制字符串表示IPv4地址,以及用十六进制字符串表示IPv6地址。但编程中我们需要先把它们转化为整数(二进制数)方能使用。而记录日志则相反,我们要把整数表示的IP地址转化为可读的字符串。

下面3个函数可用于用点分十进制字符串表示的字符串表示的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是不可重入的。

#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char const *argv[])
{
    char ip1[] = "1.2.3.4";
    char ip2[] = "10.194.71.60";
    
    struct in_addr inAddr1;    
    struct in_addr inAddr2;    

    inet_aton(ip1, &inAddr1);
    inet_aton(ip2, &inAddr2);

    char *szValue1 = inet_ntoa(inAddr1);
    char *szValue2 = inet_ntoa(inAddr2);

    printf("address1: %sn", szValue1);
    printf("address2: %sn", szValue2);

    return 0;
}
Linux网络API - socket地址API

不可重入的inet_ntoa函数实验结果

下面这对更新的函数也能完成和前面3和函数一样的功能,并且它们使用适用于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指定目标存储单元的大小。下面两个宏能帮助我们指定这个大小(分别用于IPv4和IPv6):

#include <netinet/in.h>

#define INET_ADDRSTRLEN 16

#define INET6_ADDRSTRLEN 46

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

#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>

int main()
{
    char *ipv4 = "10.0.0.200";
    char *ipv6 = "fe80::4bde:83d8:dbcf:72f3";

    in_addr inAddr4;
    in6_addr inAddr6;

    inet_pton(AF_INET, ipv4, &inAddr4);
    inet_pton(AF_INET6, ipv6, &inAddr6);

    char addr1[INET_ADDRSTRLEN];
    char addr2[INET6_ADDRSTRLEN];

    if(addr1 == inet_ntop(AF_INET, (void *)&inAddr4, addr1, INET_ADDRSTRLEN)){
        printf("truen");
    }

    printf("IPv4 addr: %sn", inet_ntop(AF_INET, (void *)&inAddr4, addr1, INET_ADDRSTRLEN));
    printf("IPv4 addr: %sn", inet_ntop(AF_INET6, (void*)&inAddr6, addr2, INET6_ADDRSTRLEN));

    return 0;
}
Linux网络API - socket地址API

 



Tags:socket地址   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
socket最开始的含义是一个IP地址和端口队(ip,port)。它唯一地表示了使用TCP通信的一端。这就是socket地址。主机字节序和网络字节序现在CPU的累加器一次都能装载(至少)4字节(这里...【详细内容】
2020-07-28  Tags: socket地址  点击:(71)  评论:(0)  加入收藏
▌简易百科推荐
HTTP 报文是在应用程序之间发送的数据块,这些数据块将通过以文本形式的元信息开头,用于 HTTP 协议交互。请求端(客户端)的 HTTP 报文叫做请求报文,响应端(服务器端)的叫做响应...【详细内容】
2021-12-27  程序员蛋蛋    Tags:HTTP 报文   点击:(1)  评论:(0)  加入收藏
一 网络概念:1.带宽: 标识网卡的最大传输速率,单位为 b/s,比如 1Gbps,10Gbps,相当于马路多宽2.吞吐量: 单位时间内传输数据量大小单位为 b/s 或 B/s ,吞吐量/带宽,就是网络的使用率...【详细内容】
2021-12-27  码农世界    Tags:网络   点击:(2)  评论:(0)  加入收藏
1.TCP/IP 网络模型有几层?分别有什么用? TCP/IP网络模型总共有五层 1.应用层:我们能接触到的就是应用层了,手机,电脑这些这些设备都属于应用层。 2.传输层:就是为应用层提供网络...【详细内容】
2021-12-22  憨猪哥08    Tags:TCP/IP   点击:(29)  评论:(0)  加入收藏
TCP握手的时候维护的队列 半连接队列(SYN队列) 全连接队列(accepted队列)半连接队列是什么?服务器收到客户端SYN数据包后,Linux内核会把该连接存储到半连接队列中,并响应SYN+ACK报...【详细内容】
2021-12-21  DifferentJava    Tags:TCP   点击:(9)  评论:(0)  加入收藏
你好,这里是科技前哨。 随着“元宇宙”概念的爆火,下一代互联网即将到来,也成了互联网前沿热议的话题,12月9日美国众议院的听证会上,共和党议员Patrick McHenry甚至宣称,要调整现...【详细内容】
2021-12-17  王煜全    Tags:Web3   点击:(14)  评论:(0)  加入收藏
一、demopublic static void main(String[] args) throws Exception { RetryPolicy retryPolicy = new ExponentialBackoffRetry( 1000, 3);...【详细内容】
2021-12-15  程序员阿龙    Tags:Curator   点击:(20)  评论:(0)  加入收藏
一、计算机网络概述 1.1 计算机网络的分类按照网络的作用范围:广域网(WAN)、城域网(MAN)、局域网(LAN);按照网络使用者:公用网络、专用网络。1.2 计算机网络的层次结构 TCP/IP四层模...【详细内容】
2021-12-14  一口Linux    Tags:网络知识   点击:(30)  评论:(0)  加入收藏
无论是在外面还是在家里,许多人都习惯了用手机连接 WiFi 进行上网。不知道大家有没有遇到过这样一种情况, 明明已经显示成功连接 WiFi,却仍然提示“网络不可用”或“不可上网”...【详细内容】
2021-12-14  UGREEN绿联    Tags:WiFi   点击:(25)  评论:(0)  加入收藏
拉了千兆宽带,买了标称 1300Mbps 的无线路由器,为什么 WiFi 还是跑不满千兆?要回答这个问题,我们先得知道这个 1300Mbps 是怎么来的。开始回答之前先说明一下,这期只讲 802.11ac,...【详细内容】
2021-12-14  Ubiquiti优倍快    Tags:WiFi   点击:(85)  评论:(0)  加入收藏
问题背景IPv6环境下,在浏览器中通过http://[vip:port]访问web业务,提示无法访问此网站,[vip]的响应时间过长。分析过程之前碰到过多次在PC浏览器上无法访问vip的情况,排查方法也...【详细内容】
2021-12-13  云原生知识星球    Tags:网络问题   点击:(27)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条