3

UDP ソケットをセットアップして、有効なネットワーク ブロードキャスト アドレス (192.168.202.255 : 23456) をバインドしようとしていbindますが、エラー10049 でWSAEADDRNOTAVAIL失敗します。localhost ブロードキャスト アドレス 127.0.0.255 を使用すると、成功します。

WSAEADDRNOTAVAILのドキュメントには、「要求されたアドレスはそのコンテキストでは有効ではありません。これは通常、ローカル コンピューターに対して無効なアドレスにバインドしようとした結果です。これは、connect、sendto、WSAConnect、WSAJoinLeaf、またはリモート アドレスまたはポートがリモート コンピューターに対して有効でない場合 (たとえば、アドレスまたはポート 0)、WSASendTo。しかし、このアドレス 192.168.202.255 は、実行時に次のエントリがあるため、有効なブロードキャスト アドレスである必要があると思いますipconfig

ipconfig は、ローカル IP が 192.168.202.213 であることを示します

何が問題なのですか?

コード

私は Winsock プログラミングに不慣れで、おそらく初歩的なエラーを犯していますが、それを見つけることができません。私がこれまでに持っているコードは次のとおりです。

m_ulAddress = ParseIPAddress(strAddress);
// Winsock 2.2 is supported in XP
const WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA oWSAData;
const int iError = WSAStartup(wVersionRequested, &oWSAData);
if (iError != 0) {
    PrintLine(L"Error starting the network connection: WSAStartup error " + IntToStr(iError));
} else if (LOBYTE(oWSAData.wVersion) != 2 || HIBYTE(oWSAData.wVersion) != 2) {
    PrintLine(L"Error finding version 2.2 of Winsock; got version " + IntToStr(LOBYTE(oWSAData.wVersion)) + L"." + IntToStr(HIBYTE(oWSAData.wVersion)));
} else {
    m_oSocket = socket(AF_INET, SOCK_DGRAM /*UDP*/, IPPROTO_UDP);
    if (m_oSocket == INVALID_SOCKET) {
        PrintLine(L"Error creating the network socket");
    } else {
        // Socket needs to be able to send broadcast messages
        int iBroadcast = true; // docs say int sized, but boolean values
        if (setsockopt(m_oSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&iBroadcast, sizeof(iBroadcast)) != 0) {
            PrintLine(L"Error setting socket to allow broadcast addresses; error " + IntToStr(WSAGetLastError()));
        } else {
            m_oServer.sin_family = AF_INET;
            m_oServer.sin_port = m_iPort;
            m_oServer.sin_addr.S_un.S_addr = m_ulAddress;

            // !!! This is the failing call
            if (bind(m_oSocket, (sockaddr*)&m_oServer, sizeof(m_oServer)) == -1) {
                PrintLine(L"Error binding address " + String(strAddress.c_str()) + L":" + IntToStr(m_iPort) + L" to socket; error " + IntToStr(WSAGetLastError()));
            } else {
                m_bInitialisedOk = true;
            }
        }
    }
}

コメント

ParseIPAddressのラッパーinet_addrです。その値を調べるm_oServer.sin_addr.S_un.S_addrと正しいようです。 m_oSocketですSOCKETsetsockoptデフォルトでは TCP 以外を介してブロードキャストできないため、 への呼び出しを追加しました(の Remarks の2 番目の段落をsendto参照)。この呼び出しは何の違いもありません。 PrintLineコンソール出力へのラッパーです。私は C++ Builder とその VCL ライブラリを使用しているため、奇妙なString / c_str()キャストは C++ wstring と VCL Unicode 文字列との間で変換されます。IP アドレスは狭い (char) 文字列です。

sendtoドキュメントには、 「ソケットが開かれ、setsockopt 呼び出しが行われ、次に sendto 呼び出しが行われると、Windows ソケットは暗黙的なバインド関数呼び出しを実行する」と記載されています。これは、それbindがまったく必要ないことを意味します。sendto呼び出しを省略すると、次のように呼び出します。

const int iLengthBytes = strMessage.length() * sizeof(char); // Narrow string
    const int iSentBytes = sendto(m_oSocket, strMessage.c_str(), iLengthBytes, 0, (sockaddr*)&m_oServer, sizeof(m_oServer));
    if (iSentBytes != iLengthBytes) {
        PrintLine(L"Error sending network message; error: " + IntToStr(WSAGetLastError()));

は、エラー 10047、WSAEAFNOSUPPORT「アドレス ファミリはプロトコル ファミリでサポートされていません」で失敗します。

の出力(の備考netsh winsock show catalog の下部にsocket記載)は長いですが、UDP と IPv4 に言及しているエントリがいくつか含まれています。

複雑になる可能性があるのは、これが VMWare Fusion ホストで実行されていることです。Fusion には、ネットワークの奇妙な設定があります。また、オフィスに戻って実行する Cisco VPN を構成しています。これを接続して切断しても違いはありません。

SOCKETm_oSocket をにハードキャストすることは、私には危険に思えますsockaddrが、例を読んでいると、これは Winsock プログラミングの通常の方法のようです。基礎となる解釈はプロトコルファミリーに依存するため、それを読む必要があるかもしれません。エラーの潜在的な原因のようですが、それを回避する方法がわかりません。

何か案は?私は困惑しています:)

設定

4

2 に答える 2

8

ここで多くの混乱。教育のためにポイントごとに説明しますが、実際のコードだけが必要な場合は、最後までスキップしてください。

// Winsock 2.2 is supported in XP

実際、Winsock 2.2 は NT 4 SP4さかのぼり、1998 年にさかのぼります。oWSAData.wVersionそのため、エラーケースをわざわざチェックするつもりはありません。基本的に、これが今後発生する可能性はありません。

幅広い移植性が目標である場合は、Winsock 1.1 をターゲットにします。これは、表示するコードに必要なすべてであり、Windows 3.x に戻っても、Winsock をサポートするすべてのものでコードをビルドおよび実行できます。

m_oSocket = socket(AF_INET, SOCK_DGRAM /*UDP*/, IPPROTO_UDP);

スタイルが悪い。PF_INETの代わりにここを使用する必要がありAF_INETます。AF値は同じですが、ここではアドレス ファミリ ( ) を指定していません。プロトコル ファミリ ( PF) を指定しています。また、最初の 2 つのパラメーターによって暗示されているため、3 番目のパラメーターは安全にゼロにすることができます。繰り返しますが、これは単なるスタイルの修正であり、機能の修正ではありません。

int iBroadcast = true; // docs say int sized, but boolean values

うん。ドキュメントを再推測しないで、boolここで使用してください。Winsock は BSD ソケットに基づいており、それは C++ が存在する前の時代にさかのぼることを思い出してください。

m_oServer.sin_addr.S_un.S_addr = m_ulAddress;

sockaddr_inこのように構造の内部を掘り下げるべきではありません。ソケット API にはそのためのショートカットがあり、これはより短く、内部実装の詳細の一部を隠しています。それは:

m_oServer.sin_addr.s_addr = m_ulAddress;

先に進む...

if (bind(m_oSocket, ...

呼び出しが正しくないという Remy の意見はbind()正しいですが、実際にはまったく必要ありません。システムのルーティング層に依存して、パケットを正しいインターフェイスに送信できます。bind()電話で「助ける」必要はありません。

デフォルトでは、TCP 以外を介してブロードキャストすることはできません (sendto の備考の 2 番目の段落を参照してください)。

MSDN が伝えていることを誤解しています。「TCP/IP」という用語を見ると、多くの場合 (常にではありません)、UDP が含まれます。ここでは、一般的な意味で使用しています。

Winsock は、TCP/IP がまだネットワーク プロトコル戦争に勝っていなかった時代に作成されたため、TCP/IP について言及している MSDN ビットです。彼らは議論を TCP/IP (実際には UDP) に制限しようとしているので、彼らの言っていることが初期の Winsock スタックでサポートされていた他のネットワーク トランスポート (NetBIOS、IPX、DECNet) に当てはまるとは思えません。 ...

実際、 UDP ソケットを使用してのみブロードキャスト (またはマルチキャスト) できます。TCP はポイントツーポイントのみです。

私には怪しいと思われることの 1 つは、SOCKET m_oSocket を sockaddr にハードキャストすることです。

これは、ソケットでの複数のネットワーク トランスポート サポートの一部でもあります。に加えてsockaddr_insockaddr_ipxIPXsockaddr_dn用、DECnet 用があります... Winsock は C++ API ではなく C API であるため、サブクラスsockaddr化して基本クラスへの参照を渡したり、バリエーションごとに関数オーバーロードを作成したりすることはできません。構造をキャストするこのトリックは、一種のポリモーフィズムを取得する典型的な C の方法です。

MinGWでビルドされた実際の例を次に示しますg++ foo.cpp -o foo.exe -lwsock32

#include <winsock.h>
#include <iostream>
#include <string.h>

using namespace std;


int main(int argc, char* argv[])
{
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(1, 1), &wsa)) {
        cerr << "Failed to init Winsock!" << endl;
        return 1;
    }

    // Get datagram socket to send message on
    SOCKET sd = socket(PF_INET, SOCK_DGRAM, 0);
    if (sd < 0) {
        cerr << "socket() failed: " << WSAGetLastError() << endl;
        return 1;
    }

    // Enable broadcasts on the socket
    int bAllow = 1;
    if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, (char*)&bAllow,
            sizeof(bAllow)) < 0) {
        cerr << "setsockopt() failed: " << WSAGetLastError() << endl;
        closesocket(sd);
        return 1;
    }

    // Broadcast the request
    string msg = "Hello, world!";
    const int kMsgLen = msg.length();
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    const uint16_t kPort = 54321;
    sin.sin_port = htons(kPort);
    sin.sin_family = AF_INET;
    if (argc == 1) {
        sin.sin_addr.s_addr = INADDR_BROADCAST;
    }
    else if ((sin.sin_addr.s_addr = inet_addr(argv[1])) == INADDR_NONE) {
        cerr << "Couldn't parse IP '" << argv[1] << "'!" << endl;
    }
    int nBytes = sendto(sd, msg.c_str(), kMsgLen, 0,
             (sockaddr*)&sin, sizeof(struct sockaddr_in));
    closesocket(sd);

    // How well did that work out, then?
    if (nBytes < 0) {
        cerr << "sendto() IP " << inet_ntoa(sin.sin_addr) <<
                " failed" << WSAGetLastError() << endl;
        return 1;
    }
    else if (nBytes < kMsgLen) {
        cerr << "WARNING: Short send, " << nBytes << " bytes!  "
                "(Expected " << kMsgLen << ')' << endl;
        return 1;
    }
    else {
        cerr << "Sent " << kMsgLen << "-byte msg to " <<
                inet_ntoa(sin.sin_addr) << ':' << kPort << '.' << endl;
    }

    return 0;
}

デフォルトでは255.255.255.255 ( INADDR_BROADCAST) に送信されますが、ダイレクト ブロードキャスト IP (192.168.202.255 値など) を最初のパラメーターとして渡すと、代わりにそれが使用されます。

于 2013-01-08T23:02:54.767 に答える
5

bind()ブロードキャスト IP アドレスに接続しないでください。bind()代わりに、個々のネットワーク アダプター IP を使用する必要があります。ブロードキャスト メッセージを送信する場合は、ブロードキャストを送信するbind()アダプターに送信し、次にsendto()ブロードキャスト IP に送信します。ブロードキャスト メッセージを受信する場合は、bind()送信先のブロードキャスト IP と一致する IP を持つ特定のアダプターにアクセスします。

于 2013-01-08T22:18:29.827 に答える