当前位置:   article > 正文

Linux高性能服务器编程(6)socket基础_linux socket csdn

linux socket csdn

Linux高性能服务器编程(6)

Linux网络编程基础API

socket地址API。socket最开始的含义是一个IP地址和端口对(ip,port)。它唯一地表示了使用TCP通信地一端。

socket基础API。socket地主要API都定义在sys/socket.h头文件中,包括创建socket、命名socket、监听socket、接收连接、发起连接、读写数据、获取地址信息、检测带外标记,以及读取和设置socket选项。

网络信息API。Linux提供了一套网络信息API,以实现主机名和IP地址之间的转换,以及服务名称和端口号之间的转换。这些API都定义在netdb.h头文件中。

socket地址API

学习socket地址API,先要理解主机字节序和网络字节序

现代CPU的累加器一次都能装载(至少)4字节,即一个整数。那么这4个字节在内存中排列的顺序将影响它被累加器装载成的整数的值。这就是字节序问题。字节序封你为大端字节序和小端字节序。大端字节序是指一个整数的高位字节(2331bit)存储在内存的低地址处,低位字节(07bit)存储在内存的高地址处。小端字节序则是指整数的高字节位存储在内存的高地址处,而地位字节则存储在内存的低地址处。

现代PC大多采用小端字节序,因此小端字节序也被称为主机字节序。

当数据在两台使用不同字节序的主机之间直接传递时,发送端要将发送的数据转化为大端字节序数据后再发送,接收端知道对方传送过来的数据总是采用大端字节序,所有接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。因此大端字节序也称为网络字节序,它给所有接收数据的主机提供一个正确解释收到数据的保证。

通用socket地址

socket网络编程接口中表示socket地址的结构体sockaddr,其定义如下:

#include<bits/socket.h>
struct sockaddr
{
	sa_family_t sa_family;
	char sa_data[14];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

sa_family成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。常见的协议族和对应的地址族如下:

协议族地址族描述
PF_UNIXAF_UNIXUNIX本地域协议族
PF_INETAF_INETTCP/IPv4协议族
PF_IENT6AF_INET6TCP/IPv6协议族

宏PF_*和AF_*都定义在bits/socket.h头文件中,且后者与前者有完全相同的值,二者常混用。
sa_data成员用于存放socket地址值。但是,不同的协议族的地址值具有不同的含义和长度,如下:

协议族地址值含义和长度
PF_UNIX文件的路径名,长度可达108字节
PF_INET16bit端口号和32bit IPv4地址,共6字节
PF_IENT616bit端口号,32bit流标识,128bit IPv6地址,32bit范围ID,共26字节

由上表可知14字节的sa_data根本无法容纳多数协议族的地址值。

专用socket地址

通用的socket地址结构体显然不好用,比如设置与获取IP地址和端口号就需要执行繁琐的位操作。所有Linux位各个协议族提供了专门的socket地址结构体:

#include<sys/socket.h>
struct sockaddr_un
{
	sa_family_t sin_family;   /* 地址族:AF_UNIX */
	char sun_path[108];       /* 文件路径名 */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,分别使用IPv4和IPv6:

struct sockaddr_in
{
	sa_family_t sin_family; 	/* 地址族:AF_INET */
	u_int16_t sin_port; 		/* 端口号,要用网络字节序表示 */
	struct in_addr sin_addr;	/* IPv4地址结构体 */
};
struct in_addr
{
	u_int32_t s_addr;  			/* IPv4地址,要用网络字节序表示 */
};
struct sockaddr_in6
{
	sa_family_t sin6_family; 	/* 地址族:AF_INET */
	u_int16_t sin6_port; 		/* 端口号,要用网络字节序表示 */
	u_int32_t sin6_flowinfo;
	struct in6_addr sin6_addr;	/* IPv6地址结构体 */
	u_int32_t sin6_scope_id;    /* scope ID,尚处于实验阶段 */
};
struct in6_addr
{
	unsigned char sa_addr[16];  /* IPv6地址,要用网络字节序表示 */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

所有专用的socket地址(以及sockaddr_storage)类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr,因为所有socket编程接口使用的地址参数类型都是sockaddr。

IP地址转换函数

下面三个函数可以用于点分十进制字符串表示的IPv4地址和网络字节序整数表示的IPv4地址间的转换:

#include<arpa/inet.h>
in_addr_t inet_addr(const char* strptr);
int inet_aton(const char* cp,struct in_addr* inp);
char* inet_ntoa(struct in_addr in);
  • 1
  • 2
  • 3
  • 4

inet_addr函数将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。失败时返回INADDR_NONE
inet_aton函数完成和inet_addr同样的功能,但是将转化结果存储于参数inp指向地址的结构体中。它成功时返回1,失败返回0.
inet_ntoa函数将网络字节序表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。但是要注意的是,该函数内部用一个静态变量存储转化结果,函数的返回值指向该静态内存,因此inet_ntoa是不可重入的。

下面的更新的函数也能完成上面桑函数相同的功能,并且同时适用于IPv4和IPv6:

#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 cnt);
  • 1
  • 2
  • 3

inet_pton函数将用字符串表示IP地址src(用点分十进制字符串表示的IPv4地址或十六进制字符串表示的IPv6地址)转换成用网络字节序整数表示的IP地址,并把转换结果存储于dst指向的内存中。其中,af参数指定地址族,可以是AF_INET和AF_INET6.inet_pton成功时返回1,失败返回0并设置errno。
inet_ntop函数进行相反的转换,前三个参数的含义与inet_pton的参数相同,最后一个参数cnt指定目标存储单元的大小。下面两个宏能帮助我们指定这个大小(分别用于IPv4和IPv6):

#include<netinet/in.h>
#define INET_ADDRSTRLEN 16
#define INET_ADDRSTRLEN 46
  • 1
  • 2
  • 3

inet_ntop成功时返回目标存储单元的地址,失败则返回NULL并设置errno

创建socket

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
  • 1
  • 2
  • 3

domain参数告诉系统使用那个底层协议族。PF_INET(用于IPv4)或PF_INET6(用于IPv6);PF_UNIX(用于unix)。
type参数指定服务类型。服务类型主要有SOCK_STEARM服务(流服务,表示传输层使用TCP协议)和SOCK_UGRAM(数据报,表示传输层使用UDP协议)服务。
从Linux内核版本2.6.17起,type参数可以接收上述服务类型与下面两个重要的标志与相与的值:SOCK_NONBLOCK和SOCK_CLOEXEC。分别表示将创建的socket设置为非阻塞的,以及用folk调用创建子进程时在子进程中关闭该cocket。
protocol参数是在前面两个参数构成的协议集合下,在选择一个具体的协议。不过这个值通常都是唯一的(前面两个参数已经完全决定了它的值)。几乎在所有情况下,我们都应该把它设置为0,表示使用默认协议。
socket系统调用成功时返回一个socket文件描述符,失败则返回-1并设置errno。

命名socket

创建socket时,我们给它指定了地址族,但并未指定使用该地址族中的哪个具体socket地址。将一个socket与socket地址绑定称为给socket命名。在服务器程序中,通常要命名socket,只有命名后客户端才能知道该如何连接它。客户端则通常不要命名socket,采用匿名方式,即操作系统自动分配的socket地址。命名socket的系统调用是bind

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen);
  • 1
  • 2
  • 3

bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数指出该socket地址的长度。
bind成功时返回0,失败则返回-1并设置errno。其中两种常见的errno是EACCES和EADDRINUSE,它们的含义分别是:
EACCES,被绑定的地址是受保护的地址,仅超级用户能够访问。
EADDRINUSE,被绑定的地址正在使用中。比如将socket绑定到一个处于TIME_WAIT状态的socket地址。

监听socket

socket命名后,还需要系统调用来创建一个监听队列以存放待处理的客户连接:

#include<sys/socket.h>
int listen(int sockfd,int backlog);
  • 1
  • 2

sockfd参数指定被监听的socket。backlog参数提示内核监听队列的最大长度。监听队列的长度如果超过backlog,服务器将不受理新的客户连接,客户端也将收到ECONNREFUSED错误信息。2.2版本后backlog只表示处于完全连接状态的socket的上限。
listen成功时返回0,失败返回-1并设置errno

接收连接

下面的系统调用从linsten监听队列中接收一个连接:

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
  • 1
  • 2
  • 3

sockfd参数是执行过listen系统调用的监听socket。addr参数用来获取被接收连接的远端socket地址,该socket地址的长度由addrlen参数指出。accept成功时返回一个新的连接socket,该socket唯一标识了被接收的这个连接,服务器可以通过读写该socket来与被接受连接对应的客户端通信。accept失败时返回-1并设置errno。

发起连接

客户端通过调用connect函数来主动与服务器建立连接。

#include<sys/types.h>
#include<sys/socket.h>
connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
  • 1
  • 2
  • 3

sockfd参数由socket系统调用返回一个socket。serv_addr参数是服务器监听的socket地址,addrlen参数指定这个地址的长度。
connect成功时返回0.一旦成功建立连接,sockfd就唯一标识了在这个连接,客户端就可以通过读写sockfd参数来与服务器通信。connect失败则返回-1并设置errno。其中两种常见的errno是ECONNREFUSED和ETIMEDOUT,它们含义如下:
ECONNREFUSED,目标端口不存在,连接被拒绝。
ETIMEDOUT,连接超时。

关闭连接

关闭一个连接实际上就是关闭该连接对应的socket。通过调用close函数来关闭

#include<unistd.h>
int close(int fd);
  • 1
  • 2

fd参数是待关闭的socket。不过,close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1.只有当fd的引用计数为0时,才是真正关闭连接。多进程程序中,一次fork系统调用默认将使父进程中打开的socket的引用计数加1,因此我们必须在父进程和子进程中都对该socket执行close调用才能将连接关闭。
如果一定要立即终止连接(不是将socket的引用计数减1),可以使用shutdown系统调用(相对close来说,它是专门为网络编程设计的):

#include<sys/socket.h>
int shutdown(int sockfd,int howto);
  • 1
  • 2

sockfd参数是待关闭的socket。howto参数决定了shutdown的行为。

可选值含义
SHUT_RD关闭sockfd上读的这一半。应用程序不能再针对socket文件描述符执行读操作,并且该socket接收缓冲区的数据都被丢弃
SHUT_WR关闭sockfd上写的这一半。sockfd的发送缓冲区中的数据会在真正关闭连接之前全部发送出去,应用程序不可再对该socket文件描述符执行写操作。这种情况下,连接处于半关闭状态。
SHUT_RDWR同时关闭sockfd上的读和写

由此可见,shutdown能够分别关闭sockfd上的读或写,或者都关闭。而close在关闭连接时只能将socket上的读和写同时关闭。shutdown成功时返回0,失败则返回-1并设置errno。

数据读写

TCP数据读写

对文件的读写操作read和write同样适用于socket。socket编程接口提供了几个专门用于socket数据读写的系统调用,它们增加了对数据读写的控制。其中用于TCP数据读写的系统调用是:

#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t len,int flags);
ssize_t send(int sockfd,const void *buf,size_t len,int flags);
  • 1
  • 2
  • 3
  • 4

recv读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小,flags参数的含义(……)通常设置为0即可。因此我们可能要多次调用recv,才能读取到完整的数据。recv可能返回0,这意味着通信双方已经关闭连接。recv出错时返回-1并设置errno。

send往sockfd上写入数据,buf和len参数分别指定写缓冲区的位置和大小。send成功时返回实际写入的数据长度,失败返回-1并设置errno。

flags参数为数据收发提供额外的控制,它可以取表选项中的一个或几个的逻辑或。

选项名含义sendrecv
MSG_CONFIRM指示数据链路层协议持续监听对方的回应,直到答复,它们仅能用于SOCK_DGRAM和SOCK_RAW类型的socketYN
MSG_DONTROUTE不查看路由表,直接将数据发送给本地局域网络内的主机。这表示发送者确切地知道目标主机就在本地网络上YN
MSG_DONTWAIT对socket地此次操作将是非阻塞的YY
MSG_MORE告诉内核应用程序还有更多数据要发送,内核将超时等待新数据写入TCP发送缓冲区后一并发送。这样可以防止TCP发送过多小的报文段,从而提高传输效率YN
MSG_WAITALL读操作仅在读取到指定数量的字节后才返回NY
MSG_PEEK窥探读缓存中的数据,此次读操作不会导致这些数据被清除NY
MSG_OOB发送或接收紧急数据YY
MSG_NOSIGNAL往读端关闭的管道或者socket连接中写数据时不引发SIGPIPE信号YN

flags参数只对send和recv的当前调用生效。

UDP数据读写

socket编程接口中用于UDP数据报读写的系统调用是:

#include<sys/types.h>
#include<sys/socket.h>
ssize_t recvfrom(int sockfd,void* buf,size_t len,int flags,struct sockaddr* src_addr,socklen_t* addrlen);
ssize_t sendto(int sockfd,const void* buf,size_tlen,int flags,const struct sockaddr* dest_addr,socklen_t addrlen);
  • 1
  • 2
  • 3
  • 4

recvfrom读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小。因为UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址,即参数src_addr所指的内容,addrlen参数则指定该地址的长度。
sendto往sockfd上写入数据,buf和len参数分别指定写缓冲区的位置和大小。dest_addr参数指定接收端的socket地址,addrlen参数则指定该地址的长度。
这两个系统调用的flags参数以及返回值的含义均与send/recv系统调用的flags参数和返回值相同。

通用数据读写函数

socket编程接口还提供了一对通用的数据读写系统调用。不仅可以用于TCP流数据,也能用于UDP数据报:

#include<sys/socket>
ssize_t recvmsg(int sockfd,struct msghdr* msg,int flags);
ssize_t sendmsg(int sockfd,struct msghdr* msg,int flags);
  • 1
  • 2
  • 3

sockfd参数指定被操作的目标socket。msg参数是msghdr结构体类型的指针,msghdr结构体如下:

struct msghdr
{
	void* msg_name;				/* socket地址 */
	socklen_t msg_namelen;		/* socket地址的长度 */
	struct iovec* msg_iov;	 	/* 分散的内存块 */
	int msg_iovlen;				/* 分散内存块的数量 */
	void* msg_contral;			/* 指向辅助数据的起始位置 */
	socklen_t msg_controllen;	/* 辅助数据的大小 */
	int msg_flags;				/* 复制函数中的flags参数,并且在调用过程中更新 */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

msg_name成员指向socket地址结构变量。它指定通信双方的socket地址。对于TCP协议,必须设置为null。因为对于数据流socket而言,对方的socket地址已知。mag_namelen成员指定了msg_name所指socket地址的长度。
msg_iov成员是iovec结构体类型的指针,(iovec结构体封装了一块内存的起始位置和长度)iovec结构体的定义如下:

struct iovec
{
	void* iov_base;		/* 内存起始地址 */
	size_t iov_len;		/* 这块内存的长度 */
}
  • 1
  • 2
  • 3
  • 4
  • 5

msg_iovlen指定这样的iovec结构对象有多少个。对于recvmsg而言,数据将被读取并存放在msg——iovlen块分散的内存中,这些内存的位置和长度则由msg_iov指向的数组指定,这称为分散读。对于sendmsg而言,msg_iovlen块分散内存中的数据将被一并发送,这称为集中写。
msg_contraol和msg_controllen成员用于辅助数据的传送。
msg_flags成员无须设定,它会复制recvmsg/sendmsg的flags参数的内容以影响读写过程。recvmsg还会在调用结束前,将某些更新后的标志设置到msg_flags中
recv/sendmsg的flags参数以及返回值的含义均与send/recv的flags参数以及返回值相同。

带外标记

实际情况中,并不能预知到带外数据何时到来。好在Linux内核检测到TCP紧急标志时,将通知应用程序有带外数据需要接收。内核通知应用程序带外数据到达的两种常见方式是:I/O复用产生的异常事件和SIGURG信号。即使应用程序得到带外数据需要接收的通知,还要知道带外数据在数据流中的具体位置,才能准确接收带外数据。这一点可以通过如下系统调用实现:

#include<sys/socket.h>
int sockatmark( int sockfd );
  • 1
  • 2

sockatmark判断sockfd是否处于带外标记,即下一个被读取到的数据是否是带外数据。如果是,sockatmark返回1,此时我们就可以利用MSG_OOB标志的recv调用来接收带外数据。如果不是则返回0.

地址信息函数

在某些情况下,我们想知道一个连接socket的本端socket地址,以及远端的socket地址。下面两个函数就是为了解决该问题:

#include<sys/socket>
int getsockname(int sockfd,struct sockaddr* address,socklen_t* address_len);
int getpeername(int sockfd,struct sockaddr* address,socklen_t* address_len);
  • 1
  • 2
  • 3

getsockname获取sockfd对应本端socket地址,并存储于address参数指定的内存中,该socket地址的长度则存储于address_len参数指向的变量中。如果实际socket地址的长度大于address所指内存区的大小,那么该socket地址将被截断。成功返回0,失败返回-1并设置errno。

getpeername获取sockfd对应的远端socket地址,其参数及返回值的含义与getsockname相同。

socket选项

下面两个系统调用是专门用来读取和设置socket文件描述符书写的方法:

#include <sys/socket.h>
int getsockopt(int sockfd,int level,int option_name,void* option_value,socklen_t* restrict option_len);
int setsockopt(int sockfd,int level,int option_name,const void* option_value,socklen_t*  option_len);
  • 1
  • 2
  • 3

sockfd参数指定被操作的目标socket。level参数指定要操作那个协议的选项(即属性),比如IPv4,IPv6,TCP等。option_name参数则指定选项的名字。socket通信中常用的socket选项。option_value和option_len参数分别是被操作许爱像的值和长度不同的选项具有不同类型的值。在这里插入图片描述

getsockopt和setsockopt这两个函数成功时返回0,失败时返回-1并设置errno。

网络信息API

gethostbyname和gethostbyaddr

gethostbyname函数根据主机名称获取主机的完整信息,gethostbyaddr函数根据IP地址获取主机的完整信息。

#include<sys/socket.h>
struct hostent* gethostbyname(const char* name);
struct hostent* gethostbyaddr(const void* addr,size_t len,int type);
  • 1
  • 2
  • 3

name参数指定目标主机的主机名,addr参数指定目标主机的IP地址,len参数指定addr所指IP地址的长度,type参数指定addr所指IP地址的类型,其合法取值包括AF_INET(用于IPv4地址)和AF_INET6(用于IPv6地址)。这两个函数的返回值都是hostent结构体指针,hostent结构体定义如下:

#include<netdb.h>
struct hostent
{
	char* h_name;			/* 主机名 */
	char** h_aliases;		/* 主机别名列表,可能有多个 */
	int h_addrtype;			/* 地址类型(地址族) */
	int h_length;			/* 地址长度 */
	char** h_addr_list;		/* 按网络字节序列出的主机IP地址列表 */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
getservbyname和getservbyport

getservbyname函数根据名称获取某个服务的完整信息,getservbyport函数根据端口号获取某个服务的完整信息。他们实际是通过读取/etc/services文件来获取服务的信息的。这两个函数的定义如下:

#include<sys/socket.h>
struct servent* getservbyname(const char* name,const chaar* proto);
struct servent* getservbyport(int port,const char* proto);
  • 1
  • 2
  • 3

name参数指定服务的名字,port参数指定目标服务对应的端口号。porto参数指定服务类型,给它传递“tcp”表示获取流服务,给它传递“udp”表示获取数据报服务,给它传递NULL则表示获取所有类型的服务。
这两个函数返回的都是servent结构体类型的指针,结构体servent的定义如下:

#include<sys/socket.h>
struct servent
{
	char* s_name;		/* 服务名称 */
	char** s_aliases;	/* 服务的别名列表,可能有多个 */
	int s_port;			/* 端口号 */
	char* s_proto;		/* 服务类型,通常是tcp或者udp */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

上面讨论的四个函数都是不可重入的,即线程安全的。不过netdb.h头文件给出了它们的可重入版本。正如Linux下所有其他函数的可重入版本的命名规则那样,这些函数的函数名是在原函数名尾部加上_r(re-entrant)。

getaddrinfo

getaddrinfo函数既能通过主机名称获得IP地址(内部使用的是gethostbyname函数),也能通过放我们获得端口号(内部使用的是getservbyname函数)。它是否可重入取决于其内部调用的gethostbyname和gerservbyname函数是否是它们的可重入版本。该函数定义如下:

int getaddrinfo(cosnt char* hostname,cosnt char* service,cosnt struct addrinfo* hints,struct addrinfo** result);
  • 1

hostname参数可以接收主机名,也可以接收字符串表示的IP地址(IPv4采用点分十进制字符串,IPv6采用十六进制字符串)。同样,service参数可以接收服务名,也可以接收字符串表示的十进制端口号。hints参数是给应用程序getaddrinfo的一个提示,以对getaddrinfo的输出进行更精确的控制。hints参数可以背被设置为NULL,表示允许getaddrinfo反馈任何可用的结果。result参数指向一个链表,该链表用于存储getaddrinfo反馈的结果。
getaddrinfo反馈的每一条结果都是addrinfo结构体类型的对象,结构体addrinfo的定义如下:

struct addrinfo
{
	int ai_flags;				/*  */
	int ai_family;				/* 地址族 */
	int ai_socktype;			/* 服务类型,SOCK_STREAM(流服务)或SOCK_DGRAM(数据报) */
	int ai_protoclo;			/*  */
	socklen_t ai_addrlen;		/* socket地址ai_addr的长度 */	
	char* ai_canonname;			/* 主机的别名 */
	struct sockaddr* ai_addr;	/* 指向socket地址 */
	struct addrinfo* ai_next;	/* 指向下一个sockinfo结构体的对象 */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

该结构体,ai_protocol成员是指具体的网络协议,其含义和socket系统调用的第三个参数相同,它通常被设置为0.ai_flags成员可用取表中的标志的按位或

选项含义
AI_PASSIVE在hints参数中设置,变送调用者是否会将取得的socket地址用于被动打开。服务器通常需要设置它,表示接受任何本地socket地址上的服务请求。客户端程序不能设置它
AI_CANONNAME在hints参数中设置,告诉getaddrinfo函数返回主机的别名
AI_NUMERICHOST在hints参数中设置,表示hostname必须是字符串表示的IP地址,从而避免了DNS查询
AI_NUMERICSERV在hints参数中设置,强制service参数使用十进制端口号的字符串形式,而不能是服务名
AI_V4MAPPED在hints参数中设置,如果ai_family被设置为AF_INET6,那么当没有满足条件的IPv6的地址被找到时,将IPv4地址映射为IPv6地址
AI_ALL必须和AI_V4MAPPED同时使用,否则将被忽略。表示同时返回符合条件的IPv6地址以及由IPv4地址映射得到的IPv6地址
AI_ADDRCONFIG仅当至少配置有一个IPv4地址(除了回路地址)时,才返回IPv4地址信息;同样,仅当至少配置有一个IPv6地址(除了回路地址)时,才返回IPv6地址信息。它和AI_V4MAPPED互斥

当我们使用hints参数的时候,可以设置其ai_flags,ai_family,ai_socktype和ai_protocol四个字段,其他字段则必须设置为NULL。

getnameinfo

getnameinfo函数能通过socket地址同时获得以字符串表示的主机名(内部使用的是gethostbyaddr函数)和服务名(内部使用的是getservbyport)。它是否可以重入取决于其内部调用的gethostbyaddr和getservbyport函数是否是它们的可重入版本。该函数定义如下:

#include<netdb.h>
void freeaddrinfo(struct addrinfo* res);
  • 1
  • 2

getnameinfo将返回的主机名存储在host参数指向的缓存中,将服务名存储在serv参数指向的缓存中,hostlen和servlen参数分别指定这两块缓存的长度。flags参数控制getnameinfo的行为,它可以接受表中的选项。

选项含义
NI_NAMEREQD如果通过socket地址不能获得主机名,则返回一个错误
NI_DGRAM返回数据报服务。大部分同时支持流和数据报的服务使用相同的端口号来提供这两种服务。
NI_NUMERICHOST返回字符串表示的IP地址,而不是主机名
NI_NUMERICSERV返回字符串表示的十进制端口号,而不是服务名
NI_NOFQDN仅返回主键域名的第一部分

getaddrinfo和getnameinfo函数成功时返回0,失败则返回错误码,可能错误码如表所示。

选项含义
EAI_AGAIN调用临时失败,提示应用程序过后再试
EAI_BADFLAGS非法的ai_flags值
EAI_FAIL名称解析失败
EAI_FAMILY不支持ai_family参数
EAI_MEMORY内存分配失败
EAI_NONAME非法的主机名或服务名
EAI_OVERFLOW用户提供的缓冲区溢出。仅发生在getnameinfo调用中
EAI_SERVICE没有支持的服务,比如用数据报服务类型来查找ssh服务。因为ssh服务只能使用流服务
EAI_SOCKTYPE不支持的服务类型
EAI_SYSTEM相同错误,错误值存储在errno中

Linux下strerrno函数能将数组错误码errno转换成易读的字符串形式。同样下面的函数可以将表中的错误码转换成其他字符串形式:

#include<netdb.h>
const char* gai_strerror(int error);
  • 1
  • 2

| EAI_NONAME | 非法的主机名或服务名 |
| EAI_OVERFLOW | 用户提供的缓冲区溢出。仅发生在getnameinfo调用中 |
| EAI_SERVICE | 没有支持的服务,比如用数据报服务类型来查找ssh服务。因为ssh服务只能使用流服务 |
| EAI_SOCKTYPE | 不支持的服务类型 |
| EAI_SYSTEM | 相同错误,错误值存储在errno中 |

Linux下strerrno函数能将数组错误码errno转换成易读的字符串形式。同样下面的函数可以将表中的错误码转换成其他字符串形式:

#include<netdb.h>
const char* gai_strerror(int error);
  • 1
  • 2
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/622877
推荐阅读
相关标签
  

闽ICP备14008679号