Linux网络API - 网络信息API_linux网络详解

Linux网络API - 网络信息API_linux网络详解

编码文章call10242025-03-02 18:25:5526A+A-

socket地址的两个要素,即IP地址和端口号,都是用数值表示。这其实不便于记忆,也不便于扩展(比如从IPv4转移到IPv6)。因此你会经常看见用主机名访问一台机器,而不是IP地址;用服务名称来代替端口号。

gethostbyname 和 gethostbyaddr

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

gethostbyname函数通常先在本地/etc/hosts配置文件中查找主机,

如果没有找到,再去访问DNS服务器。

这两个函数的定义如下:

#include

struct hostent* gethostbyname(const char* name);

struct hostent* gethostbyaddr(const void* addr, size_t len, int type);

name参数指定目标主机的主机名,addr参数指定目标主机的IP地址,len参数指定addr所指IP地址的长度,type参数指定addr所指IP地址的类型,其合法取值包括AF_INET(用于IPv4地址)和AF_INET6(用于IPv6地址)。

这两个函数返回的都是hostent结构体类型的指针,host结构体的定义如下:

#include 
struct hostent
{
    char* h_name;       /* 主机名 */
    char** h_aliases;   /* 主机别名列表,可能有多个 */
    int h_addrtype;     /* 地址类型(地址族)*/
    int h_length;       /* 地址长度*/
    char** h_addr_list; /* 按网络字节序列列出的主机IP地址列表*/
};
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    struct hostent *info = gethostbyname("www.baidu.com");
    if(!info){
        printf("error: %d - %s\n", errno, strerror(errno));
    }
    
    //主机名
    printf("%s\n", info->h_name);

    //别名
    for(int i=0; info->h_aliases[i]; i++){
        printf("Aliases %d: %s\n", i+1, info->h_aliases[i]);
    }

    //地址类型
    printf("Address type: %s\n", (info->h_addrtype==AF_INET) ? "AF_INET": "AF_INET6");

    //地址长度
    printf("Address length: %d\n", info->h_length);

    //地址
    if(info->h_addrtype == AF_INET){
        char buf[INET_ADDRSTRLEN];
        for(int i=0; info->h_addr_list[i]; i++){
            inet_ntop(info->h_addrtype, info->h_addr_list[i], buf, INET_ADDRSTRLEN);
            printf("IPv4 addr %d: %s\n", i+1, buf);
        }
    }

    if(info->h_addrtype == AF_INET6){
        char buf[INET6_ADDRSTRLEN];
        for(int i=0; info->h_addr_list[i]; i++){
            inet_ntop(info->h_addrtype, info->h_addr_list[i], buf, INET6_ADDRSTRLEN);
            printf("IPv6 addr %d: %s\n", i+1, buf);
        }
    }
    return 0;
}

getsrvbyname 和 getsrvbyport

  • getservbyname函数根据名称获取某个服务的完整信息
  • getservbyport函数根据端口号获取某个服务的完整信息

它们实际上都是通过读取/etc/services 文件获取服务的信息。

#include

struct servent* getservbyname(const char* name, const char* proto);

struct servent* getservbyport(int port, const char* proto);

name参数指定目标服务的名字,port参数指定目标服务对应的端口号。proto参数指定服务类型,给它传递“tcp”表示获取流服务,给它传递“udp"表示获取数据报服务;给它传递NULL则表示获取所有类型服务。

这两个函数返回的都是servent结构体类型的指针,结构体servent的定义如下:

#include 
struct servent
{
    char* s_name;       /* 服务名称 */
    char** s_aliases;   /* 服务的别名列表, 可能有多个 */
    int s_port;         /* 端口号 */
    char* s_proto;      /* 服务类型, 通常是tcp或者udp */
};

下面是通过主机名和服务名来访问目标服务器上的daytime服务。

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char const *argv[])
{
    assert(argc == 2);
    const char *host = argv[1];
    
    /*获取目标主机地址信息*/
    struct hostent* hostinfo = gethostbyname(host);
    assert(hostinfo);

    /*获取daytime服务信息*/
    struct servent *servinfo = getservbyname("daytime", "tcp");
    assert(servinfo);
    printf("daytime port is %d\n", ntohs(servinfo->s_port));

    struct sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_port = servinfo->s_port;

    address.sin_addr = *(struct in_addr*)*hostinfo->h_addr_list;

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    int result = connect(sockfd, (struct sockaddr*)&address, sizeof(address));
    assert(result != -1);

    char buffer[128];
    result = read(sockfd, buffer, sizeof(buffer));
    assert(result);
    buffer[result] = '\0';
    printf("the day time is: %s\n", buffer);
    close(sockfd);
    return 0;
}

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

getaddinfo

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

#include

int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result);

  • 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_protocol;
    socklen_t ai_addrlen;       /* socket地址ai_addr的长度 */
    char* ai_canonname;         /* 主机的别名 */
    struct sockaddr* ai_addr;   /* 指向socket地址 */
    struct addrinfo* ai_next;   
};
  • ai_protocol成员是指具体的网络协议,其含义和socket系统调用的第三个参数相同,它通常被设置为0。
  • ai_flags可以按照下表的标志按位或

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

下面是利用hints参数获取主机daytime流服务信息:

struct addrinfo hints;
struct addrinfo *res;
bzero(&hints, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
getaddrinfo("alexdev", "daytime", &hints, &res);

这里需要注意的是getaddrinfo隐式地分配了堆内存,因此在调用结束后,我们必须使用如下配对函数来释放这块内存

#include

void freeaddrinfo(struct addrinfo * res);

getnameinfo

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

#include

int getnameinfo( const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags);

getnameinfo将返回的主机名存储在host参数指向的缓存中,将服务名称存储在serv参数指向的缓存中,hostlen和serlen参数分别指定这两块缓存的长度。flags参数控制getnameinfo的行为:

getaddrinfo和getnameinfo函数成功时返回0,失败则返回错误码,可能的错误码如下:

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

#include

const char* gai_strerror(int error);

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4