【C++高并发服务器WebServer】-11:Socket与IP地址转换

在这里插入图片描述

一、Socket

socket地址其实是一个结构体,封装端口号和IP等信息。后面的socket相关的api中需要使用到这个socket地址。

socket地址的是结构体sockaddr,定义如下:

#include <bits/socket.h>

struct sockaddr {
	sa_family_t sa_family; //sa就是sockaddr的简称
	char sa_data[14]; //一共14个字节
};
typedef unsigned short int sa_family_t; //2哥字节

sa_family 成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。常见的协议族(protocol family,也称 domain)和对应的地址族如下所示:

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

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

在这里插入图片描述
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) ];
};

typedef unsigned short int sa_family_t;

很多网络编程函数诞生早于 IPv4 协议,那时候都使用的是 struct sockaddr 结构体,为了向前兼容,现在sockaddr 退化成了(void *)的作用,传递一个地址给函数,至于这个函数是 sockaddr_in 还是sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

在这里插入图片描述

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

struct sockaddr_in
{
	sa_family_t sin_family; //地址族的类型AF-INET
	in_port_t sin_port; //short类型,端口号,2个字节
	struct in_addr sin_addr; // IP地址
	
	unsigned char sin_zero[sizeof (struct sockaddr) - 	__SOCKADDR_COMMON_SIZE -
	sizeof (in_port_t) - sizeof (struct in_addr)];
	//剩余填充的部分
}


struct in_addr
{
	in_addr_t s_addr; //4个字节的int类型的数据 ip地址
};

typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

所有专用 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 *cp);
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);

上面的三个函数比较旧了,是早期的转换函数。

下面的函数比较新,也能够完成前面3个函数相同的功能:

#include <arpa/inet.h>
// p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst);
	af:地址族: AF_INET AF_INET6
	src:需要转换的点分十进制的IP字符串
	dst:转换后的结果保存在这个里面
	
// 将网络字节序的整数,转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
	af:地址族: AF_INET AF_INET6
	src: 要转换的ip的整数的地址
	dst: 转换成IP地址字符串保存的地方
	size:第三个参数的大小(数组的大小)
	返回值:返回转换后的数据的地址(字符串),和 dst 是一样的
#include <stdio.h>
#include <arpa/inet.h>

int main() {`在这里插入代码片`

    // 创建一个ip字符串,点分十进制的IP地址字符串
    char buf[] = "192.168.1.4";
    unsigned int num = 0;

    // 将点分十进制的IP字符串转换成网络字节序的整数
    inet_pton(AF_INET, buf, &num);
    unsigned char * p = (unsigned char *)&num; //num已经是网络字节序了 大端字节序的形式
    printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3)); //输出 192 168 1 4


    // 将网络字节序的IP整数转换成点分十进制的IP字符串
    char ip[16] = "";
    const char * str =  inet_ntop(AF_INET, &num, ip, 16);
    printf("str : %s\n", str);
    printf("ip : %s\n", str);
    printf("%d\n", ip == str);

    return 0;
}

通过下面的代码,我们可以确认完整的网络字节序是什么样子的。

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

int main() {
    char buf[] = "192.168.1.4";
    unsigned int num = 0;
    inet_pton(AF_INET, buf, &num);
    unsigned char * p = (unsigned char *)&num;

    // 十六进制打印完整的网络字节序
    printf("完整的网络字节序: 0x");
    for (int i = 0; i < sizeof(num); i++) {
        printf("%02X", p[i]); //%02X表示以两位十六进制数格式化输出,不足两位时前面补0
    }
    printf("\n");

    // 十六进制打印每个字节的值
    printf("逐字节打印: ");
    for (int i = 0; i < sizeof(num); i++) {
        printf("%02X ", p[i]);
    }
    printf("\n");

    return 0;
}


    // 二进制打印完整的网络字节序
    printf("二进制完整的网络字节序: ");
    for (int i = 0; i < sizeof(num); i++) {
        print_binary(p[i]);
        if (i < sizeof(num) - 1) {
            printf(" "); // 在字节之间添加空格
        }
    }
    printf("\n");

输出:
完整的网络字节序: 0xC0A80104
逐字节打印: C0 A8 01 04 
二进制完整的网络字节序: 11000000 10101000 00000001 00000100

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值