linux之socket相关的函数结构体解析inet_addr、gethostbyname、sockaddr和sockaddr_in、getsockopt和setsockopt函数

本文详细介绍了在Linux和Windows环境下使用socket进行网络编程的基础知识,包括socket的创建、sockaddr和sockaddr_in结构体的使用、IP地址转换函数inet_addr()、域名解析函数gethostbyname()、以及socket选项的设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、socket

不管是 Windows 还是 Linux,都使用 socket() 函数来创建套接字。socket() 在两个平台下的参数是相同的,不同的是返回值:
(1)Linux 中的一切都是文件,每个文件都有一个整数类型的文件描述符;socket 也是一个文件,也有文件描述符。使用 socket() 函数创建套接字以后,返回值就是一个 int 类型的文件描述符。
(2)Windows 会区分 socket 和普通文件,它把 socket 当做一个网络连接来对待,调用 socket() 以后,返回值是 SOCKET 类型,用来表示一个套接字。

Linux 下的 socket() 函数

在 Linux 下使用 <sys/socket.h> 头文件中 socket() 函数来创建套接字,原型为:

int socket(int af, int type, int protocol);

参数:

  1. af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。
  2. type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)
  3. protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议
    Linux下创建套接字:
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  //IPPROTO_TCP表示TCP协议

2、sockaddr和sockaddr_in详解

struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址
1、sockaddr
sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了,如下:

struct sockaddr {  
      sa_family_t sin_family;//地址族
      char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
   }; 

2、sockaddr_in
sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下:
在这里插入图片描述
sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。
3、总结
二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。
sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(int argc,char **argv)
{
    int sockfd;
    struct sockaddr_in mysock;

    sockfd = socket(AF_INET,SOCK_STREAM,0);  //获得fd

    bzero(&mysock,sizeof(mysock));  //初始化结构体
    mysock.sin_family = AF_INET;  //设置地址家族
    mysock.sin_port = htons(800);  //设置端口
    mysock.sin_addr.s_addr = inet_addr("192.168.1.0");  //设置地址
    bind(sockfd,(struct sockaddr *)&mysock,sizeof(struct sockaddr); /* bind的时候进行转化 */
    ... ...
    return 0;
}

3、inet_addr()

描述:
  inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值
返回值:
  若无错误发生,inet_addr()返回一个无符号长整型数,其中以适当字节顺序存放Internet地址。如果传入的字符串不是一个合法的Internet地址,如“a.b.c.d”地址中任一项超过255,那么inet_addr()返回INADDR_NONE

4、gethostbyname

函数原型:
  struct hostent* gethostbyname(const char *name);
调用函数成功返回一个hostent结构体

struct hostent
{
  char *h_name;
  char ** h_aliases;
  short h_addrtype;
  short h_length;
  char ** h_addr_list;
};

测试代码

#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/types.h>  
#include<sys/socket.h>
#include<signal.h>
#include<unistd.h>
#include<netdb.h>
#include<string.h>
#include <arpa/inet.h>

int main(int argc,char *argv[])
{
	int optret;
	char *ipname = NULL;
	char ip[50];
	struct hostent *gethost = NULL;
	int file_fd;
	char buf[50];
	char *ptrf;
	char *ptrb;
	int n;
	char fptr[50];
	const char *ptr = NULL;
	struct in_addr  inAddr;
	while((optret = getopt(argc,argv,"i:")) != -1)
	{
		switch(optret)
		{
			case 'i':
				ipname = optarg;
				printf("option = i,the ipname is:%s\n",optarg);
				break;
		}
	}
	gethost = gethostbyname(ipname);
	if (NULL == gethost)
	{
		perror("get ipaddr fail!");
		return -1;
	}
	printf("get host successfully!\n");
	memset(ip,0,sizeof(ip));
	ptr = inet_ntop(gethost->h_addrtype,gethost->h_addr_list[0],ip,sizeof(ip));
	printf("DNS ip is:%s\n",ptr);

	system("ping studio.iot-yun.com -c 2 >> /home/deepin/APUE/malunkun/test/.domain.log");
	file_fd=open("/home/deepin/APUE/malunkun/test/.domain.log",O_RDONLY,644);
	if (file_fd < 0)
	{
		perror("open file fail!\n");
	}
	read(file_fd,buf,sizeof(buf));
	close(file_fd);
	ptrf = strstr(buf,"(");
	ptrf += 1;
	ptrb = strstr(buf,")");
	n = strlen(ptrf)-strlen(ptrb);
	strncpy(fptr,ptrf,n);
	printf("ping ip is:%s\n",fptr);

	return 0;
}

输出:
在这里插入图片描述
使用输出的IP地址去访问网站,发现显示的也是百度
在这里插入图片描述

5、getsockopt和setsockopt函数

这两个函数仅用于套接字。

函数原型:

#include <sys/socket.h>
 #include <sys/types.h> 
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

sockfd:必须指向一个打开的套接字描述符

level:指定系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码(例如IPv4IPv6、TCP或SCTP)。
optval:指向某个变量(*optval)的指针,setsockopt从optval中取得选项设置的新值,getsockopt则把以获取的选项当前值存入optval。
optlen:指定*optval的大小
套接字选项的汇总可以查看unix网络编程(卷1)151页 图7-1和图7-2
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<think>好的,我需要回答用户关于`struct in_addr``struct in6_addr`结构体字段的问题。首先,用户已经询问过`in6_addr`的赋值方法,现在希望详细了解这两个结构体的字段。 首先,我应该回忆这两个结构体的定义。`struct in_addr`用于IPv4地址,通常包含一个32位的地址,存储为无符号整数。而`struct in6_addr`用于IPv6,包含128位的地址,通常以16字节的数组存储。 接下来,我需要确认标准头文件中的具体定义。对于`struct in_addr`,在`<netinet/in.h>`中定义,但用户提到的是`#include <arpa/inet.h>`。需要注意的是,`arpa/inet.h`通常包含网络地址转换函数的声明,而结构体定义可能在`netinet/in.h`中。不过,在实际系统中,`arpa/inet.h`可能会包含`netinet/in.h`,所以结构体定义确实在`arpa/inet.h`的上下文中可用。 对于`struct in_addr`,其字段可能是一个`in_addr_t`类型的成员,通常定义为32位无符号整数,例如`struct in_addr { in_addr_t s_addr; };`。而`struct in6_addr`通常包含一个16字节的无符号字符数组,如`unsigned char s6_addr[16];`。 另外,需要注意不同的系统可能有不同的实现,比如某些系统可能使用联合体来提供不同的访问方式。例如,`struct in_addr`可能有联合体允许以不同的方式访问地址,比如作为四个字节或一个长整型。但标准的定义可能更简单。 需要检查是否在`arpa/inet.h`中直接定义这些结构体,或者它们是从其他头文件引入的。根据POSIX标准,`struct in_addr``struct in6_addr`确实在`netinet/in.h`中定义,但`arpa/inet.h`包含了这些结构体,因为函数如`inet_pton``inet_ntop`需要它们。 因此,在回答中需要明确指出这两个结构体的字段,并说明它们的用途。例如,`struct in_addr`的`s_addr`字段存储IPv4地址的32位值,而`struct in6_addr`的`s6_addr`数组存储IPv6的128位地址。 还需要注意字节顺序的问题,IPv4的`s_addr`通常是网络字节序(大端),而IPv6的`s6_addr`是按网络字节顺序排列的字节数组,不需要额外转换。 最后,要确保回答准确,可能需要查阅相关文档或头文件,以确认结构体的具体定义,避免给出错误的信息。例如,在Linux系统中,`struct in_addr`确实只有一个成员`s_addr`,而`struct in6_addr`有`s6_addr`数组。此外,某些实现可能为`in6_addr`提供联合体,以支持不同的访问方式,但标准字段还是`s6_addr`。</think>在Linux网络编程中,`<arpa/inet.h>` 头文件定义了与网络地址转换相关函数结构体,其中`struct in_addr``struct in6_addr`分别用于存储IPv4IPv6地址。以下是这两个结构体的详细说明: --- ### 一、`struct in_addr`(IPv4地址结构体) #### 定义 ```c #include <netinet/in.h> // 通常包含在<arpa/inet.h>中 struct in_addr { in_addr_t s_addr; // 32位IPv4地址(网络字节序) }; ``` #### 字段说明 1. **`s_addr`**: - **类型**:`in_addr_t`(通常为`uint32_t`的别名)。 - **功能**:存储一个32位的IPv4地址,以**网络字节序(大端序)**表示。 - **示例**: ```c struct in_addr ipv4_addr; ipv4_addr.s_addr = inet_addr("192.168.1.1"); // 字符串转网络字节序 ``` #### 关键点 - **字节序问题**:IPv4地址需要以网络字节序存储,因此直接赋值时需使用`htonl()`或`inet_addr()`等函数进行转换。 - **兼容性**:某些系统可能通过联合体扩展`struct in_addr`,但标准实现仅包含`s_addr`。 --- ### 二、`struct in6_addr`(IPv6地址结构体) #### 定义 ```c #include <netinet/in.h> // 通常包含在<arpa/inet.h>中 struct in6_addr { union { uint8_t __u6_addr8[16]; // 以8位无符号整数访问 uint16_t __u6_addr16[8]; // 以16位无符号整数访问 uint32_t __u6_addr32[4]; // 以32位无符号整数访问 } __u6_addr; #define s6_addr __u6_addr.__u6_addr8 // 常用别名:按字节访问 #define s6_addr16 __u6_addr.__u6_addr16 // 按16位分段访问 #define s6_addr32 __u6_addr.__u6_addr32 // 按32位分段访问 }; ``` #### 字段说明 1. **联合体`__u6_addr`**: - **设计目的**:通过联合体提供多种访问方式(按8位、16位、32位分段),便于不同场景下的操作。 - **具体成员**: - **`__u6_addr8[16]`**:将128位地址视为16个连续的8位(字节)。 - **`__u6_addr16[8]`**:将地址视为8个16位整数(如IPv6分段表示)。 - **`__u6_addr32[4]`**:将地址视为4个32位整数。 - **示例**: ```c struct in6_addr ipv6_addr; inet_pton(AF_INET6, "2001:db8::1", ipv6_addr.s6_addr); // 使用s6_addr别名 ``` 2. **宏别名**: - **`s6_addr`**:最常用的字段,直接通过`uint8_t[16]`数组访问IPv6地址的每个字节。 - **`s6_addr16``s6_addr32`**:在需要按16位或32位操作地址时使用(如协议头处理)。 #### 关键点 - **字节序**:IPv6地址的每个字节直接按网络字节序存储,无需额外转换。 - **灵活性**:通过联合体宏别名支持不同粒度的地址操作,例如: ```c // 操作IPv6地址的第一个16位分段(2001) uint16_t first_segment = ntohs(ipv6_addr.s6_addr16[0]); ``` --- ### 三、对比总结 | 特性 | `struct in_addr` (IPv4) | `struct in6_addr` (IPv6) | |---------------------|----------------------------------|----------------------------------------| | **地址长度** | 32位(4字节) | 128位(16字节) | | **核心字段** | `s_addr`(`uint32_t`) | `s6_addr`(`uint8_t[16]`) | | **字节序** | 网络字节序(需转换) | 网络字节序(直接存储) | | **辅助访问方式** | 无 | 通过`s6_addr16``s6_addr32`分段访问 | --- ### 四、代码示例 #### IPv4地址操作 ```c #include <arpa/inet.h> struct in_addr ipv4; ipv4.s_addr = inet_addr("10.0.0.1"); // 字符串转网络字节序 printf("IPv4: %u\n", ntohl(ipv4.s_addr)); // 转为主机字节序输出 ``` #### IPv6地址操作 ```c #include <arpa/inet.h> struct in6_addr ipv6; const char* str = "2001:0db8:85a3::8a2e:0370:7334"; inet_pton(AF_INET6, str, ipv6.s6_addr); // 字符串转二进制 // 打印第5个16位分段(对应0370) printf("Segment[4]: %04x\n", ntohs(ipv6.s6_addr16[4])); ``` --- ### 五、注意事项 1. **避免直接赋值**:推荐使用`inet_pton()`、`inet_ntop()`等函数处理地址转换。 2. **兼容性**:IPv4映射的IPv6地址(如`::ffff:192.168.1.1`)需特殊处理前12字节。 3. **内存对齐**:操作`s6_addr16`或`s6_addr32`时需确保平台支持非对齐访问。 通过理解这两个结构体的字段,可以更灵活地处理IPv4IPv6地址的网络编程任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值