连接IPv4地址时,C++ winsock连接被拒绝

4
我正在使用winsock2编写一个服务器/客户端系统,在将客户端连接到服务器名称服务器IPv6地址时,它运行良好。然而,当我使用服务器IPv4地址时,在客户端的connect()调用中会出现"连接被拒绝"的错误。
这个错误在使用telnet或我的客户端时都会发生。但是,我可以成功地使用三种方式之一(名称、IPv4或IPv6)对服务器进行ping测试。
我已经尝试了在同一台机器上运行服务器和客户端、在不同的机器上运行它们以及关闭所有机器的防火墙等方法。
以下是我的服务器初始化和监听代码摘录:
SOCKET sockfd = INVALID_SOCKET, in_socketID;
struct addrinfo hints;
struct addrinfo *servinfo = NULL;
struct addrinfo *p;
struct addrinfo *ip;
sockaddr_storage incoming_addr;
int addr_size;
int tmp_err;
const char *sPort = "20152";

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // either IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;

tmp_err = getaddrinfo(NULL, sPort, &hints, &servinfo);
if (tmp_err != 0)
    throw exception("ERROR: getaddrinfo failed");

// loop through all the results and bind to the first we can
for(p = servinfo; p != NULL && sockfd == INVALID_SOCKET; p = p->ai_next)
{
    ip = p;
    sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    if (sockfd == INVALID_SOCKET)
    {
        cerr << "ERROR on socket(): " << WSAGetLastError() << endl;
    } // end if
    else if (bind(sockfd, p->ai_addr, p->ai_addrlen) == SOCKET_ERROR)
    {
        cerr << "ERROR on bind(): " << WSAGetLastError() << endl;
        closesocket(sockfd);
        sockfd = INVALID_SOCKET;
    } // end if
} // end for

if (sockfd == INVALID_SOCKET)
{
    // looped off the end of the list with no successful bind
    throw exception("ERROR: Failed to bind socket");
}

// clean up
if (servinfo)
    freeaddrinfo(servinfo);

if (listen(sockfd, SOMAXCONN ) == SOCKET_ERROR)
    throw exception("Listen failed");

while (true)
{
    memset(&incoming_addr, 0, sizeof(incoming_addr));
    addr_size = sizeof(incoming_addr);
    in_socketID = accept(socketID, (sockaddr *)&incoming_addr, &addr_size);

    // do stuff with incoming connection
}

这是我的客户端代码:
int sockfd = INVALID_SOCKET;
struct addrinfo hints;
struct addrinfo *servinfo = NULL;
struct addrinfo *p;
struct addrinfo *ip;
int tmp_err;
const char *sHost = "192.168.1.136";
const char *sPort = "20152";

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // either IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // use TCP

tmp_err = getaddrinfo(sHost,        // web address or ip to connect to
                      sPort,        // port or protocol
                      &hints,       // initialized hints structure
                      &servinfo);   // return structure
if (tmp_err != 0)
    throw exception("ERROR: getaddrinfo failed");

// loop through all the results and connect to the first we can
for(p = servinfo; p != NULL && sockfd == INVALID_SOCKET; p = p->ai_next)
{
    ip = p;
    sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    if (sockfd == INVALID_SOCKET)
    {
        cerr << "ERROR on socket(): " << WSAGetLastError() << endl;
        //continue;
    } // end if
    else if (connect(sockfd, p->ai_addr, p->ai_addrlen) < 0)
    {
        cerr << "ERROR on connect(): " << WSAGetLastError() << endl;
        closesocket(sockfd);
        sockfd = INVALID_SOCKET;
        //continue;
    } // end if
} // end for

if (sockfd == INVALID_SOCKET)
    throw exception("ERROR: Failed to connect");


// clean up
if (servinfo)
    freeaddrinfo(servinfo);

// do stuff with new socket

我已经在网站上阅读了几个类似的问题,但都没有回答这个问题。

我怎样才能连接到服务器的IPv4地址?请帮忙,谢谢。

谢谢。

编辑:

根据用户Sorayuki的建议,我进行了一些更改,只是为了测试他的理论是否正确。

我成功地通过在服务器上进行更改来连接到IPv4。

hints.ai_family = AF_UNSPEC;

为了

hints.ai_family = AF_INET;

我知道这肯定能用,但是当我这么做时,IPv6当然就不能使用了。
看起来用户Sorayuki是对的,我的循环正在连接到IPv6。
似乎没有简单的方法来统一IPv6和IPv4。您的套接字必须监听其中一个,使整个过程非常烦人。
根据文档,旧版本同时监听IPv4和IPv6的方法是为每个创建一个套接字并监听两个。这是针对Windows Server 2003和Windows XP SP1。
首选的现代方法(适用于Windows Vista、7和8)是将您的套接字转换为双重套接字,它将同时监听IPv4和IPv6。然而,您的客户端也必须能够设置双重套接字,因此,如果您的应用程序正在提供服务给老客户端,那么您就必须使用旧方法。
谢谢!
3个回答

3
这是因为绑定IPv6地址并不能自动绑定IPv4地址。
在Linux上,默认绑定[::]会导致IPv6和IPv4同时工作(除非/proc/sys/net/ipv6/bindv6only设置为1)。
然而,在Mac OS X和Windows上,绑定[::]只能用于IPv6。你还必须绑定IPv4地址(或0.0.0.0)才能使其工作。
你在评论中描述的逻辑“遍历所有结果并绑定到第一个可行的地址”恰好就是这里的问题。你应该使用IPV6_V6ONLY标志(参见setsockopt())同时绑定[::]0.0.0.0

是的,我明白了。谢谢您的回答 :)。我已经修复了我的代码,现在它可以工作了。 - sidezr
没问题。请使用勾选标记此答案为正确的,谢谢。 - Jeremy Visser

2

是否因为您将服务器套接字绑定到IPv6地址?在“for”循环中,IPv6地址出现在IPv4地址之前似乎会导致您的服务器套接字侦听IPv6地址。因此,您的服务器不侦听任何IPv4地址,因为对服务器的所有IPv4地址的连接都被拒绝。

尝试使用工具或某些命令(例如netstat)查看所有侦听端口所在的IP地址。


-1

你尝试过在同一台机器上运行服务器和客户端吗?

这听起来像是防火墙问题。如果你成功连接了telnet/你的应用程序,那么你就知道这是问题所在了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接