C语言Socket编程为什么要将sockaddr_in强制转换成sockaddr?

在阅读尹圣雨《TCP/IP网络编程》(C语言示例)的时候,看到将sockaddr_in类型的struct的指针强制转换成了sockaddr类型的指针。

比如hello_client.c:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "arpa/inet.h"
#include "sys/socket.h"

void error_handling(char *message);

int main(int argc,char *argv[]){
    int sock;
    struct sockaddr_in server_address;
    char message[30];
    int str_len;

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        error_handling("socket() error");
    }

    memset(&server_address,0,sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_address.sin_port = htons(atoi("9190"));

    if (connect(sock, (struct sockaddr *) &server_address, sizeof(server_address)) == -1) {
        error_handling("connect() error!");
    }

    str_len = read(sock,message,sizeof(message) -1);
    if (str_len == -1) {
        error_handling("read() error");
    }

    printf("message from server: %s\n", message);
    close(sock);
    return 0;

}

void error_handling(char *message){
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

中的connect调用:

connect(sock, (struct sockaddr *) &server_address, sizeof(server_address))

或者hello_server.c:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "arpa/inet.h"
#include "sys/socket.h"


void error_handling(char *message);

int main(int argc,char *argv[]){
    int server_socket;
    int client_socket;

    // sockaddr_in中的in表示InterNet
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;
    socklen_t client_address_size;

    char message[] = "Hello World!";

    server_socket = socket(PF_INET,SOCK_STREAM,0);
    if (server_socket == -1) {
        error_handling("socket() error");
    }

    memset(&server_address,0,sizeof(server_address));
    server_address.sin_family=AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    //  htons即是host to network short long的缩写,表示将主机字节序转成16位的网络字节序即大端法
    //  atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数
    server_address.sin_port = htons(atoi("9190"));
    if (bind(server_socket, (struct sockaddr *) &server_address, sizeof(server_address)) == -1) {
        error_handling("bind() error");
    }

    if (listen(server_socket, 5) == -1) {
        error_handling("listen() error");
    }

    client_address_size = sizeof(client_address);
    client_socket = accept(server_socket,(struct sockaddr*)&client_address,&client_address_size);
    if (client_socket == -1) {
        error_handling("accept() error");
    }

    write(client_socket, message, sizeof(message));
    close(client_socket);
    close(server_socket);
    return 0;
}

void error_handling(char *message){
    fputs(message,stderr);
    fputc('\n', stderr);
    exit(1);
}

中的bind和accept调用:

bind(server_socket, (struct sockaddr *) &server_address, sizeof(server_address))
accept(server_socket, (struct sockaddr*) &client_address, &client_address_size)

结构体sockaddr_in的定义是:

/*
 * Socket address, internet style.
 */
struct sockaddr_in {
	__uint8_t       sin_len;
	sa_family_t     sin_family;
	in_port_t       sin_port;
	struct  in_addr sin_addr;
	char            sin_zero[8];
};

结构体sockaddr的定义是:

/*
 * [XSI] Structure used by kernel to store most addresses.
 */
struct sockaddr {
	__uint8_t       sa_len;         /* total length */
	sa_family_t     sa_family;      /* [XSI] address family */
	char            sa_data[14];    /* [XSI] addr value (actually larger) */
};

其中的__unit8_t类型是unsigned char(占用1个字节)的类型别名:

typedef unsigned char           __uint8_t;

sa_family_t类型又是__unit8_t的类型别名:

typedef __uint8_t               sa_family_t;

in_port_t类型是__uint16_t(占用2个字节)的别名(typedef unsigned short __uint16_t):

typedef __uint16_t              in_port_t;

in_addr结构体占用4个字节。

这样sockaddr_in和sockaddr两个结构体都占用16个字节。

在C语言中,指针类型的变量是有类型的,它的类型就是它所指向的变量的类型,指针类型的变量的值是一个地址。(理解C语言的指针,有一本好书推荐《征服C指针》前桥和弥著)

比如:

#include <stdio.h>


int main() {
    int a = 34;
    int *int_ptr = &a;
    printf("%d\n", *int_ptr);
    printf("int_ptr   value is address:%p\n", int_ptr);
    printf("int_ptr+1 value is address:%p\n", int_ptr+1);

    long b = 45L;
    long *long_ptr = &b;

    printf("%d\n", *long_ptr);
    printf("long_ptr   value is address:%p\n", long_ptr);
    printf("long_ptr+1 value is address:%p\n", long_ptr+1);

    return 0;
}

运行后输出:

34
int_ptr   value is address:0x16ae73608
int_ptr+1 value is address:0x16ae7360c
45
long_ptr   value is address:0x16ae735f8
long_ptr+1 value is address:0x16ae73600

同样是地址加一,即指针的值加一,指向int类型的指针加一后,地址增加4个字节(0x16ae7360c-0x16ae73608=4),指向long类型的指针加一后,地址增加8个字节(0x16ae73600-0x16ae735f8=8)。

也就是说指向内存地址的指针,如果是int类型指针,编译器会将指针指向的地址后边的4个字节解释为一个int类型值,如果是long类型指针,编译器会将指针指向的地址后边的8个字节解释为一个long类型值。

那么对于16个字节的内存,我们可以将它解释为sockaddr_in或sockaddr。而且通过它们的指针,访问结构体的前面的成员的时候(成员类型占用字节数一样的,比如__uint8_t和sa_family_t都只占用1个字节),编译器生成的代码都可以正确的获取到结构体的成员值。

上面分析了从语法及C语言工作原理的角度来说,是可以将一个结构体的指针强制转换成另一个的结构体(其实,不管结构体占用的内存大小是否相等都可以转换,但是一般都是大结构体向小结构体转换,比较安全,类似Java中的子类可以安全地转换成父类)。

那么为什么C语言的socket编程要这么做呢?

因为除了IP Socket外,还有其他类型的Socket,比如Unix领域的socket,它的socket地址结构体是sockaddr_un:

/*
 * [XSI] Definitions for UNIX IPC domain.
 */
struct  sockaddr_un {
	unsigned char   sun_len;        /* sockaddr len including null */
	sa_family_t     sun_family;     /* [XSI] AF_UNIX */
	char            sun_path[104];  /* [XSI] path name (gag) */
};

可以看到sockaddr_un占用106个字节,并不是16个字节。
还有其他的sockaddr_XX:

在这里插入图片描述

这些sockaddr_XX结构体的前两个字段都是占用1个字节,把它们指向的内存解释成sockaddr,只要只取前两个字节的数据就很安全(不会访问到一些未知内存,或者将内存数据解释成错误的类型),然后根据sa_familiy_t类型的变量的具体的值,就可以知道是哪个具体的sockaddr_XX,有点类似面向对象中的多态。

也就是说Socket网络编程中这么用,就是利用了C语言的指针转换原理,实现了类似面向对象编程的多态特性。


参考资料:

https://www.anycodings.com/1questions/5010325/why-do-we-cast-sockaddrin-to-sockaddr-when-calling-bind

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值