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

这是我目前的理解:在调用ConnectExbindINADDR_ANY和端口0的套接字之前(除非已绑定(bind)):

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不会自动执行此步骤。

我的问题是:
  • 我的评估正确吗?
  • 有没有办法执行与地址族无关的自动绑定(bind),还是我必须手动处理AF_INETAF_INET6AF_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;
    }
    

    最佳答案



    正确的。



    是的。



    我相信INADDR_ANY在所有地址族中都是一堆零,因此您可以尝试使用memset()并完全省略对addr.sin_addr.s_addr的赋值。这是否是犹太洁食,可移植,在政治上是正确的等等,这是我将不再讨论的另一个问题。

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

    10-04 20:21