5.必要的系统调用
这一节我们开始接触那些允许你访问网络功能的系统调用。当你在UNIX或其它支持套接字API的系统上(BSD,Windows,Linux,Mac等等)调用这些功能时系统会自动为你做好所有的工作。
因为不知道按什么顺序调用,许多人到这里非常困惑。你可能已经发现了我没有使用完整的例子。好的,为了解决这个问题我试着按在程序中调用的顺序来按排章节。
(请注意,例子中的代码都不健壮,因为几乎没有错误检查。请你把这些代码只当框架使用。)
5.1.getaddrinfo()------准备开始
这确是个有大堆选项的复杂函数,但用法简单,它用来建立一些后面要使用的结构体。
以前:如果要用gethostbyname()做DNS查询,你需要在使用前手动填充sockaddr_in结构
现在已经不需要了,你有了函数getaddrinfo()它为你做许多事情,包括DNS和服务查询并且会填充你需要的结构体。
看一下!
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo( const char *node, //eg. www.example.com or IP
const char *service, //eg. “http” or port number
const struct addrinfo * hints,
struct addrinfo ** res);
你给这个函数3个参数,它返回一个链表指针res,参数node是要连接的一个主机名称或一个IP地址,参数Service是一个端口号例如“80”,或是一个公知的服务名称像“http”或“ftp”或“telnet”或“smtp”等等。最后,hints参数指向你填写好相关的addrinfo结构体。
这里有一个简单的调用,假设你是服务器想要监听本机IP地址3490端口。注意这里没有监听操作;仅仅是准备后面要用到的结构体。
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(stdder, “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.
注意:我设置ai_family为AF_UNSPEC,就是说我不需要关心使用IPv4还是IPv6。你可以设置为AF_INET或AF_INET6。
你也看到了AI_PASSIVE标记;这是告诉getaddrinfo()把本机的IP地址填充到套接字结构。这是很有用的,你不用手动做这些工作了(或者你可以在第一个参数处填写特殊地址,上面写的是NULL)。
我们调用getaddrinfo()如果有错误发生,可以使用gai_strerror()打印出错误原因来。如果正常servinfo将指向一个链表结构addrinfo,每个结点有一个sockaddr结构,我们在后面可以使用。最后,当我们处理完所有的工作后,我们可以(也需要)用freeaddrinfo()释放它。
这里有个例子:假设你是想通过www.example.net端口”3490”连接特定的服务器的客户机。重复,这并不是实际的进行了连接,但是为我们准备了后面要用到的结构。
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
//get read to connect
Status = getaddrinfo(www.example.net, “3490”, &hints, &servinfo);
//servinfo now point to a linked list of 1 or more struct addrinfos
// etc.
要强调的一点是servinfo是一个链表包含有许多地址信息,让我们写一个样例显示这些信息。这个短程序将打印出你在命令行中键入的主机的IP地址。
/*
** showip.c -- show IP addresses for a host given on the command line
*/
#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: showip hostname/n");
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;
}
printf("IP addresses for %s:/n/n", argv[1]);
for(p = res;p != NULL; p = p->ai_next) {
void *addr;
char *ipver;
// get the pointer to the address itself,
// different fields in IPv4 and IPv6:
if (p->ai_family == AF_INET) { // IPv4
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else { // IPv6
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
}
// convert the IP to a string and print it:
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
printf(" %s: %s/n", ipver, ipstr);
}
freeaddrinfo(res); // free the linked list
return 0;
}
正像你看到的,代码调用getaddrinfo(),不论命令行传递什么内容,填充好res结构后,我们就可以遍历链表并打印出内容或进行别的操作。
运行一下例子!每个人都喜欢屏幕输出。
$ showip www.example.net
IP addresses for www.example.net:
IPv4: 192.0.2.88
$ showip ipv6.example.com
IP addresses for ipv6.example.com:
IPv4: 192.0.2.101
IPv6: 2001:db8:8c00:22::171
在我们的控制下,我们得到了getaddrinfo()的返回值并传递给了另外的套接字,我们将一步步的建立我们的连接。继续阅读。