getaddrinfo

از ویکی‌پدیا، دانشنامهٔ آزاد

getaddrinfo()‎ و getnameinfo()‎ دو تابع در استاندارد پازیکس هستند که برای تبدیل کردن اسامی میزبان (به انگلیسی: hostname) و آدرس‌های آی‌پی، از فرم متنی قابل فهم برای انسان، به قالب دودویی ساختارمند که برای رایانه قابل فهم است (و برعکس)، مورد استفاده قرار می‌گیرند. این دو تابع معکوس همدیگر هستند. getaddrinfo نام دامنه یا آدرس IP را به نمایش دودویی قابل فهم برای رایانه تبدیل می‌کند و getnameinfo هم برعکس، نمایش دودویی یک آدرس را به فرم قابل فهم برای انسان تبدیل می‌کند. این توابع هم از IPv4 و هم از IPv6 پشتیبانی می‌کنند. برای ساخت برنامه‌های غیر وابسته به یک پروتکل خاص و همینطور برای گذار و مهاجرت از IPv4 به IPv6، استفاده از این توابع توصیه شده است. از نظر داخلی، این دو تابع برای انجام پرسوجوی DNS توابع دیگر و سطح پایینتری مانند gethostbyname()‎ را فراخوانی می‌کنند. فایل resolv.conf نحوه انجام این پرس و جو را مشخص می‌کند و از طریق این فایل می‌توان این رفتار را تغییر داد.

ساختار addrinfo[ویرایش]

ساختاری که در زبان برنامه‌نویسی سی برای کار با این توابع استفاده می‌شود addrinfo نام دارد و نمایش‌دهنده آدرس‌های آی‌پی و اسامی میزبان است. این ساختار در فایل سرایند netdb.h تعریف شده است:

     struct addrinfo {
             int ai_flags;           /* input flags */
             int ai_family;          /* protocol family for socket */
             int ai_socktype;        /* socket type */
             int ai_protocol;        /* protocol for socket */
             socklen_t ai_addrlen;   /* length of socket-address */
             struct sockaddr *ai_addr; /* socket-address for socket */
             char *ai_canonname;     /* canonical name for service location */
             struct addrinfo *ai_next; /* pointer to next in list */
     };

در سیستم‌عاملهای قدیمی، عضو ai_addrlen به جای اینکه از نوع socklen_t باشد، از نوع size_t تعریف شده بود که در سیستم‌های جدید تغییر یافته است. بیشتر توابع مربوط به سوکت‌ها، همانند accept()‎ و getpeername()‎، معمولاً پارامتری از نوع socklen_t*‎ دارند و برنامه‌نویس معمولاً آدرس عضو ai_addrlen در ساختار addrinfo را به عنوان آرگومان برای این توابع ارسال می‌کند. اگر این نوع داده‌ها سازگار نباشند، امکان بروز خطای زمان اجرا وجود دارد. برای مثال در سیستم‌عامل ۶۴بیتی و big-endian سولاریس ۹، اندازه size_t هشت بایت است و socklen_t چهار بایت است که size_t و socklen_t ناسازگار هستند.

getaddrinfo()‎[ویرایش]

getaddrinfo()‎، اسامی میزبان و آدرس‌های آی‌پی که به صورت رشته‌های متنی قابل فهم برای انسان هستند را به یک لیست پیوندی پویا که از ساختارهای addrinfo تشکیل شده، تبدیل می‌کند. نمونه اولیه (به انگلیسی: prototype) این تابع بدین شکل است:

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *hostname,
                const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);
  • پارامتر hostname هم می‌تواند یک دامنه مانند «nice.com» یا یک آدرس آی‌پی مانند "127.0.0.1" یا حتی مقدار NULL باشد. اگر NULL باشد، یکی از آدرس‌های 0.0.0.0 یا 127.0.0.1 بسته به فلگ‌های پارامتر hints انتخاب می‌شود.
  • پارامتر service مشخص کننده یک شماره پورت همانند "80" یا مشخص کننده نام سرویس همانند "echo" است. در صورت استفاده کردن از نام سرویس، تابع gethostbyname()‎ فراخوانی شده و این نام با استفاده از فایل ‎/etc/services به شماره پورت، در اینجا 7 تبدیل میشود.
  • پارامتر hints یا می‌تواند NULL باشد یا می‌تواند یک ساختار addrinfo باشد که نوع سرویس مورد نظر برنامه‌نویس را مشخص می‌کند. به عنوان مثال، یک سوکت می‌تواند یا TCP یا UDP باشد، می‌توان نوع مورد نظر خود را انتخاب کرد.
  • پارامتر res اشاره‌گری دوتایی به یک ساختار addrinfo است. gethostbyname()‎ یک لیست حاوی اطلاعات خواسته شده ایجاد می‌کند که این پارامتر اشاره‌گری به اولین گره آن خواهد بود. با دنبال کردن اشاره‌گر ai_next می‌توان این لیست را پیمایش کرد.

تابع در صورت موفقیت مقدار صفر و در صورت شکست یک مقدار منفی را برمیگرداند.

هر چند که این تابع به شکل متفاوتی در هر سیستم‌عامل پیاده‌سازی شده است، اما روش کار آن معمولاً به این صورت است که ابتدا سعی می‌کند شماره پورت مورد نظر که توسط پارامتر service مشخص شده را بدست آورد. اگر پارامتر service یک شماره پورت بود، به کمک تابع htons()‎ شماره پورت مورد نظر را به صورت Network byte order در می‌آورد. اگر پارامتر service یک نام مانند www بود، تابع getservbyname()‎ را اجرا می‌کند تا شماره پورت معادل www را بدست آورد (در اینجا 80). hints->ai_socktype به عنوان پارامتر دوم به تابع getservbyname()‎ فرستاده می‌شود. اگر مقدار پارامتر hostname مشخص شده باشد و NULL نباشد، تابع gethostbyname()‎ را هم اجرا خواهد کرد تا آدرس آی‌پی دامنه خواسته شده را بدست آورد. در غیر این صورت، اگر پارامتر hostname برابر NULL باشد، و همچنین اگر hints->ai_flags هم برابر AI_PASSIVE باشد، مقدار 0.0.0.0 را در نظر می‌گیرد. اگر hints->ai_flags برابر AI_PASSIVE نباشد، مقدار 127.0.0.1 در نظر گرفته خواهد شد. سپس malloc_ai فراخوانی می‌شود و شماره پورتی که در ابتدا بدست آمده بود را به آن ارسال می‌کند تا یک ساختار addrinfo که با sockaddr_in مناسب پر شده است، بدست آید. سپس پارامتر ‎**res را تغییر می‌دهد تا به یک ساختار addrinfo که به تازگی اختصاص داده شده، اشاره کند. در برخی از پیاده‌سازی‌ها، hints->ai_protocol مقدار hints->ai_socktype را بی‌اثر می‌کند، در حالی که در برخی دیگر از پیاده‌سازی‌ها، برعکس این قضیه صادق است، بنابراین، برای پورتابل بودن کد، هر دو فیلد ساختار باید توسط برنامه‌نویس مشخص شوند.

getnameinfo[ویرایش]

تابع getnameinfo نمایش دودویی یک آدرس IP که به فرم struct sockaddr است را به فرم قابل فهم برای انسان تبدیل می‌کند. در صورتی که آدرس بدست آمده قابل بازگردانی به نام دامنه متناظرش باشد، نام دامنه، در غیر این صورت، آدرس IP هم همراه شماره پورت و سرویس برمی‌گردد.

#include <sys/socket.h>
#include <netdb.h>

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

freeaddrinfo[ویرایش]

این تابع حافظه‌ای که توسط تابع getaddrinfo اختصاص داده شده را آزاد می‌کند. getaddrinfo یک لیست پیوندی از ساختارهای addrinfo برمی‌گرداند، در نتیجه freeaddrinfo تمام این لیست را پیمایش کرده و حافظه مربوط به کلیه عناصر آن را آزاد می‌کند.

#include <sys/socket.h>
#include <netdb.h>

void freeaddrinfo(struct addrinfo *ai);

پارامتر ai گره ابتدایی لیست را مشخص می‌کند.

مثال[ویرایش]

مثال زیر از تابع getaddrinfo برای ترجمه دامنه www.example.com به آدرس‌های IP متناظرش استفاده می‌کند و سپس getnameinfo را بر روی این آدرس‌ها فراخوانی می‌کند تا نام متعارف هر کدام را پیدا کند. به طور کلی، انجام این کار موجب می‌شود همان نام دامنه اولیه بدست آید، مگر اینکه یک آدرس خاص، چندین نام دامنه داشته باشد، که در این صورت نام متعارف بازگردانده می‌شود. در این مثال، نام دامنه در کل سه بار چاپ می‌شود، یکی به ازای هر نتیجه بدست آمده.

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#ifndef NI_MAXHOST
#define NI_MAXHOST 1025
#endif

int main(void)
{
    struct addrinfo *result;
    struct addrinfo *res;
    int error;

    /* resolve the domain name into a list of addresses */
    error = getaddrinfo("www.example.com", NULL, NULL, &result);
    if (error != 0)
    {   
        if (error == EAI_SYSTEM)
        {
            perror("getaddrinfo");
        }
        else
        {
            fprintf(stderr, "error in getaddrinfo: %s\n", gai_strerror(error));
        }   
        exit(EXIT_FAILURE);
    }

    /* loop over all returned results and do inverse lookup */
    for (res = result; res != NULL; res = res->ai_next)
    {   
        char hostname[NI_MAXHOST];

        error = getnameinfo(res->ai_addr, res->ai_addrlen, hostname, NI_MAXHOST, NULL, 0, 0); 
        if (error != 0)
        {
            fprintf(stderr, "error in getnameinfo: %s\n", gai_strerror(error));
            continue;
        }
        if (*hostname != '\0')
            printf("hostname: %s\n", hostname);
    }

    freeaddrinfo(result);
    return 0;
}

یک مثال دیگر:

int sockfd, error;

struct addrinfo hints, *res, *res0;

const char *cause = NULL;

(void)memset(&hints, '\0', sizeof(struct addrinfo));

hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_socktype = SOCK_STREAM;

error = getaddrinfo(argv[1], argv[2], &hints, &res0);

if (error)
{
	errx(1, "%s", gai_strerror(error));
	/* NOTREACHED */
}
	
sockfd = -1;

for (res = res0; res; res = res->ai_next)
{
	if (-1 == (sockfd = socket(res->ai_family, res->ai_socktype,
	 res->ai_protocol)))
	{
		cause = "socket";
		continue;
	}

	if (-1 == connect(sockfd, res->ai_addr, res->ai_addrlen))
	{
		cause = "connect()";
		close(sockfd);
                sockfd = -1;
		continue;
	}

	break;
	/* NOTREACHED */

}

if (-1 == sockfd)
	perror(cause);
        exit(EXIT_FAILURE);

منابع[ویرایش]

مشارکت‌کنندگان ویکی‌پدیا. «getaddrinfo». در دانشنامهٔ ویکی‌پدیای انگلیسی، بازبینی‌شده در ۲۷ فوریه ۲۰۱۵.