下面是一个实现双栈网络基础设施的C++服务器的最小工作示例。(这意味着单个套接字同时处理ipv4和ipv6连接。)
#include <format>
#include <print>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
const auto SERVER_PORT = 7778;
const auto server_fd = socket(AF_INET6, SOCK_STREAM, 0);
int opt = 0;
setsockopt(server_fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
sockaddr_in6 server_address;
std::memset(&server_address, 0, sizeof(server_address));
server_address.sin6_family = AF_INET6;
server_address.sin6_addr = in6addr_any;
server_address.sin6_port = htons(SERVER_PORT);
bind(server_fd, reinterpret_cast<sockaddr*>(&server_address), sizeof(server_address));
listen(server_fd, 10);
sockaddr_storage peer_address;
socklen_t peer_address_length = sizeof(peer_address);
auto peer_fd = accept(server_fd, reinterpret_cast<sockaddr*>(&peer_address), &peer_address_length);
if(peer_address.ss_family == AF_INET)
{
const auto p_peer_address = &peer_address;
sockaddr_in* ipv4 = (sockaddr_in*)p_peer_address;
std::println("Client port (IPv4): {}", ntohs(ipv4->sin_port));
}
else if(peer_address.ss_family == AF_INET6)
{
const auto p_peer_address = &peer_address;
sockaddr_in6* ipv6 = (sockaddr_in6*)p_peer_address;
std::println("Client port (IPv6): {}", ntohs(ipv6->sin6_port));
}
else
{
throw std::runtime_error("unrecognized ss_family");
}
close(peer_fd);
close(server_fd);
return 0;
}
然而,它并没有像预期的那样工作。无论客户端是通过ipv4还是ipv6连接,逻辑始终遵循
else if
分支
if
声明。第一个
如果
分支从不运行,表示并没有启动任何连接
AF_INET
家庭。
实际上,这两个客户端似乎都正常工作。当连接启动时,服务器会做出响应——无论客户端连接类型如何,只会打印ipv6消息。(ipv4或ipv6)
如果有区别,客户端和服务器都在同一台机器上运行,通过localhost连接(
127.0.0.1
)
下面提供了这两种客户端的示例C++代码。
#include <format>
#include <print>
#include <format>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
const auto PORT = 7778;
const auto socket_fd = socket(AF_INET6, SOCK_STREAM, 0);
in6_addr server_sin6_address;
std::memset(&server_sin6_address, 0, sizeof(server_sin6_address));
inet_pton(AF_INET6, "::1", &server_sin6_address);
sockaddr_storage server_address;
std::memset(&server_address, 0, sizeof(server_address));
server_address.ss_family = AF_INET6;
sockaddr_storage *p_server_address = &server_address;
sockaddr_in6 *p_server_address_in6 = reinterpret_cast<sockaddr_in6*>(p_server_address);
p_server_address_in6->sin6_family = AF_INET6;
p_server_address_in6->sin6_port = htons(PORT);
p_server_address_in6->sin6_flowinfo = 0;
p_server_address_in6->sin6_addr = server_sin6_address;
p_server_address_in6->sin6_scope_id = 0;
const auto connect_result = connect(socket_fd, reinterpret_cast<sockaddr*>(&server_address), sizeof(server_address));
const char* const buffer = "hello world ipv6";
const auto buffer_length = strlen(buffer) + 1;
send(socket_fd, buffer, buffer_length, 0);
close(socket_fd);
return 0;
}
#include <format>
#include <print>
#include <format>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
const auto PORT = 7778;
const auto socket_fd = socket(AF_INET, SOCK_STREAM, 0);
in_addr server_sin_address;
std::memset(&server_sin_address, 0, sizeof(server_sin_address));
inet_pton(AF_INET, "127.0.0.1", &server_sin_address);
sockaddr_storage server_address;
std::memset(&server_address, 0, sizeof(server_address));
server_address.ss_family = AF_INET;
sockaddr_storage *p_server_address = &server_address;
sockaddr_in *p_server_address_in = reinterpret_cast<sockaddr_in*>(p_server_address);
p_server_address_in->sin_family = AF_INET;
p_server_address_in->sin_port = htons(PORT);
p_server_address_in->sin_addr = server_sin_address;
const auto connect_result = connect(socket_fd, reinterpret_cast<sockaddr*>(&server_address), sizeof(server_address));
const char* const buffer = "hello world ipv4";
const auto buffer_length = strlen(buffer) + 1;
const auto send_result = send(socket_fd, buffer, buffer_length, 0);
close(socket_fd);
std::println("Server quit");
return 0;
}
也许这是ipv4客户端实现中的一个错误,但到目前为止,我没有发现任何看起来明显不正确的东西。
到ipv6目标的ipv4连接是否会被操作系统或其他东西自动升级?这看起来真的很奇怪。