SFML 检测到服务器的多个连接并计算它们

SFML Detect multiple connections to server and count them

我正在创建一个非常简单的 client/server 应用程序。 我希望我的服务器检测有多少客户端已连接,如果超过 2 个,则打印一条消息。

到目前为止,我的服务器代码非常少:

std::vector<sf::TcpSocket*> clients;

sf::TcpListener listener;

if (listener.listen(SERVERPORT) != sf::Socket::Done)
{
    printf("Error\n");
}

printf("Waiting for first connection...\n");

sf::TcpSocket *socket = new sf::TcpSocket;

if (listener.accept(*socket) != sf::Socket::Done)
{
    printf("Error\n");
}

clients.push_back(socket);

while (clients.size() < 2)
{
    printf("Waiting for second connection...\n");
}

我遇到的问题是它检测到第一个连接没有问题,但它没有检测到第二个,即使我的第二个客户端已连接。对于客户端连接,我只使用 SFML 文档中解释的非常简单的代码。 我很困惑,因为 clients.size() 总是 returns 1.

因为您不接受第二次连接...

稍微更改一下代码顺序:

std::vector<sf::TcpSocket*> clients;

sf::TcpListener listener;

if (listener.listen(SERVERPORT) != sf::Socket::Done)
{
    printf("Error\n");
}

printf("Waiting for first connection...\n");

while (clients.size() < 2)
{
    sf::TcpSocket *socket = new sf::TcpSocket;
    if (listener.accept(*socket) != sf::Socket::Done)
    {
        printf("Error\n");
    }

    clients.push_back(socket);

    // OK, that one would be printed twice!!!
    printf("Waiting for second connection...\n");
}

不过,这不是最后一个循环,它只是为了演示。

请注意,您不会删除在任何地方创建的客户端(-> 内存泄漏!)。您可能会考虑使用智能指针而不是原始指针。

好的,现在:你想接受任意数量的客户,一旦你有两个以上的客户,运行一些申请。

首先,你需要在循环中继续接受新客户(我稍微更改了日志消息......:

std::vector<sf::TcpSocket*> clients;
sf::TcpListener listener;
if (listener.listen(SERVERPORT) != sf::Socket::Done)
{
    printf("Error\n");
    return; // no need to go on on error!
}

printf("start waiting for clients...\n");

for(;;) // endless loop...
{
    sf::TcpSocket* socket = new sf::TcpSocket;
    if (listener.accept(*socket) != sf::Socket::Done)
    {
        printf("Error\n");
        // TODO: consider some appropriate error handling
        // minimally:
        delete socket; // otherwise, you produce memory leaks!
    }

    clients.push_back(socket);
    printf
    (
        "new client accepted; number of clients now: %llu\n"
        "continue waiting...\n",
        clients.size()
    );
    // llu: most likely on 64-bit machine, on 32 bit either lu or u,
    // depending on type of size_t; you get around having to decide
    // if using C++ streams instead...
}

现在的问题是:您还需要适当地处理连接!看看这个 SFML tutorial,尤其是 Blocking on a group of sockets 部分。此处描述的选择器正是您所需要的。最重要的:

A selector can monitor all types of sockets: sf::TcpSocket, sf::UdpSocket, and sf::TcpListener.

所以你可以添加所有的套接字监听器。不过,您仍然需要自己维护您的实例:

A selector is not a socket container. It only references (points to) the sockets that you add, it doesn't store them. There is no way to retrieve or count the sockets that you put inside. Instead, [...].

好吧,您已经用矢量覆盖了 'instead' 部分。通常我建议直接将对象存储在向量中;但是,您不能,因为您需要在选择器中引用。所以你需要在向量中存储指针——你可以使用std::list代替,这不会使引用或指针无效存储有关插入或删除对象的数据(当然,除了指向已删除对象本身的指针)。

如果您想继续使用向量,您可以考虑使用智能指针,这样您就不必关心内存管理(例如 std::vector<std::unique_ptr<sf::TcpSocket>> clients)。

我个人更喜欢 std::list:

std::list<sf::TcpSocket> clients; // notice: no pointers...
sf::TcpListener listener;
sf::SocketSelector selector;
selector.add(listener); // you want to listen as well...
for(;;)
{
    // wait on selector as described in tutorial, I'd recommend only
    // waiting for one second: you might want to handle the application
    // exiting at some point of time!
}

如果等待成功,您现在首先检查服务器套接字,然后检查客户端):

if(listener.isReady())
{
    // OK, we now know that a client is waiting, so next steps won't block:
    clients.emplace_back(); // new client
    if(listener.accept(clients.back()) != sf::Socket::Done)
    {
        // OK, something went wrong, current client us valueless...
        clients.pop_back();
        // adding and then removing? well, normally, this case here
        // should not occur anyway, so we optimized for the far more
        // common case.
    }
    else
    {
        // want to listen on as well
        selector.add(clients.back());
    }
}
for(auto& c : clients)
{
    if(c.isReady())
    {
        // handle communication
    }
}

处理通信由您决定...如果至少有两个客户端连接,我不会只进行通信。如果您不处理来自一个客户端的消息,您可能最终需要在另一个客户端到达时立即处理大量过时的消息,所以我宁愿立即处理任何传入的消息,即使只有一个客户端。如果它看起来更适合您,您仍然可以在(基于范围的)for 循环周围放置一个 if(clients.size() >= 2)

如果客户端断开连接,请不要忘记将其从选择器中删除!这样做之后,您也可以安全地将其从客户端列表中删除。

最后:无限循环!

你可能想通过一些条件循环来替换它,检查服务器是否仍然应该 运行ning。然后您还需要一些机制来将此条件设置为假。有不同的选择,例如。 G。一些单独的线程检查一些特定的输入,signals(尤其是在 POSIX 系统上),...