8

ConnectEx函数需要一个“未连接的、先前绑定的套接字”。实际上,如果我在示例中省略了绑定步骤(见下文),ConnectEx将失败并显示 WSAEINVAL

这是我目前的理解:在调用ConnectEx之前,将套接字绑定INADDR_ANY端口 0(除非它已经绑定):

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. 有没有办法进行这种与地址系列无关的自动绑定,还是我必须手动处理每个、、、AF_INETAF_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 会自动执行此步骤,但 ConnectEx 不会。

正确的。

我的评估是否正确?

是的。

有没有办法进行这种与地址系列无关的自动绑定,还是我必须手动处理 AF_INET、AF_INET6、AF_BTH(蓝牙)等?

我相信这INADDR_ANY是所有地址系列中的一堆零,因此您可以尝试使用memset()并完全省略分配addr.sin_addr.s_addr。这是否是犹太洁食、便携、政治正确等是另一个我不会讨论的问题。

考虑到保存系统调用是它存在的动机,并且考虑到大多数程序根本从不绑定出站套接字,微软没有设法在内部ConnectEx()调用似乎很奇怪。bind()

于 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

然后使用返回的地址列表的第一个结果。

要获取套接字的地址族,您可以getsockopt使用SO_PROTOCOL_INFOW.

解决方案 2

用于定义在 中SOCKADDR_STORAGE的地址结构和调用。它支持和。INETADDR_SETANYMSTcpIP.hAF_INETAF_INET6

于 2016-01-10T06:18:04.597 回答