😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
⏰发布时间⏰:2024-01-29 20:30:30
⏰复习时间⏰:2024-02-27 11:35:15
本文未经允许,不得转发!!!
目录
🎄一、概述
本文介绍网络编程的一些基础知识。在网络编程中,我们很多时候都需要准备地址结构的,下面先介绍三种需要用到的地址结构以及使用场景。然后介绍网络字节序相关的几个函数。最后再介绍IP地址转换的几个函数。这些知识是网络编程的必备基础知识。
🎄二、套接字地址结构
✨2.1 IPv4套接字地址结构
IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in
命名,定义在<netinet/in.h>
头文件中。结构体定义如下:
#include <netinet/in.h>
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
sin_family
:始终设置为AF_INET
。sin_port
:用网络字节序
表示的端口。sin_addr
:是IP主机地址。结构in_addr
的s_addr
成员是网络字节序
表示的IP地址。in_addr
应被分配一个INADDR_*值(例如,INADDR_ANY),或使用inet_aton、inet_addr、inet_makeaddr库函数进行设置。
使用场景:一般用来构造IPv4的地址结构,然后用于bind、connect等函数。
✨2.2 通用套接字地址结构
通用套接字地址结构以sockaddr
命名,定义在<sys/socket.h>
头文件中。结构体定义如下:
#include <sys/socket.h>
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
sin_family
:- AF_INET 表示 IPv4;
- AF_INET6 表示IPv6;
- AF_UNIX 表示unix;
- AF_APPLETALK 表示ddp;
- AF_PACKET 表示packet;
- AF_X25 表示x25;
- AF_NETLINK 表示netlink。
sa_data
:协议指定的IP地址、端口等信息。
使用场景:一般不会定义实际变量,只用来做强制转换,将协议指定的套接字地址结构强制转换后传递给需要该地址的套接字函数(如:bind、connect、sendto
等)。
使用struct sockaddr*
作为传入参数的函数:bind、connect、sendto
;作为传出参数的函数:accept、recvfrom、getsockname、getpeername
。
为什么需要强制转换?因为不同协议的地址结构不同,但套接字函数却只要一个,所以需要定义一个通用套接字地址结构
指针作为参数来使用各个协议的结构体,所以将结构体地址传给套接字函数时需要强制转换成通用套接字地址结构指针(struct sockaddr*
)。
✨2.3 IPv6套接字地址结构
IPv6套接字地址结构以sockaddr_in6
命名,定义在<netinet/in.h>
头文件中。结构体定义如下:
#include <netinet/in.h>
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
使用场景:一般用来构造IPv6的地址结构,然后用于bind、connect等函数。
🎄三、字节序转换函数
数据在内存中的存储有两种方式:
- 小端字节序(little-endian):低位字节存储在低位地址;
- 大端字节序(big-endian):低位字节存储在高位地址;
网际协议使用大端字节序来传输多字节整数,一般的Linux主机是小端字节序的。这就需要做转换,Linux编程时,可以使用下面4个函数做字节序转换:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
上面几个函数中,h
表示host
,n
表示network
,l
表示long
,s
表示short
。
htonl
:将一个long
型数据,从主机字节序转换到网络字节序;
htons
:将一个short
型数据,从主机字节序转换到网络字节序;
ntohl
:将一个long
型数据,从网络字节序转换到主机字节序;
ntohs
:将一个short
型数据,从网络字节序转换到主机字节序;
🎄四、IP地址转换函数
IPv4
的IP地址转换函数:inet_aton、inet_addr、inet_ntoa;
IPv6
的IP地址转换函数:inet_pton、inet_ntop
✨4.1 inet_aton、inet_addr、inet_ntoa
inet_aton、inet_addr、inet_ntoa这几个函数可以将IPv4
的IP地址在点分十进制字符串
和网络字节序
两种形式之间相互转换。
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
-
inet_aton
:将C字符串
形式IP地址转换为网络字节序
形式的IP地址;- 参数:
cp:传入参数,点分十进制的IP地址字符串;
inp:传出参数,用来存储转换后的32位网络字节序IP地址的二进制值; - 返回值:字符串有效返回1,否则返回0;
- 参数:
-
inet_addr
:将C字符串
形式IP地址转换为网络字节序
形式的IP地址;- 参数:
cp:传入参数,点分十进制的IP地址字符串; - 返回值:字符串有效返回32位网络字节序IP地址的二进制值;无效返回
INADDR_NONE
(数值-1),该函数无法转换255.255.255.255
,因为这个字符串返回也是-1。要避免这个问题的话,可以使用inet_aton
。
- 参数:
-
inet_ntoa
:将一个32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串。- 参数:
in:传入参数,32位的网络字节序二进制IPv4地址; - 返回值:返回:指向一个点分十进制数串的指针
注意,
inet_ntoa
返回值所指向的字符串驻留在静态内存中。如果其他线程也调用该函数,会修改字符串的值。 - 参数:
例子:
#include <arpa/inet.h>
#include <stdio.h>
int main()
{
unsigned int ip;
inet_aton("192.168.1.100", (struct in_addr *)&ip);
printf("inet_aton: ip=%x\n", ip);
ip = inet_addr("192.168.1.100");
printf("inet_addr: ip=%x\n", ip);
struct in_addr ip_addr;
ip_addr.s_addr = ip;
char *ip_str = inet_ntoa(ip_addr);
printf("inet_ntoa: ip=%s\n", ip_str);
return 0;
}
运行结果:
✨4.2 inet_pton、inet_ntop
inet_pton、inet_ntop 是随IPv6出现的新函数,对于IPv4地址和IPv6地址都适用。函数名中p
和n
分别代表表达(presentation)和数值(numeric)。地址的表达格式通常是ASCII字符串,数值格式则是存放到套接字地址结构中的二进制值。
#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 size);
inet_pton
:将src指向的C字符串
形式IP地址转换为数值
形式的IP地址并存放在dst的内存;- 参数:
af:表示地址协议,可以是AF_INET
(IPv4),也可以是AF_INET6
(IPv6);
src:传入参数,点分十进制的IP地址字符串;
dst:传出参数,用来存储转换后的数值形式IP地址结果; - 返回值:若成功则为1,若输入不是有效的表达格式则为0,若出错则为-1。
- 参数:
inet_ntop
:将src指向的数值
形式的IP地址转换为C字符串
形式IP地址并存放在dst的内存;- 参数:
af:表示地址协议,可以是AF_INET
(IPv4),也可以是AF_INET6
(IPv6);
src:传入参数,数值形式IP地址;
dst:传出参数,用来存储转换后的点分十进制字符串
结果;
size:是dst指向的内存的大小,以免发生溢出。如果size太小,不足以容纳表达格式结果(包括结尾的空字符),那么返回一个空指针,并置errno为ENOSPC。size的取值参考下面两个宏定义。#include <netinet/in.h> #define INET_ADDRSTRLEN 16 #define INET6_ADDRSTRLEN 46
- 返回值:若成功则为指向结果的指针,若出错则为NULL。
- 参数:
例子:
#include <arpa/inet.h>
#include <stdio.h>
int main()
{
int i = 0;
struct sockaddr_in6 inet6_addr;
inet_pton(AF_INET6, "fe80::20c:29ff:fe26:c844", &inet6_addr.sin6_addr);
printf("inet_pton: inet6=");
for(i=0; i<sizeof(inet6_addr.sin6_addr.s6_addr); i++)
{
printf("%x", inet6_addr.sin6_addr.s6_addr[i]);
}
printf("\n");
char inet6_str[INET6_ADDRSTRLEN+1]={0,};
const char *ip_str = inet_ntop(AF_INET6, &inet6_addr.sin6_addr, inet6_str, sizeof(inet6_str));
printf("inet_ntop: inet6_str=%s\n", ip_str);
return 0;
}
运行结果:
🎄五、总结
👉本文详细介绍Linux系统网络编程的基础知识,包括套接字地址结构(sockaddr_in、sockaddr、sockaddr_in6)、字节序转函数(htonl、htons、ntohl,ntohs)、IP转换函数(inet_aton、inet_addr、inet_ntoa、inet_pton、inet_ntop)。
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁