面试题答案
一键面试实现思路
- 通用地址结构:使用
sockaddr_storage
来存储地址,它足够大可以容纳sockaddr_in
(IPv4)和sockaddr_in6
(IPv6)。 - 地址初始化:根据传入的地址类型(IPv4 或 IPv6),分别初始化
sockaddr_in
或sockaddr_in6
,再赋值给sockaddr_storage
。 - 套接字操作:使用
getaddrinfo
函数来解析主机名和服务名,它可以同时处理 IPv4 和 IPv6 地址。这个函数返回的addrinfo
结构链表包含了不同协议族的地址信息。 - 兼容性处理:使用条件编译(
#ifdef
)来处理不同平台下可能存在的差异,确保代码在各种支持 IPv4 和 IPv6 的操作系统上都能正常工作。 - 性能优化:尽量减少不必要的地址转换和协议切换操作。如果应用程序对性能要求极高,可以在初始化阶段确定主要使用的地址版本(如根据配置文件或网络环境),优先使用该版本进行通信,只有在必要时才切换到另一个版本。
关键代码
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
// 函数用于创建套接字并连接到服务器
int createSocketAndConnect(const std::string& host, const std::string& port) {
struct addrinfo hints;
struct addrinfo *result, *rp;
int sfd;
// 初始化hints结构体
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; // 允许IPv4或IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = 0;
hints.ai_protocol = 0;
// 获取地址信息
int s = getaddrinfo(host.c_str(), port.c_str(), &hints, &result);
if (s != 0) {
std::cerr << "getaddrinfo: " << gai_strerror(s) << std::endl;
return -1;
}
// 遍历结果链表,尝试连接
for (rp = result; rp != nullptr; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1)
continue;
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
break; // 成功连接
close(sfd);
}
if (rp == nullptr) { // 没有成功连接
std::cerr << "Could not connect" << std::endl;
return -1;
}
freeaddrinfo(result);
return sfd;
}
你可以这样调用上述函数:
int main() {
std::string host = "example.com";
std::string port = "80";
int sockfd = createSocketAndConnect(host, port);
if (sockfd != -1) {
std::cout << "Connected successfully. Socket fd: " << sockfd << std::endl;
// 在这里进行后续的读写操作
close(sockfd);
}
return 0;
}
上述代码通过 getaddrinfo
函数获取主机的 IPv4 或 IPv6 地址信息,并尝试连接到指定端口。AF_UNSPEC
使得代码可以同时处理两种地址类型。在实际应用中,还需根据具体需求完善错误处理和数据传输部分的代码。