8

ConnectEx関数には、「接続されていない、以前にバインドされたソケット」が必要です。実際、私の例でバインド手順を省略した場合 (以下を参照)、ConnectExはWSAEINVALで失敗します。

これが私の現在の理解です: ConnectExを呼び出す前に、ソケットをポート 0にバインドINADDR_ANYします(既にバインドされていない場合):

struct sockaddr_in addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = 0;
rc = bind(sock, (SOCKADDR*) &addr, sizeof(addr));
if (rc != 0) { ... bind failed; call WSAGetLastError to see why ... }

または IPv6 ソケットの場合:

struct sockaddr_in6 addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any;
addr.sin6_port = 0;
rc = bind(sock, (SOCKADDR*) &addr, sizeof(addr));
if (rc != 0) { ... bind failed; call WSAGetLastError to see why ... }

これにより、オペレーティング システムはローカル アドレスをソケットに割り当てることができます (接続先のリモート アドレスとは対照的です)。 connectはこのステップを自動的に行いますが、ConnectExは行いません。

私の質問は次のとおりです。

  1. 私の評価は正しいですか?

  2. アドレスファミリに依存しないこの自動バインドを行う方法はありますか、または、、、(Bluetooth) などを手動で処理するAF_INET必要がAF_INET6ありますか?AF_BTH

ConnectEx の動作例 (Gist にもあります: https://gist.github.com/4158972 ):

#include <stdio.h>
#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>

#pragma comment(lib, "Ws2_32.lib")

struct mswsock_s {
    LPFN_CONNECTEX ConnectEx;
} mswsock;

static BOOL load_mswsock(void)
{
    SOCKET sock;
    DWORD dwBytes;
    int rc;

    /* Dummy socket needed for WSAIoctl */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET)
        return FALSE;

    {
        GUID guid = WSAID_CONNECTEX;
        rc = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
                      &guid, sizeof(guid),
                      &mswsock.ConnectEx, sizeof(mswsock.ConnectEx),
                      &dwBytes, NULL, NULL);
        if (rc != 0)
            return FALSE;
    }

    rc = closesocket(sock);
    if (rc != 0)
        return FALSE;

    return TRUE;
}

int main(int argc, char *argv[])
{
    int rc;
    BOOL ok;
    WSADATA wsaData;
    SOCKET sock;

    rc = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (rc != 0) {
        printf("WSAStartup failed: %d\n", rc);
        return 1;
    }
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        printf("Your computer is from the wrong millenium.\n");
        WSACleanup();
        return 1;
    }

    if (!load_mswsock()) {
        printf("Error loading mswsock functions: %d\n", WSAGetLastError());
        return 1;
    }

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
        printf("socket: %d\n", WSAGetLastError());
        return 1;
    }

    /* ConnectEx requires the socket to be initially bound. */
    {
        struct sockaddr_in addr;
        ZeroMemory(&addr, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = 0;
        rc = bind(sock, (SOCKADDR*) &addr, sizeof(addr));
        if (rc != 0) {
            printf("bind failed: %d\n", WSAGetLastError());
            return 1;
        }
    }

    /* Issue ConnectEx and wait for the operation to complete. */
    {
        OVERLAPPED ol;
        ZeroMemory(&ol, sizeof(ol));

        sockaddr_in addr;
        ZeroMemory(&addr, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr("173.194.37.36"); // google.com
        addr.sin_port = htons(80);

        ok = mswsock.ConnectEx(sock, (SOCKADDR*) &addr, sizeof(addr), NULL, 0, NULL, &ol);
        if (ok) {
            printf("ConnectEx succeeded immediately\n");
        } else if (WSAGetLastError() == ERROR_IO_PENDING) {
            printf("ConnectEx pending\n");

            DWORD numBytes;
            ok = GetOverlappedResult((HANDLE) sock, &ol, &numBytes, TRUE);
            if (ok)
                printf("ConnectEx succeeded\n");
            else
                printf("ConnectEx failed: %d\n", WSAGetLastError());
        } else {
            printf("ConnectEx failed: %d\n", WSAGetLastError());
            return 1;
        }
    }

    /* Make the socket more well-behaved. */
    rc = setsockopt(sock, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0);
    if (rc != 0) {
        printf("SO_UPDATE_CONNECT_CONTEXT failed: %d\n", WSAGetLastError());
        return 1;
    }

    /* This will fail if SO_UPDATE_CONNECT_CONTEXT was not performed. */
    rc = shutdown(sock, SD_BOTH);
    if (rc != 0) {
        printf("shutdown failed: %d\n", WSAGetLastError());
        return 1;
    }

    printf("Done\n");
    return 0;
}
4

2 に答える 2

5

connect does this step automatically, but ConnectEx does not.

Correct.

Is my assessment correct?

Yes.

Is there a way to do this automatic bind that is agnostic to the address family, or will I have to handle each of AF_INET, AF_INET6, AF_BTH (Bluetooth), etc. manually?

I believe that INADDR_ANY is a bunch of zeros in all address families, so you could just try using the memset() and omitting the assignment to addr.sin_addr.s_addr completely. Whether this is kosher, portable, politically correct etc. is another question into which I will not enter.

It seems pretty curious that Microsoft didn't manage to have ConnectEx() call bind() internally, considering that saving system calls is the motivation for its existence, and also considering that most programs never bind an outbound socket at all.

于 2012-11-28T10:07:44.313 に答える
1

ConnectEx のバインド アドレスは、アドレス ファミリに依存しない方法で取得できます。

解決策 1

getaddrinfo次のオプションで呼び出します。

pServiceName = "0"

hints.ai_flags = AI_PASSIVE
hints.ai_family = address family of the socket

次に、返されたアドレス リストの最初の結果を使用します。

で使用できるソケットのアドレス ファミリを取得するにはgetsockoptSO_PROTOCOL_INFOW.

解決策 2

で定義されてSOCKADDR_STORAGEいるアドレス構造体と呼び出しに使用します。と をサポートします。INETADDR_SETANYMSTcpIP.hAF_INETAF_INET6

于 2016-01-10T06:18:04.597 に答える