使用套接字通信阻止服务器-客户端之间的连接

Blocking connection between server-client using socket communication

我在使用 UDP 和 SFML 的客户端-服务器连接时遇到了一些奇怪的问题,我慢慢地 运行 不知道可能出了什么问题,所以也许有人能帮助我。

目前我可以将客户端连接到服务器并从服务器向客户端发送消息。当我终止客户端应用程序并再次重新启动它(在同一台机器上)时,提供相同的服务器连接参数,没有任何反应。看起来好像没有建立连接。客户端只是在等待来自服务器的消息,而服务器同时不断地发送消息。

我的服务器端是这样的:

std::shared_ptr<sf::UdpSocket> startUdpServer()
{
    std::cout << "Local address: ";
    std::cout << sf::IpAddress::getLocalAddress().toString() << std::endl;
    std::cout << "Public address: ";
    std::cout << sf::IpAddress::getPublicAddress().toString() << std::endl;
    std::shared_ptr<sf::UdpSocket> socket(new sf::UdpSocket());

    if(socket->bind(sf::Socket::AnyPort) != sf::Socket::Done)
        return nullptr;
    std::cout << "Server is listening to port " << socket->getLocalPort() << ", waiting for a message... " << std::endl;
    return socket;
}

std::pair<sf::IpAddress, unsigned short> runUdpServer(std::shared_ptr<sf::UdpSocket> socket)
{
    // Wait for a message
    char in[128];
    std::size_t received;
    sf::IpAddress sender;
    unsigned short senderPort;
    if (socket->receive(in, sizeof(in), received, sender, senderPort) != sf::Socket::Done)
    {
        std::cout << "Connection error" << std::endl;
        return std::make_pair(sender, senderPort);
    }
    std::cout << "Message received from client " << sender << ": \"" << in << "\"" << std::endl;
    return std::make_pair(sender, senderPort);
}

void sendUdpMessage(std::shared_ptr<sf::UdpSocket> socket, std::pair<sf::IpAddress, unsigned short> config,
                    std::string message)
{
    const char* out = message.c_str();
    if (socket->send(out, sizeof(char) * message.length(), config.first, config.second) != sf::Socket::Done)
    {
        std::cout << "Message not send" << std::endl;
        return;
    }
}

void sendMessages(std::shared_ptr<sf::UdpSocket> socket, std::pair<sf::IpAddress, unsigned short> config)
{
    if(config.first != sf::IpAddress::None)
    {
        while(true)
            sendUdpMessage(socket,config,"Test msg");
    }
    else
        std::cout << "Message sending error" << std::endl;
}

auto socket = startUdpServer();
auto config = runUdpServer(socket);
std::thread messages_thread(sendMessages,socket,config);

和我的客户:

std::shared_ptr<sf::UdpSocket> startUdpClient()
{
    sf::IpAddress server;
    do
    {
        std::cout << "Type the address or name of the server to connect to: ";
        std::cin  >> server;
    }
    while (server == sf::IpAddress::None);

    unsigned short port;
    std::cout << "Type the port number: ";
    std::cin >> port;

    std::shared_ptr<sf::UdpSocket> socket(new sf::UdpSocket());
    sf::Packet packet;
    const char out[] = "Hi, I'm a client";
    if (socket->send(out, sizeof(out), server, port) != sf::Socket::Done)
        return nullptr;
    return socket;
}

void runUdpClient(std::shared_ptr<sf::UdpSocket> socket)
{
    char in[256];
    std::size_t received;
    sf::IpAddress sender;
    unsigned short senderPort;
    if (socket->receive(in, sizeof(in), received, sender, senderPort) != sf::Socket::Done)
        return;
    std::cout << "Message received: \"" << in << "\"" << std::endl;
}

auto socketUDP = startUdpClient();
std::thread messagesThread(receiveMessages,socketUDP);

void receiveMessages(std::shared_ptr<sf::UdpSocket> socket)
{
    while(true)
        runUdpClient(socket);
}

对于网络,UDP 和 TCP 有 2 个非常重要的要点要记住。

TCP - 是基于连接的,这意味着它每次尝试向它发送消息时都需要有人在另一端。

UDP - 基于无连接,这意味着他会将信息发送到您希望他发送到的任何地方。他可以发送和接收信息,但为了接收数据,他需要绑定到一个端口。所以在你的服务器中,你每次都将他绑定到同一个端口。

在你的客户中,你给他一个端口来发送信息,而不是将他绑定到一个特定的端口。每当您关闭客户端时,他都应该释放端口,并且每当您重新启动客户端时,如果您希望他能够从您的服务器接收数据,他应该绑定到同一端口。如果你不是本质上发生了什么是数据到达 IP 和它被发送到的端口,但是没有与该端口关联的应用程序因此数据包丢失。

为了通过 UDP 在两方之间进行通信,每一方都需要一个唯一的(地址、端口)对。通常,服务器将其套接字绑定到 fixed/well-known 端口,客户端端口因客户端而异——因此允许多个客户端与同一服务器通信,每个客户端都在自己的端口上。

如果您的客户端,如此处,没有明确地将其套接字绑定到一个端口,操作系统将动态分配一个"random"未使用的端口给它并且在第一次使用端口发送数据时自动绑定它。只要该客户端继续使用同一个套接字,端口号就固定了。

但是,当您重新启动客户端时,它会获得一个 new 套接字,并且在使用新套接字首次发送时,OS 将套接字绑定到一个新港口。但是,您的服务器假设客户端端口号仍然是它从第一个客户端接收到的端口号。

由于这种工作方式,对于 UDP 服务器,通常的模式是每条消息都是独立的。每次收到消息时,服务器都会记下客户端的地址和端口号,然后对其进行响应 address/port。服务器通常不会假定任何两个连续消息将来自同一客户端,因为它无法知道任何给定客户端何时消失。

(您 可以 在 UDP 之上构建您自己的更持久的 "connection" 概念——例如 NFS 传统上所做的——但这是一项大量的工作这需要在设计协议时给予应有的注意。并且它在上述相同的基本模型中工作。)

您的客户端也可以始终显式绑定到您 select 的端口。但是,这将限制您在任何一台机器上 运行 客户端的一个实例(好吧,实际上是在任何一个网络地址上)。