3

winsock2 を使用してサーバー/クライアント システムをプログラミングしていますが、クライアントをサーバー名またはサーバー IPv6 アドレスに接続するとうまく機能します。ただし、サーバーの IPv4 アドレスを使用すると、クライアントの connect() への呼び出しから「接続が拒否されました」というエラーが表示されます。

このエラーは、クライアントまたは telnet を使用している場合に発生します。ただし、IPv4 または IPv6 の 3 つの名前のいずれかを使用して、サーバーに正常に 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 の両方をリッスンします。ただし、クライアントもデュアル ソケットをセットアップできる必要があるため、アプリケーションが古いクライアントにサービスを提供している場合、古い方法にとらわれてしまいます。

ありがとう!

4

3 に答える 3

3

これは、IPv6 アドレスへのバインドが IPv4 アドレスへの魔法のようなバインドではないためです。

Linux では、デフォルトで にバインドする[::]と、IPv6 と IPv4 が機能します (/proc/sys/net/ipv6/bindv6onlyが に設定されている場合を除く1)。

ただし、Mac OS X および Windows では、へのバインド[::]は IPv6 に対してのみ機能します。動作させるには、IPv4 アドレス (または ) にもバインドする必要があります0.0.0.0

あなたのコメントで説明されているロジックは、「すべての結果をループして、最初にできるものにバインドします」がまさにここでの問題です。フラグ ( を参照) と[::]の両方をバインドする必要があります。IPV6_V6ONLYsetsockopt()0.0.0.0

于 2013-04-21T20:28:56.123 に答える
1

サーバーソケットをIPv6アドレスにバインドしているためですか? 「for」ループでは、IPv4 アドレスの前に IPv6 アドレスが表示されているため、サーバーのソケットが IPv6 アドレスをリッスンしているようです。したがって、サーバーはIPv4アドレスをリッスンしていないため、サーバーのIPv4アドレスへのすべての接続が拒否されます.

ツールまたは何らかのコマンド (netstat など) を使用して、すべてのリッスン ポートがどの IP アドレスにあるかを確認してください。

于 2013-04-21T08:13:50.860 に答える
-1

サーバーとクライアントを同じマシンで実行しようとしましたか?

これはファイアウォールの問題のようです。同じマシン上で telnet とアプリケーションの接続に成功した場合、これが問題であることがわかります。

于 2013-04-21T08:09:14.883 に答える