本篇文章中讨论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
以下是代码实际执行情况:
理解了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);
}
以下是代码的执行情况: