【问题标题】:C++ Winsock2 Client not connecting to server through remote IPC ++ Winsock2客户端未通过远程IP连接到服务器
【发布时间】:2018-09-16 17:56:01
【问题描述】:

我正在尝试通过使用 Winsock2 API 来学习网络编程的基础知识。我已经通过 LAN IP 地址成功连接,但我已经苦苦挣扎了一天多,试图让客户端通过其公共 IP 连接到我的服务器。

我已经在我的路由器上设置了端口转发,甚至使用 Wireshark 来监视客户端连接请求。我在 Wireshark 中看到了请求,但它从未连接到服务器,最终出现超时错误。

我很茫然,我感谢任何能指出我正确方向的人!

这是客户端实现:

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

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



#define DEFAULT_PORT //MY PORT
#define DEFAULT_BUFLEN 512
#define SERVER_IPV4 //"MY PUBLIC IP STRING"


int main(int argc, char **argv)
{

    //wsaData to hold Winsock dll information
    WSADATA wsaData;
    WORD wVersionRequired = MAKEWORD(2, 2);

    //Attempts to load winsock dll matching required version and fills WSADATA object
    int wsaInit = WSAStartup(wVersionRequired, &wsaData);
    if(wsaInit != 0)
    {
        printf("WSAStartup failed with error code: %d\n", wsaInit);
    }

    //If dll fails to load correct version free winsock dll resources
    if(wsaData.wHighVersion != wVersionRequired)
    {
        printf("No usable version of Winsock.dll found\n");
        WSACleanup();
        return 1;
    }
    else
    {
        printf("Winsock dll 2.2 loaded correctly\n");
    }


    /**********************Socket Code Here**********************/

    SOCKADDR_IN SockAddrIP4;
    SockAddrIP4.sin_family = AF_INET;
    SockAddrIP4.sin_addr.s_addr = inet_addr(SERVER_IPV4);
    SockAddrIP4.sin_port = htons(DEFAULT_PORT);

    /**************Create Socket****************/

    //INVALID_SOCKET used like NULL
    SOCKET ConnectSocket = INVALID_SOCKET;

    // TODO(baruch): Only supporting IP_V4
    ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if(ConnectSocket == INVALID_SOCKET)
    {
        printf("socket() error: %ld\n", WSAGetLastError());
        //clean up address info after getaddrinfo function when socket fails
        WSACleanup();
        return 1;
    }

    /*****************Connect to socket**************/

    int connectResult = connect(ConnectSocket, (SOCKADDR*)&SockAddrIP4, sizeof(SOCKADDR_IN));
    if(connectResult == SOCKET_ERROR)
    {
        printf("Connect failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        ConnectSocket = INVALID_SOCKET;
    }
    else
    {
        printf("Connected with server: %s\n", SERVER_IPV4);
    }

    if(ConnectSocket == INVALID_SOCKET)
    {
        printf("Unable to connect with server\n");
        WSACleanup();
        return 1;
    }

    /*************END of Socket CODE CLEANUP********/

    // TODO(baruch): close socket
    WSACleanup();

}

这是服务器:

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

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

// TODO(baruch): Only supporting ascii consider Unicode later
#undef UNICODE

#define DEFAULT_PORT //My Port
#define DEFAULT_BUFLEN 512

int main(int argc, char **argv)
{

    //wsaData to hold Winsock dll information
    WSADATA wsaData;
    WORD wVersionRequired = MAKEWORD(2, 2);


    int wsaInit = WSAStartup(wVersionRequired, &wsaData);
    if(wsaInit != 0)
    {
        printf("WSAStartup failed with error code: %d\n", wsaInit);
    }

    //If dll fails to load correct version free winsock dll resources
    if(wsaData.wHighVersion != wVersionRequired)
    {
        printf("No usable version of Winsock.dll found\n");
        WSACleanup();
        return 1;
    }
    else
    {
        printf("Winsock dll 2.2 loaded correctly\n");
    }

    /**********************Socket Code Here**********************/


    /**************Create Socket****************/
    SOCKADDR_IN SockAddrIP4;
    SockAddrIP4.sin_family = AF_INET;
    SockAddrIP4.sin_addr.s_addr = INADDR_ANY;
    SockAddrIP4.sin_port = htons(DEFAULT_PORT);

    //INVALID_SOCKET used like NULL
    SOCKET ListenSocket = INVALID_SOCKET;

    // TODO(baruch): Only supporting IP_V4
    // Socket for server to listen on for client connections
    ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if(ListenSocket == INVALID_SOCKET)
    {
        printf("socket() error: %ld\n", WSAGetLastError());
        //clean up address info after getaddrinfo function when socket fails
        WSACleanup();
        return 1;
    }

    /**************Bind Socket******************/
    int bindResult = bind(ListenSocket, (SOCKADDR*)&SockAddrIP4, sizeof(SOCKADDR_IN));
    if(bindResult == SOCKET_ERROR)
    {
        printf("failed to bind with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    /************Listen for Connections**********/
    int listenResult = listen(ListenSocket, SOMAXCONN);
    if(listenResult == SOCKET_ERROR)
    {
        printf("Listen failed, error: %d\n", WSAGetLastError() );
        WSACleanup();
        return 1;
    }
    else
    {
        printf("Now listening for client connections...\n");
    }
    /************Accept and Handle CLient Connections***********/
    // TODO(baruch): For testing, only allowing a single client. Eventually need to create a loop to handle all client connections.
    SOCKET ClientSocket;
    SOCKADDR_IN connectedAddress;
    int addressLength = sizeof(connectedAddress);
    ClientSocket = accept(ListenSocket, (SOCKADDR *) &connectedAddress, &addressLength);
    if(ClientSocket == SOCKET_ERROR)
    {
        printf("Accept failed, error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    else
    {
        //inet_ntoa converts ip address to binary format.
        char *clientIp = inet_ntoa(connectedAddress.sin_addr);
        // TODO(baruch): Make sure this string correctly prints address
        printf("Client connection from: %s accepted\n", clientIp);
    }

    /**********Handle inbound and outbound data**********/

    char inBuf[DEFAULT_BUFLEN];
    int dataBufLen = DEFAULT_BUFLEN;
    int inDataResult, outDataResult;

    do
    {
        inDataResult = recv(ClientSocket, inBuf, dataBufLen, 0);
        if(inDataResult > 0)
        {
            printf("Number of bytes received: %d", inDataResult);

            //Confirm to client message received
            char confirmReceipt[] = "\nMessage received!\n";
            outDataResult =
                send(ClientSocket, confirmReceipt, sizeof(confirmReceipt), 0);
            if(outDataResult == SOCKET_ERROR)
            {
                printf("Confirmation message failed with error: %d\n", WSAGetLastError());
                closesocket(ClientSocket);
                WSACleanup();
                return 1;
            }
            else
            {
                printf("Confirmation message sent\n");
            }
        }
        else if(inDataResult == 0)
        {
            printf("Connection closing\n");
        }
        else
        {
            printf("Data receipt failed with error: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }

    } while(inDataResult > 0);

    //Shutdown sending portion of socket, can still receive data
    int shutDownResult = shutdown(ClientSocket, SD_SEND);
    if(shutDownResult == SOCKET_ERROR)
    {
        printf("Shutdown failure, error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }


    /*************End Socket Code Clean up Winsock dll**********/

    closesocket(ClientSocket);
    WSACleanup();
}

【问题讨论】:

    标签: c++ winsock2


    【解决方案1】:

    如果您可以通过本地 LAN IP 地址进行连接,但不能通过公共 IP 地址进行连接,则可能是这些问题之一。

    1. 您是否允许您的程序通过 Windows 防火墙?完全关闭 Windows 防火墙(暂时)以确保。

    2. 如果您的客户端和服务器都位于同一个 NAT 之后,您的 NAT 可能不允许客户端连接通过公共 IP 地址进行连接。这称为NAT hairpinning。并非所有 NAT 都支持这一点。验证您是否可以通过服务器网络外部的客户端连接到服务器的 IP 地址。

    3. 如有疑问,请使用netcat 之类的简单程序来测试 PC 之间的套接字连接。简单的测试是在您的端口上以侦听模式运行 nc,然后使用另一个 nc 实例连接到它。在 Internet 上搜索“Netcat for Windows”。如果您可以通过 netcat 连接到您的端口,但不能通过您的客户端/服务器代码,那么您的代码存在问题。如果您无法通过 netcat 连接到您的端口,则可能是防火墙或网络配置错误。

    【讨论】:

    • 我试试netcat。谢谢!
    • 所以我在端口上运行了 Nmap,我收到了这条消息:在 2018-09-16 14:23 Central Daylight Time 开始 Nmap 7.70 (nmap.org) 注意:主机似乎已关闭。如果它真的启动了,但是阻止了我们的 ping 探测,尝试 -Pn Nmap done: 1 IP address (0 hosts up) 在 8.05 秒内扫描到这似乎证明有什么东西阻止了连接?
    • 您是否通过在 NAT 外部进行测试来消除 NAT 发夹问题?
    • 这是我的下一个测试,我无法立即访问外部 Windows 电脑。我刚刚确认我的代码在我最新的更改后仍然在我的 LAN 网络上正常运行,所以我会尽可能在外部进行测试。再次感谢。
    • 我终于有机会从外部网络进行测试,但得到了相同的结果。我会继续尝试其他的。
    【解决方案2】:

    多亏了 Selbie,我才得以完成排除过程,并确定我的服务有问题。我联系了我的 ISP,他们不得不更改服务类型并给我一个真实的公共 IP。默认情况下,他们使用运营商级 NAT,这意味着住宅站点被分配了一个私有 IP,该私有 IP 由 ISP 网络中某处的“中间盒网络地址转换器”转换为公共 IP。 谢谢!

    【讨论】: