11.4.8 套接字接口的辅助函数
11.4.8 套接字接口的辅助函数
初学时,getnameinfo 函数和套接字接口看上去有些可怕。用高级的辅助函数包装一下会方便很多,称为 open_clientfd 和 open_listenfd,客户端和服务器互相通信时可以使用这些函数。
1. open_clientfd 函数
客户端调用 open_clientfd 建立与服务器的连接。
#include "csapp.h"
int open_clientfd(char *hostname, char *port);返回: 若成功则为描述符,若出错则为 -1。
open_clientfd 函数建立与服务器的连接,该服务器运行在主机 hostname 上,并在端口号 port 上监听连接请求。它返回一个打开的套接字描述符,该描述符准备好了,可以用 Unix I/O 函数做输入和输出。图 11-18 给出了 open_clientfd 的代码。
我们调用 getaddrinfo,它返回 addrinfo 结构的列表,每个结构指向一个套接字地址结构,可用于建立与服务器的连接,该服务器运行在 hostname 上并监听 port 端口。然后遍历该列表,依次尝试列表中的每个条目,直到调用 socket 和 connect 成功。如果 connect 失败,在尝试下一个条目之前,要小心地关闭套接字描述符。如果 connect 成功,我们会释放列表内存,并把套接字描述符返回给客户端,客户端可以立即开始用 Unix I/O 与服务器通信了。
注意,所有的代码都与任何版本的 IP 无关。socket 和 connect 的参数都是用 getaddrinfo 自动产生的,这使得我们的代码干净可移植。
2. open_listenfd 函数
调用 open_listenfd 函数,服务器创建一个监听描述符,准备好接收连接请求。
#include "csapp.h"
int open_listenfd(char *port);返回: 若成功则为描述符,若出错则为 -1。
code/src/csapp.c
int open_clientfd(char *hostname, char *port) {
int clientfd;
struct addrinfo hints, *listp, *p;
/* Get a list of potential server addresses */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; /* Open a connection */
hints.ai_flags = AI_NUMERICSERV; /* ... using a numeric port arg. */
hints.ai_flags |= AI_ADDRCONFIG; /* Recommended for connections */
Getaddrinfo(hostname, port, &hints, &listp);
/* Walk the list for one that we can successfully connect to */
for (p = listp; p; p = p->ai_next) {
/* Create a socket descriptor */
if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))
< 0) continue; /* Socket failed, try the next */
/* Connect to the server */
if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)
break; /* Success */
Close(clientfd); /* Connect failed, try another */
}
/* Clean up */
Freeaddrinfo(listp);
if (!p) /* All connects failed */
return -1;
else /* The last connect succeeded */
return clientfd;
}图 11-18 open_clientfd:和服务器建立连接的辅助函数。它是可重入和与协议无关的
open_listenfd 函数打开和返回一个监听描述符,这个描述符准备好在端口 port 上接收连接请求。图 11-19 展示了 open_listenfd 的代码。
open_listenfd 的风格类似于 open_clientfd。调用 getaddrinfo,然后遍历结果列表,直到调用 socket 和 bind 成功。注意,在第 20 行,我们使用 setsockopt 函数(本书中没有讲述)来配置服务器,使得服务器能够被终止、重启和立即开始接收连接请求。一个重启的服务器默认将在大约 30 秒内拒绝客户端的连接请求,这严重地阻碍了调试。
因为我们调用 getaddrinfo 时,使用了 AI_PASSIVE 标志并将 host 参数设置为 NULL,每个套接字地址结构中的地址字段会被设置为通配符地址,这告诉内核这个服务器会接收发送到本主机所有 IP 地址的请求。
code/src/csapp.c
int open_listenfd(char *port)
{
struct addrinfo hints, *listp, *p;
int listenfd, optval=1;
/* Get a list of potential server addresses */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; /* Accept connections */
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */
hints.ai_flags |= AI_NUMERICSERV; /* ... using port number */
Getaddrinfo(NULL, port, &hints, &listp);
/* Walk the list for one that we can bind to */
for (p = listp; p; p = p->ai_next) {
/* Create a socket descriptor */
if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))
< 0) continue; /* Socket failed, try the next */
/* Eliminates "Address already in use" error from bind */
Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval, sizeof(int));
/* Bind the descriptor to the address */
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break; /* Success */
Close(listenfd); /* Bind failed, try the next */
}
/* Clean up */
Freeaddrinfo(listp);
if (!p) /* No address worked */
return -1;
/* Make it a listening socket ready to accept connection requests */
if (listen(listenfd, LISTENQ) < 0) {
Close(listenfd);
return -1;
}
return listenfd;
}图 11-19 open_listenfd:打开并返回监听描述符的辅助函数。它是可重入和与协议无关的
最后,我们调用 listen 函数,将 listenfd 转换为一个监听描述符,并返回给调用者。如果 listen 失败,我们要小心地避免内存泄漏,在返回前关闭描述符。