How to create several connections to server with Boost ASIO

Viewed 929

I am using Boost Asio as a TCP networking solution.

Here is my server Code:

Server.h:

#ifndef VIBRANIUM_CORE_SERVER_H
#define VIBRANIUM_CORE_SERVER_H
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
#include <deque>
#include "Logger.h"
#include "Client.h"

using boost::asio::ip::tcp;
namespace Vibranium {
    class Server {
    public:
        Server(boost::asio::io_service &io_service, short port)
                : acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
                  socket_(io_service) {
            do_accept();
            Logger::Log("Server Started! Listening on Port("+std::to_string(port)+")", Logger::Success, true);
        }
        static std::deque<std::shared_ptr<Client>> Clients;

    private:
        void do_accept();
        int incrementor;
        tcp::acceptor acceptor_;
        tcp::socket socket_;
    };

}


#endif //VIBRANIUM_CORE_SERVER_H

Server.cpp:

#include "Server.h"
#include "Client.h"

using namespace Vibranium;
std::deque<std::shared_ptr<Client>> Server::Clients;
void Server::do_accept()
{
    acceptor_.async_accept(socket_,
   [this](boost::system::error_code ec)
   {
       if (!ec)
       {
           incrementor++;
           Logger::Log("New Connection (ID: " + std::to_string(incrementor) + ")",Logger::Success);
           std::shared_ptr<Client> c = std::make_shared<Client>(std::move(socket_));
           c->start();
           c->connectionId = incrementor;
           Server::Clients.push_back(c);
       }

       do_accept();
   });
}

Client.h:

#ifndef VIBRANIUM_CORE_CLIENT_H
#define VIBRANIUM_CORE_CLIENT_H

#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

namespace Vibranium{
    class Client: public std::enable_shared_from_this<Client>
    {
    public:
        Client(tcp::socket socket)
        : socket_(std::move(socket))
        {
        }
        void start();
        int connectionId;
    private:
        void do_read();
        void do_write(std::size_t length);
        tcp::socket socket_;
        enum { max_length = 1024 };
        char data_[max_length];
    };
}
#endif //VIBRANIUM_CORE_CLIENT_H

Client.cpp:

#include "Client.h"
#include "Server.h"

void Vibranium::Client::start() {
    do_read();
}

void Vibranium::Client::do_read() {
    auto self(shared_from_this());
    socket_.async_read_some(boost::asio::buffer(data_, max_length),
    [this, self](boost::system::error_code ec, std::size_t length)
    {
        if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
        {
            Logger::Log("Disconnected ID: " + std::to_string(connectionId),Logger::Error, true);
            for (int i = 0; i < Server::Clients.size(); ++i) {
                if(Server::Clients[i]->connectionId == connectionId)
                    Server::Clients.erase(Server::Clients.begin()+i);
            }
        }
        else
        {
            std::cout.write(data_, length);
            std::cout << "\n";
            do_write(length);
        }
    });
}

void Vibranium::Client::do_write(std::size_t length) {
    auto self(shared_from_this());
    boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
     [this, self](boost::system::error_code ec, std::size_t /*length*/)
     {
         if (!ec)
         {
             do_read();
         }
     });
}

Here is how I start the server:

#include "Config.h"
#include "Database/MySQLConnection.h"
#include "Implementation/LoginDatabase.h"
#include "Banner.h"
#include "Server/Server.h"
#include <boost/asio.hpp>

using boost::asio::ip::tcp;
using namespace std;
using namespace Vibranium;

int main() {
    //Don't mind Logger::FatalError it's just for coloring!
    Banner::Show(Logger::Error,"AuthServer");
    Config config("AuthServer");
    std::string defaultPort = "8080";
    MySQLConnectionInfo mySqlConnectionInfo(config, "LoginDatabaseInfo");
    LoginDatabaseConnection loginDatabaseConnection(mySqlConnectionInfo);
    loginDatabaseConnection.LoadDatabase();

    try
    {
        boost::asio::io_service io_service;
        Server s(io_service, std::stoi(config.GetConfigValue("AuthServerPort", defaultPort)));
        io_service.run();
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

Here is how I try to make several connections at once and keep them alive:

Config config("AuthServer");
std::string defaultIP   = "127.0.0.1";
std::string defaultPort = "8080";
int connectionsNumber   = CommandQuestion<int>::AskQuestion("How many connections do you want established?");
std::cout << "Initializing " << std::to_string(connectionsNumber) << " connection/s." << std::endl;

std::cout << "Trying to connect to  " <<  defaultIP << " on port: " << config.GetConfigValue("AuthServerPort", defaultPort)  << std::endl;
for (int i = 0; i < connectionsNumber; ++i) {
   try
    {
        boost::asio::io_context io_context;
        tcp::socket s(io_context);
        tcp::resolver resolver(io_context);
        boost::asio::connect(s, resolver.resolve( defaultIP,config.GetConfigValue("AuthServerPort", defaultPort)));

        std::string message = "I am testing here!!!";
        size_t request_length = std::strlen(message.c_str());
        boost::asio::write(s, boost::asio::buffer(message, request_length));

        char reply[max_length];
        size_t reply_length = boost::asio::read(s,boost::asio::buffer(reply, request_length));
        Logger::Log(std::to_string(i) + " Connected!",Logger::Success);
        /*        std::cout << "Reply is: ";
                std::cout.write(reply, reply_length);
                std::cout << "\n";*/
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

}

I am able to make a successful connections, but they got dropped directly after they are created and transferred the example echo message.

When I try to initialize for example 3 connections i see the following output on the server:

Server Started! Listening on Port(8085)
New Connection (ID: 1)
I am testing here!!!
Disconnected ID: 1
New Connection (ID: 2)
I am testing here!!!
Disconnected ID: 2
New Connection (ID: 3)
I am testing here!!!
Disconnected ID: 3

I am initializing the connections to the server from the same PC but from another C++ target executable. So I have server executable up and running and another executable which is trying to create several connections and keep them at once.

How can I make my connections to persist and not drop directly after they are created? Where is my mistake and how can I fix it?

1 Answers

The tcp::socket will close its connection when it goes out of scope (reference). If you want to keep the connections open, you have to keep the object alive.

You can do this by moving the io_context out of the loop in your client example and keeping some list for holding the sockets:

  boost::asio::io_context io_context;
  std::vector<tcp::socket> sockets{};

  for (int i = 0; i < connectionsNumber; ++i) {

    try {
      sockets.emplace_back(io_context);
      tcp::socket& s{sockets.back()};

      // initialize socket as before ...

The server output will then show the disconnection message after the client loop has completed:

New Connection (ID: 2)
Hello, world!
New Connection (ID: 3)
Hello, world!
New Connection (ID: 4)
Hello, world!
Disconnected ID: 2
Disconnected ID: 3
Disconnected ID: 4

One additional note: You did not show the content of do_write, but I would assume your implementation of this function in some way calls do_read again, so that it can handle the eventual EOF or connection reset.

Related