常用套接字函数原型及说明

正向和反向获取主机和服务信息的函数,以及套接字选项设置和获取的函数原型及说明。

一、早期套接字函数原型及说明

[!NOTE]

以下仅支持解析IPv4 且 不是线程安全的

  1. getsockname
    功能:获取套接字本地端的地址信息原型:

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

    说明:sockfd为套接字描述符,addr用于存储本地地址,addrlen传入地址结构体长度,传出实际长度。成功返回 0,失败返回 - 1(设置errno)。

  2. getpeername
    功能:获取套接字对端的地址信息原型:

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

    说明:仅适用于已连接的 TCP 套接字(connectaccept后),参数含义同getsockname

  3. getsockopt
    功能:获取套接字的选项值原型:

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

    说明:level为选项级别(如SOL_SOCKETIPPROTO_TCP),optname为选项名,optval存储结果,optlen传入缓冲区长度并传出实际使用长度。

  4. setsockopt
    功能:设置套接字的选项值原型:

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

    说明:optval为待设置的值,optlen为值的长度,其他参数同getsockopt。部分选项需在bind/connect前设置。

  5. gethostbyname
    功能:通过主机名获取主机信息(IPv4)原型:

    1
    2
    #include <netdb.h>
    struct hostent *gethostbyname(const char *name);

    说明:已过时(仅支持 IPv4),推荐使用getaddrinfo。返回struct hostent指针,失败返回NULL(设置h_errno)。

  6. gethostbyaddr
    功能:通过 IP 地址获取主机名(IPv4)原型:

    1
    2
    #include <netdb.h>
    struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);

    说明:addr为网络字节序的 IP 地址,len为地址长度(如sizeof(struct in_addr)),type为地址族(如AF_INET)。已过时。

  7. getservbyname
    功能:通过服务名和协议获取服务信息原型:

    1
    2
    #include <netdb.h>
    struct servent *getservbyname(const char *name, const char *proto);

    说明:name为服务名(如"http"),proto为协议(如"tcp",可为NULL)。返回struct servent指针,包含端口号(网络字节序)。

  8. getservbyport
    功能:通过端口号和协议获取服务信息原型:

    1
    2
    #include <netdb.h>
    struct servent *getservbyport(int port, const char *proto);

    说明:port为网络字节序的端口号(需用htons转换),其他参数同getservbyname

    struct hostent(主机信息结构体)

    用于存储主机的相关信息(如主机名、IP 地址列表等),定义在<netdb.h>

    1
    2
    3
    4
    5
    6
    7
    struct hostent {
    char *h_name; // 主机的官方名称(规范名)
    char **h_aliases; // 主机的别名列表(以NULL结尾的字符串数组)
    int h_addrtype; // 地址类型(如AF_INET表示IPv4,AF_INET6表示IPv6)
    int h_length; // 每个地址的长度(字节),如IPv4为4,IPv6为16
    char **h_addr_list; // 主机的IP地址列表(网络字节序,以NULL结尾的指针数组)
    };

    字段说明

    • h_name:主机的正式名称(如www.example.com)。

    • h_aliases:主机的其他别名(如example.com可能是www.example.com的别名),最后一个元素为NULL

    • h_addrtype:地址族类型,通常为AF_INET(IPv4),但现代系统也可能支持AF_INET6(IPv6)。

    • h_length:每个 IP 地址的长度(IPv4 为 4 字节,IPv6 为 16 字节)。

    • h_addr_list:存储主机的 IP 地址数组(网络字节序),每个元素是in_addr(IPv4)或in6_addr(IPv6)结构体的指针。为方便使用,通常用宏h_addr指代h_addr_list[0](第一个 IP 地址)。

    struct servent(服务信息结构体)

    用于存储网络服务的相关信息(如服务名、端口号、协议等),同样定义在<netdb.h>中。

    1
    2
    3
    4
    5
    6
    struct servent {
    char *s_name; // 服务的官方名称(规范名)
    char **s_aliases; // 服务的别名列表(以NULL结尾的字符串数组)
    int s_port; // 服务对应的端口号(网络字节序)
    char *s_proto; // 服务使用的协议(如"tcp"或"udp")
    };

    字段说明

    • s_name:服务的正式名称(如"http""ftp")。

    • s_aliases:服务的其他别名(如"www"可能是"http"的别名),最后一个元素为NULL

    • s_port:服务对应的端口号,以网络字节序(大端序)存储,使用时需通过ntohs转换为主机字节序。

    • s_proto:服务使用的协议类型(如"tcp"用于面向连接的服务,"udp"用于无连接服务)。

二、现代套接字函数原型及说明

getaddrinfo 函数:正向解析(主机名→IP 地址,服务名→端口号)

getaddrinfo的核心功能是将主机名(或 IP 字符串)服务名(或端口号字符串) 转换为一组用于创建套接字的struct sockaddr结构(套接字地址结构),方便后续的bindconnect等操作。

1. 函数原型
1
2
3
4
#include <netdb.h>
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
2. 参数详解
参数名 类型 含义
node const char* 输入参数:主机名(如"www.example.com")或 IP 地址字符串(如"192.168.1.1")。若为NULL,结合hints.ai_flagsAI_PASSIVE可用于服务器绑定通配地址(INADDR_ANY)。
service const char* 输入参数:服务名(如"http""ssh")或端口号字符串(如"80""22")。若为NULL,则不指定端口号(需手动在返回的struct sockaddr中设置)。
hints const struct addrinfo* 输入参数:用于过滤和指定返回结果的 “提示” 结构体。若为NULL,则默认返回所有可能的地址类型(IPv4/IPv6,TCP/UDP 等)。
res struct addrinfo** 输出参数:指向struct addrinfo链表的指针,存储解析结果。需通过freeaddrinfo释放内存。
3. 关键结构体:struct addrinfo

hintsres均为struct addrinfo类型,其定义如下:

1
2
3
4
5
6
7
8
9
10
struct addrinfo {
int ai_flags; // 标志位(见下文)
int ai_family; // 地址族(如AF_INET、AF_INET6、AF_UNSPEC)
int ai_socktype; // 套接字类型(如SOCK_STREAM、SOCK_DGRAM)
int ai_protocol; // 协议(如IPPROTO_TCP、IPPROTO_UDP,0表示任意)
socklen_t ai_addrlen; // ai_addr指向的地址结构的长度
char *ai_canonname; // 主机的规范名称(若指定AI_CANONNAME标志)
struct sockaddr *ai_addr; // 指向套接字地址结构(sockaddr_in或sockaddr_in6)
struct addrinfo *ai_next; // 链表下一个节点(可能有多个解析结果)
};

其中核心字段说明:

  • ai_family:指定地址族,AF_INET(仅 IPv4)、AF_INET6(仅 IPv6)、AF_UNSPEC(不限制,返回所有可能)。

  • ai_socktype:指定套接字类型,SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、0(任意类型)。

  • ai_protocol:指定协议,IPPROTO_TCP(TCP)、IPPROTO_UDP(UDP)、0(任意协议)。

  • ai_flags:常用标志(可组合使用,按位或):

    • AI_PASSIVE:用于服务器,nodeNULL时,返回的地址将是通配地址(INADDR_ANY for IPv4,in6addr_any for IPv6),适合bind
    • AI_CANONNAME:要求返回主机的规范名称(存储在ai_canonname)。
    • AI_NUMERICHOSTnode必须是 IP 地址字符串(不进行域名解析),否则返回错误。
    • AI_NUMERICSERVservice必须是端口号字符串(不解析服务名),否则返回错误。
4. 返回值
  • 成功:返回0res指向解析结果链表(可能有多个地址,需遍历处理)。

  • 失败:返回非 0 错误码(如EAI_NONAME表示主机名无效),可通过gai_strerror(err)获取错误信息字符串。

5. 内存释放

解析完成后,必须通过freeaddrinfo释放res指向的链表内存,避免泄漏:

1
void freeaddrinfo(struct addrinfo *res);
6. 使用场景
  • 客户端:将服务器主机名(如"www.baidu.com")和服务名(如"http")解析为struct sockaddr,用于connect

  • 服务器:将NULL(主机名)和服务名(如"8080")解析为通配地址(结合AI_PASSIVE),用于bind

getnameinfo 函数:反向解析(IP 地址→主机名,端口号→服务名)

getnameinfogetaddrinfo的反向操作:将struct sockaddr(套接字地址结构,包含 IP 和端口)转换为主机名服务名

1. 函数原型
1
2
3
4
5
#include <netdb.h>
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,
char *host, socklen_t hostlen,
char *serv, socklen_t servlen,
int flags);
2. 参数详解
参数名 类型 含义
addr const struct sockaddr* 输入参数:指向struct sockaddr(或sockaddr_in/sockaddr_in6)的指针,包含待解析的 IP 和端口。
addrlen socklen_t 输入参数:addr指向的地址结构的长度(如sizeof(struct sockaddr_in))。
host char* 输出参数:存储主机名的缓冲区(若为NULL,则不获取主机名)。
hostlen socklen_t 输入参数:host缓冲区的大小(字节)。
serv char* 输出参数:存储服务名的缓冲区(若为NULL,则不获取服务名)。
servlen socklen_t 输入参数:serv缓冲区的大小(字节)。
flags int 输入参数:控制解析行为的标志(见下文)。
3. 关键标志(flags)
  • NI_NAMEREQD:若无法解析主机名(如无 DNS 记录),则返回错误(EAI_NONAME),不返回 IP 字符串。

  • NI_NUMERICHOST:强制返回 IP 地址字符串(不解析为主机名)。

  • NI_NUMERICSERV:强制返回端口号字符串(不解析为服务名)。

  • NI_DGRAM:表示是 UDP 服务(默认假设是 TCP,某些服务名在 TCP 和 UDP 中可能不同)。

4. 返回值
  • 成功:返回0hostserv缓冲区分别存储主机名和服务名(若对应参数非NULL)。

  • 失败:返回非 0 错误码,可通过gai_strerror(err)获取错误信息。

5. 使用场景
  • 服务器:获取客户端连接的 IP 对应的主机名(如日志记录)。

  • 网络工具:将套接字地址转换为人类可读的主机名和服务名(如netstat显示www.baidu.com:http而非180.101.50.242:80)。

示例代码片段

1. getaddrinfo 示例(解析主机名和服务名)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main() {
const char* host = "www.baidu.com"; // 主机名
const char* service = "http"; // 服务名(对应80端口)

struct addrinfo hints, *res, *p;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // 不限制IPv4/IPv6
hints.ai_socktype = SOCK_STREAM; // TCP

int status = getaddrinfo(host, service, &hints, &res);
if (status != 0) {
std::cerr << "getaddrinfo error: " << gai_strerror(status) << std::endl;
return 1;
}

// 遍历解析结果
for (p = res; p != nullptr; p = p->ai_next) {
char ipstr[INET6_ADDRSTRLEN]; // 足够存储IPv6地址的缓冲区
void* addr;

// 判断是IPv4还是IPv6
if (p->ai_family == AF_INET) {
struct sockaddr_in* ipv4 = (struct sockaddr_in*)p->ai_addr;
addr = &(ipv4->sin_addr);
} else {
struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)p->ai_addr;
addr = &(ipv6->sin6_addr);
}

// 转换为字符串
inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr));
std::cout << "IP地址: " << ipstr << std::endl;
}

freeaddrinfo(res); // 释放内存
return 0;
}
2. getnameinfo 示例(解析 IP 地址和端口)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main() {
struct sockaddr_in addr; // IPv4地址结构
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(80); // 端口80
inet_pton(AF_INET, "180.101.50.242", &addr.sin_addr); // 百度的一个IP

char host[NI_MAXHOST]; // 主机名缓冲区
char serv[NI_MAXSERV]; // 服务名缓冲区

// 解析:获取主机名和服务名
int status = getnameinfo((struct sockaddr*)&addr, sizeof(addr),
host, sizeof(host),
serv, sizeof(serv),
0); // 不使用特殊标志

if (status != 0) {
std::cerr << "getnameinfo error: " << gai_strerror(status) << std::endl;
return 1;
}

std::cout << "主机名: " << host << std::endl; // 可能输出"www.baidu.com"
std::cout << "服务名: " << serv << std::endl; // 输出"http"
return 0;
}