Сервер и клиент работают на ПК.Может ли клиент подключиться к серверу, используя IP-адрес ПК? - PullRequest
0 голосов
/ 07 июля 2019

Я новичок в сетевом программировании, так что это меня несколько дней озадачивало.

Я написал 2 приложения: одно - сервер, другое - клиент.

Я хочу, чтобы они сделали следующее: когда я предоставляю клиенту IP-адрес, клиент подключается к серверу на машине с предоставленным IP-адресом.

Я проверил это, открыв сервер и клиент на моем ПК, введя адрес обратной связи (127.0.0.1). Клиент подключился к серверу нормально (и я мог даже отправлять текстовые сообщения друг от друга).

Так что я попробовал дальше. Я погуглил свой IP (который был 113.20.98.124) и затем передал его моему клиенту. Я ожидал, что клиент также подключится к серверу, но этого не произошло. Клиент завершил работу и сказал, что время соединения истекло (WSAETIMEDOUT 10060).

Что я хочу спросить: почему я не могу использовать свой IP, чтобы сообщить клиенту, что он должен подключиться к моему серверу? Это даже вещь?

Вот мои коды сервера и клиента (на случай, если я что-то сделал не так):

(я кодировал, используя VS2017 в Windows)

Клиент:

#include <iostream>
#include <WS2tcpip.h>
#include <winsock2.h>
#include <string>


//The port which will be used to connect to server
#define PORT "7777"


std::string getIPAddress()
{
    std::string out;
    std::cout << "IP/Domain to connect to: ";
    std::cin >> out;
    std::cin.ignore();
    return out;
}

//Print out error code and exit the program in case something goes wrong
void failure_exit(std::string message, int exitVal)
{
    std::cerr << message
        << " Error code: " << WSAGetLastError() << " \n";
    WSACleanup();
    std::cin.get();
    exit(exitVal);
}



int main(int argc, char *argv[])
{
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(1, 0), &wsaData) != 0)
        failure_exit("WSAStartup failed!", 1);


    addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC; //AF_INET or AF_INET6 are totally fine
    hints.ai_socktype = SOCK_STREAM; //TCP connection


    //Get the IP from user
    std::string IPconnect = getIPAddress();


    //A pointer to a linked list of juicy results we can use 
    addrinfo *result(nullptr);

    //Prepare the addrinfo for a connecting socket
    if (getaddrinfo(IPconnect.c_str(), PORT, &hints, &result) != 0)
        failure_exit("getaddrinfo failed!", 2);


/*PREPARING A SOCKET*/
    SOCKET sockfd;
    //A pointer to one of the addrinfos from result, which is a usable one
    //'chosen' will also be used in connect() call
    addrinfo *chosen(result);
    //Run through the results got from getaddrinfo and pick the first usable addrinfo
    for (; chosen != nullptr; chosen = chosen->ai_next)
    {
        //see if sockfd is legit to use
        if ((sockfd = socket(chosen->ai_family, chosen->ai_socktype, chosen->ai_protocol)) != -1)
            break;
    }

    freeaddrinfo(result);

    //Socket preparation failed
    if (sockfd<=0)
        failure_exit("Socket preparation failed!", 3);


/*CONNECT!*/
    std::cout << "Connecting... 20 seconds until timed out.\n";

    if (connect(sockfd, chosen->ai_addr, chosen->ai_addrlen) != 0)
        failure_exit("Failed to connect!", 4);

    std::cout << "Connected!\n";

    WSACleanup();
    std::cin.get();
    return 0;
}

Сервер:

#include <iostream>
#include <WS2tcpip.h>
#include <winsock2.h>
#include <string>


//The port which this server will be using 
//to listen to incoming connections
#define PORT "7777"

//Limits how many pending connections
//can be queued up
#define BACKLOG 10


void failure_exit(std::string message, int exitVal)
{
    std::cerr << message
        << " Error code: " << WSAGetLastError() << " \n";
    WSACleanup();
    std::cin.get();
    exit(exitVal);
}


int main(int argc, char* argv[])
{
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(1, 0), &wsa) != 0)
        failure_exit("WSAStartup failed!", 1);


    addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    //IPv4 or IPv6 are both Ok
    hints.ai_family = AF_INET;
    //This addrinfo will be used for binding
    hints.ai_flags = AI_PASSIVE;
    //TCP connection
    hints.ai_socktype = SOCK_STREAM;


    addrinfo *result;
    if (getaddrinfo(NULL, PORT, &hints, &result) != 0)
        failure_exit("getaddrinfo failed!", 3);


//PREPARE A SOCKET
    SOCKET sockfd(0);

    //The usable addrinfo will be pointed to by 'chosen'
    addrinfo *chosen(result);

    //Loop through the results to find a suitable one
    for (; chosen != nullptr; chosen = chosen->ai_next)
    {
        sockfd = socket(chosen->ai_family, chosen->ai_socktype, chosen->ai_protocol);
        //Stop at the first usable
        if (sockfd != -1)
            break;
    }
    freeaddrinfo(result);

    //Check for preparation failure
    if (sockfd <= 0)
        failure_exit("Socket preparation failed!", 4);


    //Bind the socket above to my chosen PORT
    if (bind(sockfd, chosen->ai_addr, chosen->ai_addrlen) == -1)
        failure_exit("Binding failed!", 5);


    //Start listening for incoming connections
    if (listen(sockfd, BACKLOG) == -1)
        failure_exit("Listening failed!", 6);


    //The new socket to be returned by accept()
    SOCKET newfd;

    sockaddr_storage newConnection;
    socklen_t newlength(sizeof(newConnection));

    std::cout << "Anyone?\n";

    //Accept a pending connection
    if ((newfd = accept(sockfd, reinterpret_cast<sockaddr*>(&newConnection), &newlength)) == -1)
        failure_exit("Accepting connection failed!", 7);

    std::cout << "Connection accepted!\n";

    WSACleanup();
    std::cin.get();
    return 0;
}

Все, что я узнал о сетевом программировании, получено из http://beej.us/guide/.

1 Ответ

2 голосов
/ 07 июля 2019

Когда вы гуглили свой IP, вы получили свой публичный IP, как это видит внешний мир. Если ваш ПК напрямую подключен к вашему интернет-модему, то этот IP-адрес принадлежит вашему ПК. Но если ваш компьютер находится за сетевым маршрутизатором (что очень часто встречается в домохозяйствах, имеющих много подключенных к Интернету устройств), то этот IP-адрес принадлежит вашему сетевому маршрутизатору, а не вашему ПК.

Ваше серверное приложение может прослушивать только локальные / локальные IP-адреса, которые были назначены непосредственно ПК, на котором оно работает. Если этот компьютер работает за сетевым маршрутизатором, он не может прослушивать публичный IP-адрес вашего маршрутизатора.

Код вашего сервера привязывает свой прослушивающий сокет таким образом, что он прослушивает все доступные IPv4-IP-адреса, назначенные ПК, на котором он работает. Чтобы узнать, что на самом деле представляют собой эти IP-адреса, вы можете использовать инструмент командной строки Windows ipconfig. Или в коде своего сервера вы можете использовать API GetAdaptersInfo() или GetAdaptersAddresses().

Если ваш клиент работает на том же ПК / в сети, что и серверное приложение, он может напрямую подключаться к любому локальному / локальному IP-адресу, который прослушивает сервер. Но если клиент работает в другой сети (то есть на другом компьютере через Интернет), ему нужно вместо этого подключиться к общедоступному IP-адресу. А в случае, когда этот публичный IP-адрес принадлежит сетевому маршрутизатору, маршрутизатор должен быть настроен для пересылки входящих соединений по заданному <PublicIP>:<PublicPort> на серверный ПК <LanIP:LanPort> (т. Е. Пересылка с 113.20.98.124:777 на что-то вроде 192.168.0.1:777 или любой IP-адрес, фактически назначенный серверу ПК).

...