getaddrinfo的使用方法

本篇文章中讨论socket编程中的getaddrinfo这个function。man page中对getaddrinfo的说明如下:

NAME

     getaddrinfo, freeaddrinfo -- socket address structure to host and service
     name

SYNOPSIS

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

     int
     getaddrinfo(const char *hostname, const char *servname,
         const struct addrinfo *hints, struct addrinfo **res);

     void
     freeaddrinfo(struct addrinfo *ai);

DESCRIPTION

     The getaddrinfo() function is used to get a list of IP addresses and port
     numbers for host hostname and service servname.  It is a replacement for
     and provides more flexibility than the gethostbyname(3) and
     getservbyname(3) functions.



其中struct addrinfo的定义如下:

struct addrinfo {
    int     ai_flags;
    int     ai_family;
    int     ai_socktype;
    int     ai_protocol;
    size_t  ai_addrlen;
    struct  sockaddr *ai_addr;
    char    *ai_canonname;     /* canonical name */
    struct  addrinfo *ai_next; /* this struct can form a linked list */
};


我们可以很灵活地使用addrinfo这个struct。而Ruby对这个struct以及getaddrinfo有很棒的封装,并且使用起来比c的实现更省事,所以我们先讨论Ruby的封装。首先是这段代码:

power:~ weinanli$ ruby -v
ruby 2.0.0p598 (2014-11-13 revision 48408) [x86_64-darwin14.0.0]
power:~ weinanli$ irb
2.0.0-p598 :001 > require 'socket'
 => true
2.0.0-p598 :002 > Addrinfo.foreach(nil, 80) {|x| p x }
#<Addrinfo: [::1]:80 UDP>
#<Addrinfo: [::1]:80 TCP>
#<Addrinfo: 127.0.0.1:80 UDP>
#<Addrinfo: 127.0.0.1:80 TCP>
 => [#<Addrinfo: [::1]:80 UDP>, #<Addrinfo: [::1]:80 TCP>, #<Addrinfo: 127.0.0.1:80 UDP>, #<Addrinfo: 127.0.0.1:80 TCP>]
2.0.0-p598 :003 >


可以看到Addrinfo是同时支持ipv4和ipv6的。从上面的代码可以看到,Addrinfo可以封装socket的地址信息,上面我们只是指定端口号为80,因此Addrinfo列出了4种可能:分别是ipv4/ipv6的tcp及udp的端口80。

接下来我们可以看看Ruby封装的getaddrinfo方法:

getaddrinfo(nodename, service, family, socktype, protocol, flags) => [addrinfo, ...] click to toggle source
getaddrinfo(nodename, service, family, socktype, protocol) => [addrinfo, ...]
getaddrinfo(nodename, service, family, socktype) => [addrinfo, ...]
getaddrinfo(nodename, service, family) => [addrinfo, ...]
getaddrinfo(nodename, service) => [addrinfo, ...]


以下是一些使用的例子:

power:~ weinanli$ irb
2.0.0-p598 :001 > require 'socket'
 => true


表示localhost udp的80端口:

2.0.0-p598 :005 > Addrinfo.getaddrinfo("localhost", 80, nil, :DGRAM)
 => [#<Addrinfo: [::1]:80 UDP (localhost)>, #<Addrinfo: 127.0.0.1:80 UDP (localhost)>, #<Addrinfo: [fe80::1%lo0]:80 UDP (localhost)>]


表示localhost tcp的80端口:

2.0.0-p598 :007 > Addrinfo.getaddrinfo("localhost", 80, nil, :STREAM)
 => [#<Addrinfo: [::1]:80 TCP (localhost)>, #<Addrinfo: 127.0.0.1:80 TCP (localhost)>, #<Addrinfo: [fe80::1%lo0]:80 TCP (localhost)>]


表示localhost ipv4 tcp的80端口:

2.0.0-p598 :008 > Addrinfo.getaddrinfo("localhost", 80, :AF_INET, :STREAM)
 => [#<Addrinfo: 127.0.0.1:80 TCP (localhost)>]


表示localhost ipv6 tcp的80端口:

2.0.0-p598 :010 > Addrinfo.getaddrinfo("localhost", 80, :AF_INET6, :STREAM)
 => [#<Addrinfo: [::1]:80 TCP (localhost)>, #<Addrinfo: [fe80::1%lo0]:80 TCP (localhost)>]


直接表示tcp socket:

2.0.0-p598 :021 > Addrinfo.tcp("127.0.0.1", 2000)
 => #<Addrinfo: 127.0.0.1:2000 TCP>
2.0.0-p598 :022 > Addrinfo.tcp("localhost", 2000)
 => #<Addrinfo: [::1]:2000 TCP (localhost)>


接下来我们写一个简单的tcp服务器:

require 'socket'

server = TCPServer.new 2000 # Server bound to port 2000

loop do
  client = server.accept # Wait for a client to connect
  client.puts "Hello, Martian!"
  client.close
end


然后我们可以用Addrinfo来绑定这个服务端地址并进行访问:

require 'socket'

Addrinfo.tcp("127.0.0.1", 2000).connect do |s|
  p s.recv(50)
end



以下是代码实际执行情况:


getaddrinfo的使用方法_第1张图片


getaddrinfo的使用方法_第2张图片


理解了Ruby对Addrinfo的封装后,我们可以写一个C的客户端代码:

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <fcntl.h> // for open
#include <unistd.h> // for close
#include <string.h>
#include <err.h>
#include <stdio.h>

int main(void) {
    struct addrinfo hints, *res, *res0;
    int error;
    int s;
    const char *cause = NULL;
    char buffer[50];

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    error = getaddrinfo("127.0.0.1", "2000", &hints, &res0);
    if (error) {
        errx(1, "%s", gai_strerror(error));
/*NOTREACHED*/
    }
    s = -1;
    for (res = res0; res; res = res->ai_next) {
        s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (s < 0) {
            warn("%s,", "socket");
            continue;
        }

        if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
            warn("%s", "connect");
            close(s);
            continue;
        } else {
            read(s, buffer, 50);
            printf("%s\n", buffer);
            close(s);
            break;
        }
    }
    freeaddrinfo(res0);
}



以下是代码的执行情况:


getaddrinfo的使用方法_第3张图片

你可能感兴趣的:(c,socket,Ruby)