大小端与字节序位序的关系实测

大端和小端

众所周知小端就是低地址存低位数,高地址存高位数。

如0x01020304 在小端存储中是 0x04 , 0x03, 0x02 ,0x01 (左边低地址右边高地址), 读数从右往左读。

大端反之, 0x01020304则为 0x01, 0x02 ,0x03 , 0x04 (左边低地址右边高地址),读数从左往右读。

看起来好像位序不受影响,影响顺序的最小单位为字节。

字节序测试

typedef struct  {
    unsigned char b0:1, b1:1,b2:1,b3:1,b4:1,b5:1,b6:1,b7:1;
} bit8s;

typedef union {
    unsigned char v;
    bit8s b;
} byte;

typedef union {
    unsigned short v;
    byte c[2];
} byte2;

typedef union {
    unsigned int v;
    byte c[4];
} byte4;

void test_bytes_order(unsigned int num)
{
    byte4 x;
    x.v = num;

    printf("Unsigned Num: %08x\n", x.v);
    int i = 0;
    for (i = 0; i < 4; i++) {
        printf("%p : %02x \n", &x.c[i], x.c[i].v);

    }
    printf("\n");
}

int main()
{
    test_bytes_order(0x01020304);
    return 0;
}

在小端上执行

低地址存低位,高地址存高位

root@ubuntu:~/temp# ./a.out  
Unsigned Num: 01020304
0x7fff603f8804 : 04 
0x7fff603f8805 : 03 
0x7fff603f8806 : 02 
0x7fff603f8807 : 01 

在大端上执行

低地址存高位,高地址存低位

root@debian-mips:~/temp# ./a.out 
Unsigned Num: 01020304
0x7f8b723c : 01 
0x7f8b723d : 02 
0x7f8b723e : 03 
0x7f8b723f : 04 

位序测试

降上述代码稍加修改,增加为输出

void test_bytes_order(unsigned int num)
{
    byte4 x;
    x.v = num;

    printf("Unsigned Num: %08x\n", x.v);
    int i = 0;
    for (i = 0; i < 4; i++) {
        printf("%p : %02x ", &x.c[i], x.c[i].v);
        printf("%d%d%d%d%d%d%d%d\n", x.c[i].b.b0, x.c[i].b.b1,x.c[i].b.b2,x.c[i].b.b3,
                                     x.c[i].b.b4,x.c[i].b.b5,x.c[i].b.b6,x.c[i].b.b7);
    }
    printf("\n");
}

在小端上执行

root@ubuntu:~/temp# ./a.out  
Unsigned Num: 01020304
0x7ffcb76dd074 : 04 00100000
0x7ffcb76dd075 : 03 11000000
0x7ffcb76dd076 : 02 01000000
0x7ffcb76dd077 : 01 10000000

在大端上执行

root@debian-mips:~/temp# ./a.out
Unsigned Num: 01020304
0x7f9b44cc : 01 00000001
0x7f9b44cd : 02 00000010
0x7f9b44ce : 03 00000011
0x7f9b44cf : 04 00000100

可以看出,同一个32位数,在大端和小端机器上不仅有相反的字节序,也有相反的位序。那么就有一个问题,在网络传输中,不同端的这个位序能保证吗?

网络传输测试

UDP服务端代码

#include 
#include 
#include 
#include 
#include 
#include 

#include "end.h"

#define BUFFER_SIZE 1024

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        exit(1);
    }

    int port = atoi(argv[1]);

    int server_fd, client_fd, len, recv_len;
    struct sockaddr_in server_addr, client_addr;
    char buffer[BUFFER_SIZE];

    // 创建socket
    if ((server_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    // 初始化服务器地址结构体
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);

    // 绑定端口
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(1);
    }

    printf("UDP server is listening on port %d...\n", port);
    unsigned int num = 0;
    while(1) {
        len = sizeof(client_addr);

        // 接收消息
        if ((recv_len = recvfrom(server_fd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_addr, &len)) == -1) {
            perror("recvfrom");
            exit(1);
        }

        if (recv_len == 4) {
            num = *(unsigned int *)&buffer[0];
            printf("Receiv a num\n");
            test_bytes_order(num);
            
            num = ntohl(num);
            printf("After ntohl\n");
            test_bytes_order(num);
        }

        //printf("Received message: %s", buffer);

        // 发送响应消息
        if (sendto(server_fd, buffer, recv_len, 0, (struct sockaddr*)&client_addr, len) == -1) {
            perror("sendto");
            exit(1);
        }
    }

    close(server_fd);

    return 0;
}

UDP客户端代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "end.h"

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("Usage: %s   \n", argv[0]);
        return 1;
    }

    const char* server_ip = argv[1];
    int server_port = atoi(argv[2]);
    uint32_t data = strtoul(argv[3], NULL, 16); // 将十六进制字符串转换为无符号整数

    int sockfd;
    struct sockaddr_in server_addr;

    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket creation failed");
        return 1;
    }

    // 设置服务器地址信息
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);
    if (inet_pton(AF_INET, server_ip, &(server_addr.sin_addr)) <= 0) {
        perror("invalid server address");
        return 1;
    }
    
    printf("Send a num\n");
    test_bytes_order(data);
    data = htonl(data);
    printf("After htonl\n");
    test_bytes_order(data);
    
    // 发送数据
    if (sendto(sockfd, &data, sizeof(data), 0, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("sendto failed");
        return 1;
    }

    printf("Data sent successfully.\n");

    // 关闭套接字
    close(sockfd);

    return 0;
}

Makefile

all: server client

server: udpserver.c end.c end.h
        gcc -g udpserver.c end.c -o server

client: udpclient.c end.c end.h
        gcc -g udpclient.c end.c -o client
clean:
        rm -f *.o
        rm -f server client

测试结果

小端机器启动服务,大端机器启动客户端发送数据

大端发送
root@debian-mips:~/temp# ./client 172.0.1.53 5555 0x01020304
Send a num
Unsigned Num: 01020304
0x7fd00fd4 : 01 00000001
0x7fd00fd5 : 02 00000010
0x7fd00fd6 : 03 00000011
0x7fd00fd7 : 04 00000100

After htonl
Unsigned Num: 01020304
0x7fd00fd4 : 01 00000001
0x7fd00fd5 : 02 00000010
0x7fd00fd6 : 03 00000011
0x7fd00fd7 : 04 00000100

Data sent successfully.
小端接收
root@ubuntu:~/temp# ./server  5555
UDP server is listening on port 5555...

Receiv a num
Unsigned Num: 04030201
0x7ffc5497a624 : 01 10000000
0x7ffc5497a625 : 02 01000000
0x7ffc5497a626 : 03 11000000
0x7ffc5497a627 : 04 00100000

After ntohl
Unsigned Num: 01020304
0x7ffc5497a624 : 04 00100000
0x7ffc5497a625 : 03 11000000
0x7ffc5497a626 : 02 01000000
0x7ffc5497a627 : 01 10000000

发送的字节顺序和接收到的一致。01 02 03 04发出,收到也是01 02 03 04, 经过ntohl转换,获得 04 03 02 01, 即小端的0x01020304。

发送的位序和接收到的位序不一致。发出00000001 ,收到10000000,似乎硬件底层已经帮助完成了位序的转换。

位序完整展示

Number 0x01020304
#大端  0x01      0x02     0x03      0x04
#大端 00000001 00000010 00000011 00000100
#小端 00100000 11000000 01000000 10000000
#小端  0x04      0x03     0x02      0x01

可以看完整位序正好相反,完全镜像。

位结构体的思考

大端和小端,对于基本数字类型不仅字节序不同,位序也不同。

数字在网络传输中可以通过htons, htonl等方法转换字节序;

位序会在硬件层面自动完成转换,如果需要访问某几个位,那需要做额外处理,就像IP头中的版本号和头长度

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u8    ihl:4,
            version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
    __u8    version:4,
            ihl:4;
#else
#error "Please fix "
#endif
    __u8    tos;
    __be16 -tot_len;
    __be16 -id;
    __be16 -frag_off;
    __u8    ttl;
    __u8    protocol;
    __be16 -check;
    __be32 -saddr;
    __be32 -daddr;
};

常见的这个字节 0x45, 4为ipv4, 5为ip header长度, 5x4 = 20 bytes

当主机收到IP包时,包里的每一个字节都已经是当前主机的位序。

大端 01000101, 前 4bits 4 (version), 后 4bits 5 (ihl)

小端 10100010, 前 4bits 5 (ihl), 后 4bits 4 (version).

测试环境相关

大端虚拟机制备

参考了https://blog.csdn.net/gsls200808/article/details/79113864,做了一些简化

在ubuntu虚拟机上安装QEMU

 sudo apt install qemu

下载debian mips镜像

#下载地址:
https://packages.baidu.com/app/qemu-mips/

## https://people.debian.org/~aurel32/qemu/ 已经404,只有百度这里还有资源
#镜像和内核
debian_wheezy_mips_standard.qcow2  
vmlinux-3.2.0-4-4kc-malta 

启动虚拟机

qemu-system-mips -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -nographic

#账号密码
root/root

#修改源
cat /etc/apt/sources.list
deb http://mirrors.aliyun.com/debian-archive/debian/ wheezy main non-free contrib
deb http://mirrors.aliyun.com/debian-archive/debian/ wheezy-proposed-updates main non-free contrib
deb-src http://mirrors.aliyun.com/debian-archive/debian/ wheezy main non-free contrib
deb-src http://mirrors.aliyun.com/debian-archive/debian/ wheezy-proposed-updates main non-free contrib

#apt-get install gcc 即可

虚拟机网络禁ping,但是可以直接TCP/UDP访问出去。

你可能感兴趣的:(学习笔记,网络,大小端,字节序,位序,位结构体)