我发现以下代码在C中打开连接:
int OpenConnection(const char *hostname, int port)
{
int sd;
struct hostent *host;
struct sockaddr_in addr = {0};
if ((host = gethostbyname(hostname)) == NULL)
{
perror(hostname);
abort();
}
sd = socket(PF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = *(long *)(host->h_addr_list[0]);
if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
{
close(sd);
perror(hostname);
abort();
}
return sd;
}
在我用C ++重写此代码时,我发现我应该使用getaddrinfo
而不是gethostbyname
,这是根据这篇帖子的评论:C - What does *(long *)(host->h_addr); do?。
我看着Beej's guide to socket programming,发现了以下示例:
int status;
struct addrinfo hints;
struct addrinfo *servinfo; // will point to the results
memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
exit(1);
}
// servinfo now points to a linked list of 1 or more struct addrinfos
// ... do everything until you don't need servinfo anymore ....
freeaddrinfo(servinfo); // free the linked-list
但是,此示例适用于创建服务器,这使我感到惊讶
A]应该getaddrinfo
用于客户
和
B]如何修改提示结构和函数参数,以使此代码适合创建客户端?
当前,我有以下代码,但我
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo *client_info;
const int status = getaddrinfo(hostname, port, &hints, &client_info); //don't know if its correct
更新:
对于任何可能觉得有用的人,这是阅读公认的答案后的最终代码:
int OpenConnection(const char *hostname, const char *port)
{
struct hostent *host;
if ((host = gethostbyname(hostname)) == nullptr)
{
//More descriptive error message?
perror(hostname);
exit(EXIT_FAILURE);
}
struct addrinfo hints = {0}, *addrs;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
const int status = getaddrinfo(hostname, port, &hints, &addrs);
if (status != 0)
{
fprintf(stderr, "%s: %s\n", hostname, gai_strerror(status));
exit(EXIT_FAILURE);
}
int sfd, err;
for (struct addrinfo *addr = addrs; addr != nullptr; addr = addr->ai_next)
{
sfd = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
if (sfd == ERROR_STATUS)
{
err = errno;
continue;
}
if (connect(sfd, addr->ai_addr, addr->ai_addrlen) == 0)
{
break;
}
err = errno;
sfd = ERROR_STATUS;
close(sfd);
}
freeaddrinfo(addrs);
if (sfd == ERROR_STATUS)
{
fprintf(stderr, "%s: %s\n", hostname, strerror(err));
exit(EXIT_FAILURE);
}
return sfd;
}
答案 0 :(得分:6)
自“永远”以来,我一直使用gethostbyname()。它始终有效,并且继续有效,而且“更简单”。
getaddrinfo()是较新的功能:
http://man7.org/linux/man-pages/man3/getaddrinfo.3.html
getaddrinfo()函数结合了 gethostbyname(3)和getservbyname(3)提供的功能 功能集成到单个界面中,但与后者不同, getaddrinfo()是可重入的,并允许程序消除 IPv4-vs-IPv6依赖性。
我了解到getaddrinfo()更健壮,更高效,更安全:You shouldn't be using gethostbyname() anyway
附录:
回答您的特定问题:
A] getaddrinfo()
比gethostbyname()
优先查找主机名的IP地址; “客户端”或“服务器”。
B]问:如何修改提示结构和函数参数?
A:“提示”看起来不错,但我可能会将端口修改为NULL。
这是一个完整的例子:
https://www.kutukupret.com/2009/09/28/gethostbyname-vs-getaddrinfo/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
struct addrinfo hints, *res, *p;
int status;
char ipstr[INET6_ADDRSTRLEN];
if (argc != 2) {
fprintf(stderr, "Usage: %s hostname\n", argv[0]);
return 1;
}
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
hints.ai_socktype = SOCK_STREAM;
if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
return 2;
}
for(p = res;p != NULL; p = p->ai_next) {
void *addr;
if (p->ai_family == AF_INET) {
return 1;
} else {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
/* convert the IP to a string and print it: */
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
printf("Hostname: %s\n", argv[1]);
printf("IP Address: %s\n", ipstr);
}
}
freeaddrinfo(res); // free the linked list
return 0;
}
答案 1 :(得分:5)
gethostbyname()
和gethostbyaddr()
函数在大多数平台上已被弃用,并且它们未实现对IPv6的支持。 IPv4已经达到了极限,世界已经过渡到IPv6一段时间了。分别使用getaddrinfo()
和getnameinfo()
。
要回答您的问题:
A。 getaddrinfo()
和getnameinfo()
可以用于客户端和服务器,就像gethostbyname()
和gethostbyaddr()
一样。它们只是主机/地址解析功能,解析值的使用方式取决于调用应用程序。
B。使用getaddrinfo()
的客户端代码如下所示:
int OpenConnection(const char *hostname, int port)
{
int sd, err;
struct addrinfo hints = {}, *addrs;
char port_str[16] = {};
hints.ai_family = AF_INET; // Since your original code was using sockaddr_in and
// PF_INET, I'm using AF_INET here to match. Use
// AF_UNSPEC instead if you want to allow getaddrinfo()
// to find both IPv4 and IPv6 addresses for the hostname.
// Just make sure the rest of your code is equally family-
// agnostic when dealing with the IP addresses associated
// with this connection. For instance, make sure any uses
// of sockaddr_in are changed to sockaddr_storage,
// and pay attention to its ss_family field, etc...
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
sprintf(port_str, "%d", port);
err = getaddrinfo(hostname, port_str, &hints, &addrs);
if (err != 0)
{
fprintf(stderr, "%s: %s\n", hostname, gai_strerror(err));
abort();
}
for(struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next)
{
sd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (sd == -1)
{
err = errno;
break; // if using AF_UNSPEC above instead of AF_INET/6 specifically,
// replace this 'break' with 'continue' instead, as the 'ai_family'
// may be different on the next iteration...
}
if (connect(sd, addr->ai_addr, addr->ai_addrlen) == 0)
break;
err = errno;
close(sd);
sd = -1;
}
freeaddrinfo(&addrs);
if (sd == -1)
{
fprintf(stderr, "%s: %s\n", hostname, strerror(err));
abort();
}
return sd;
}