首页 > 解决方案 > SFML Detect multiple connections to server and count them

问题描述

I'm creating a very simple client/server application. I want my server to detect how many clients have connected and if there are more than 2, just print a message.

The code for the server I have so far is very minimal:

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");
}

The problem I have is that it detects the first connection with no problem, but it doesn't detect the second one, even though my second client is connected. For the client connection I'm just using the very simple code explained in the SFML documentation. I'm very confused as clients.size() always returns 1.

标签: c++socketsnetworkingsfml

解决方案


因为你不接受第二个连接......

稍微改变代码顺序:

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 教程,尤其是Blocking on a group of sockets部分。那里描述的选择器正是您所需要的。最重要的:

选择器可以监视所有类型的套接字:sf::TcpSocket、sf::UdpSocket 和 sf::TcpListener。

因此,您可以将所有套接字侦听器添加到。但是,您仍然必须自己维护您的实例:

选择器不是套接字容器。它仅引用(指向)您添加的套接字,而不存储它们。无法检索或计算您放入其中的套接字。反而, [...]。

好吧,您已经用向量覆盖了“替代”部分。通常我建议将对象直接存储在向量中;但是,您不能,因为您需要在选择器中引用。所以你需要在向量中存储指针——或者你可以使用 astd::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
    }
}

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

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

最后:无限循环!

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


推荐阅读