前言
本篇文章将给大家介绍套接字地址结构和字节操作函数的使用。
一、IPV4套接字地址结构
IPV4套接字地址结构通常也称为"网际套接字地址结构"它以sockaddr_in命名。定义在头文件中。
sin_family:表示协议族,一般为AF_INET。
sin_port:表示端口号,需要以网络字节序存储,通常使用htons()函数进行转换。
sin_addr:表示IP地址,类型为in_addr结构体指针,也需要以网络字节序存储。可以使用inet_aton()或inet_addr()函数进行转换,或者手动设置。
sin_zero:用于填充,保证长度为sockaddr的长度。
/* Structure describing an Internet socket address. */ struct sockaddr_in { __SOCKADDR_COMMON (sin_); in_port_t sin_port; /* Port number. */ struct in_addr sin_addr; /* Internet address. */ /* Pad to size of `struct sockaddr'. */ unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; };
前面的客户端和服务端编程我们都对这个结构体进行设置。这里我们详细讲解一下客户端和服务端是如何设置这个结构体的参数的。
客户端:
sin_family设置为AF_INET代表使用的是IPV4协议。
sin_port代表端口号这里设置为了8888
sin_addr代表客户端要连接服务端的IP地址
servaddr.sin_family = AF_INET; servaddr.sin_port = htons(8888); if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) { printf("inet_pton is err\n"); }
服务端:
服务端和客户端最大的区别就是在s_addr 的设置上。
INADDR_ANY是一个常量,它的值是0,定义在头文件中。它的作用是让套接字可以监听任意IP地址,这通常在多网卡机器或者服务端需要监听多个网络接口时使用。
saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = htonl(INADDR_ANY); saddr.sin_port = htons(8888);
二、通用套接字地址结构
套接字通用地址结构通常用于存储网络地址和端口号等信息,它的定义如下:
struct sockaddr { unsigned short sa_family; // 地址族(Address Family) char sa_data[14]; // 可变长度的地址数据 };
看到之前的编写的程序是如何使用到这个通用套接字地址结构的:
bind(server, (struct sockaddr*)&saddr, sizeof(saddr))
这个saddr我们在编写代码的时候是使用的IPV4的地址结构。在使用bind函数时第二个参数需要指定为套用套接字地址结构,所以这里需要进行一个强制类型转换。
将IPV4套接字地址结构改变为套用套接字地址结构。
为什么要把IPV4地址结构转换为通用套接字地址结构呢?
因为 BSD socket API 可以同时支持 IPv4 和 IPv6 网络协议族,而这些网络协议族的地址结构可能不同。所以需要用 struct sockaddr 结构体体现通用性,便于能够适配多个协议族的网络地址结构。
三、字节操作函数
1.网络字节序主机字节序转换函数
htons和htonl都是指网络字节序和主机字节序之间的转换函数。在计算机网络中,网络字节序是一种通用的数据表示形式,它用于在互联网上传输数据。不同的计算机体系结构使用不同的字节序,即大端字节序和小端字节序。网络字节序是一种统一的字节序,它与主机字节序不同。因此,在发送和接收网络数据时,必须将数据从主机字节序转换为网络字节序,并在接收数据时将其转换回主机字节序。htons和htonl是用于这种转换的函数。
htons和htonl都是位于arpa/inet.h头文件中的函数。htons函数的作用是将主机字节序的16位整数转换为网络字节序的16位整数。htonl函数的作用是将主机字节序的32位整数转换为网络字节序的32位整数。
代码的使用:
这里是将8888这个端口号从主机字节序转换为了网络字节序。
servaddr.sin_port = htons(8888);
2.地址转换函数
我们熟知的地址一般是以字符串的形式出现的例如:“206.168.112.96”
但是在网络中进行通信时需要把字符串形式的IP地址转换为二进制类型的地址。
将字符串地址转换成二进制地址是为了在网络中让设备能够准确识别和处理数据包,从而实现正确的数据传输。
1.inet_aton、inet_addr、inet_ntoa函数
在man手册中都可以查看到这些函数的具体用法。
inet_aton:
int inet_aton(const char *cp, struct in_addr *inp);
inet_aton函数将一个点分十进制的IP地址转换成一个32位的整数,并将结果存储在指向结构体in_addr的指针中。其操作非常简单,直接输入一个字符串形式的IP地址,指定一个in_addr结构体类型的指针,即可完成ip字符串转整数的转换,如果转换成功则返回非零值(1),否则返回 0。
inet_addr
in_addr_t inet_addr(const char *cp);
inet_addr函数与inet_aton函数类似,同样是把以点分十进制表示的字符串IP地址转换成32位的网络字节序整数。它的返回值是一个in_addr_t类型的整数,若调用失败则返回-1。
inet_ntoa
char *inet_ntoa(struct in_addr in)
inet_ntoa函数则将一个32位二进制整数转换成一个点分十进制IP地址的字符串。其传入一个in_addr结构体类型的数据,即可得到相应的IP字符串,如果转换成功,则返回表示该字符串IP地址的指针,否则返回 NULL。需要注意的是,由于inet_ntoa函数的返回值是一个指向静态存储区的指针,所以多次调用该函数时需要在调用存储之间将其复制到一个缓冲区中。
2.inet_pton、inet_ntop函数
这两个函数是随着IPV6的出现而出现的函数。在IPV4和IPV6都是适用的。
inet_pton:
int inet_pton(int af, const char *src, void *dst);
inet_pton函数用于将IPv4或IPv6地址字符串转换为网络字节序的二进制格式。
其中,af表示地址族(AF_INET代表IPv4地址族,AF_INET6代表IPv6地址族),src是一个字符串,表示待转换的IP地址,dst是一个指向存储转换后结果的内存单元的指针。
inet_ntop:
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
inet_ntop函数则用于将网络字节序的IPv4或IPv6地址转换为可读的地址字符串。
其中,af表示地址族(AF_INET代表IPv4地址族,AF_INET6代表IPv6地址族),src是指向网络字节序的二进制形式的地址的结构体指针,dst是存储转换后结果的缓冲区指针,size表示缓冲区大小。
4.编程实验
这里我们只使用inet_pton和inet_ntop这两个函数进行实验。其他的函数大家可以自己去尝试。
代码:
#include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> #define INET_ADDRSTRLEN 100 int main() { struct in_addr addr; char ip_str[INET_ADDRSTRLEN]; // 将IP地址从字符串转换为二进制形式 if (inet_pton(AF_INET, "192.168.1.1", &addr) != 1) { printf("inet_pton"); } // 将二进制形式的IP地址转换为字符串 const char *ret = inet_ntop(AF_INET, &addr, ip_str, INET_ADDRSTRLEN); if (ret == NULL) { printf("inet_ntop"); } printf("IPv4 address: %s\n", ip_str); return 0; }
上述代码中,inet_pton函数将IPv4地址从字符串转换为二进制形式,inet_ntop函数将二进制形式的IP地址转换为字符串,并将结果存储在ip_str数组中。其中,INET_ADDRSTRLEN是一个宏定义,表示IPv4地址字符串的最大长度。如果转换失败,inet_pton或inet_ntop函数会返回-1,并设置errno值。
总结
本篇文章主要介绍了通用套接字地址结构和IPV4套接字地址结构,以及字节操作函数的使用。