I need to do a server-client architecture for an university project and i'm stuck at the sockets part. I thought that i can instantiate the whole server once, then call the socket whenever needed, but, if i do this, whenever i run the server, nothing happens on the client side, unless i specify it to write something in the moment of instantiation.
The code for the instantiated socket:
#include <asio.hpp>
#include <iostream>
using asio::ip::tcp;
class ServerSocket
{
public:
ServerSocket(ServerSocket& otherSingleton) = delete;
void operator=(const ServerSocket& copySingleton) = delete;
static tcp::socket* GetInstance();
private:
static ServerSocket* instance;
tcp::socket* socket;
ServerSocket()
{
std::cout << "Server started";
try
{
asio::io_context io_context;
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 13));
for (;;)
{
tcp::socket socket(io_context);
acceptor.accept(socket);
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
~ServerSocket()
{
std::cout << "Server closed";
}
};
and its cpp
ServerSocket* ServerSocket::instance(nullptr);
tcp::socket* ServerSocket::GetInstance()
{
if (instance == nullptr)
{
instance = new ServerSocket();
}
return instance->socket;
}
I was thinking that actually i need to instantiate the acceptor and specify a socket in my function, ex:
void DBUser::SendUser()
{
ServerSocket* instance;
tcp::socket* socket = instance->GetInstance();
try
{
//instead of trying to write directly in the function i should call the acceptor again
//and create a socket on the spot
asio::error_code ignored_error;
asio::write(*socket, asio::buffer(m_user), ignored_error);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
if anyone can help me understand what i'm doing wrong and how i can fix it i'd appreciate it
Related
I've tried to separate my server socket in a singleton. Here's the code:
ServerSocket.h
#pragma once
#include <asio.hpp>
#include <iostream>
using asio::ip::tcp;
class ServerSocket
{
public:
ServerSocket(ServerSocket& otherSingleton) = delete;
void operator=(const ServerSocket& copySingleton) = delete;
tcp::acceptor* InitAcceptor();
tcp::socket* InitSocket();
void StartServerSocket();
void SendData(std::string);
std::array<char, 5000> RecieveData();
static ServerSocket* GetInstance();
private:
static ServerSocket* instance;
tcp::acceptor* acceptor;
tcp::socket* socket;
asio::io_context io_context;
ServerSocket() {
acceptor = InitAcceptor();
socket = InitSocket();
}
~ServerSocket()
{
std::cout << "Server closed";
}
};
ServerSocket.cpp
#include "ServerSocket.h"
tcp::acceptor* ServerSocket::InitAcceptor()
{
try
{
tcp::acceptor* acceptor = new tcp::acceptor(io_context, tcp::endpoint(tcp::v4(), 27015));
return acceptor;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
tcp::socket* ServerSocket::InitSocket()
{
try
{
tcp::socket* socket = new tcp::socket(io_context);
return socket;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
void ServerSocket::StartServerSocket()
{
try
{
std::cout << "Server started";
for (;;)
{
acceptor->accept(*socket);
};
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
std::array<char, 5000> ServerSocket::RecieveData()
{
try {
std::array<char, 5000> buf;
asio::error_code error;
size_t len = socket->read_some(asio::buffer(buf), error);
buf[len] = '\0';
return buf;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
ServerSocket* ServerSocket::instance(nullptr);
ServerSocket* ServerSocket::GetInstance()
{
if (instance == nullptr)
{
instance = new ServerSocket();
}
return instance;
}
Server socket starts, I get:
Server started
when a client connects, I get:
accept: Already open
and the server stops.
I think the error comes from the acceptor being in a for function. But according to the docs, it should work this way. (or at least that's how I understand - https://think-async.com/Asio/asio-1.20.0/doc/asio/tutorial/tutdaytime2.html)
I tried deleting the for loop, like this:
try
{
std::cout << "Server started";
acceptor->accept(*socket);
}
and now there is no problem. But the connection isn't kept open by the server. The client connects once, sends data, and the server stops running.
As far as I understand from the docs, if I set the acceptor in a for(;;), it should be running - but it doesn't work in my case.
So, how can I keep my socket open in my implementation? I want it to be running for more than one SendData - I want it to be able to communicate with the client as long as the client is connected.
Thanks.
//Edit:
Here's the client code:
#include <iostream>
#include <asio.hpp>
#include "../../cereal/archives/json.hpp"
using asio::ip::tcp;
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: client <host>" << std::endl;
return 1;
}
// Socket Parameters
const unsigned port = 27015;
auto ip_address = asio::ip::make_address_v4(argv[1]);
auto endpoint = tcp::endpoint{ ip_address, port };
// Creating and Connecting the Socket
asio::io_context io_context;
auto resolver = tcp::resolver{ io_context };
auto endpoints = resolver.resolve(endpoint);
auto socket = tcp::socket{ io_context };
asio::connect(socket, endpoints);
std::array<char, 5000> buf;
std::cout << "Message to server: ";
asio::error_code ignored_error;
std::string username = "test", password = "mihai";
std::stringstream os;
{
cereal::JSONOutputArchive archive_out(os);
archive_out(
CEREAL_NVP(username),
CEREAL_NVP(password)
);
}
asio::write(socket, asio::buffer(os.str()), ignored_error);
return 0;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
return 1;
}
And Communication.h which is responsible to catching the operation from the client and sending it to the server
#pragma once
#include <iostream>
#include "DBUser.h"
#include "DBPost.h"
class Communication
{
public:
enum class Operations {
eLogin,
eRegister
};
void ExecuteOperation(Operations operation,const std::array<char, 5000>& buffer);
};
.cpp
#include "Communication.h"
void Communication::ExecuteOperation(Operations operation,const std::array<char, 5000>& buffer)
{
DBUser* user= DBUser::getInstance();
switch (operation)
{
case Communication::Operations::eLogin:
{
std::string username, password;
std::stringstream is(buffer.data());
{
cereal::JSONInputArchive archive_in(is);
archive_in(username,password);
}
try
{
user->LoginUser(username, password);
}
catch (const std::exception& e)
{
std::cout << e.what();
}
break;
}
case Communication::Operations::eRegister:
{
std::string username, password;
std::stringstream is(buffer.data());
{
cereal::JSONInputArchive archive_in(is);
archive_in(username, password);
}
try
{
user->CreateUser(username, password);
}
catch (const std::exception& e)
{
std::cout << e.what();
}
break;
}
}
}
Main
#include <iostream>
#include <pqxx/pqxx>
#include "DBLink.h"
#include "DBUser.h"
#include "DBPost.h"
#include "../Logging/Logging.h"
#include <iostream>
#include <string>
#include <asio.hpp>
#include "ServerSocket.h"
#include "Communication.h"
int main()
{
ServerSocket* test = ServerSocket::GetInstance();
test->StartServerSocket();
std::array<char, 5000> buf = test->RecieveData();
Communication communicationInterface;
communicationInterface.ExecuteOperation(Communication::Operations::eRegister, buf);
system("pause");
}
There's a lot of antipattern going on.
Overuse of pointers.
Overuse of new (without any delete, a guaranteed leak)
The destructor claims that "Server closed" but it doesn't actually do a single thing to achieve that.
Two-step initialization (InitXXXX functions). Firstly, you should obviously favor initializer lists
ServerSocket()
: acceptor_(InitAcceptor()), socket_(InitSocket())
{ }
And you need to makeInitAcceptor/InitSocket private to the implementation.
I'll forget the Singleton which is anti-pattern 99% of the time, but I guess that's almost debatable.
In your StartServerSocket you have a loop that reuses the same socket all the time. Of course, it will already be connected. You need separate socket instances:
for (;;) {
acceptor_->accept(*socket_);
};
Simplify/Fix
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
struct Listener {
void Start()
{
std::cout << "Server started";
for (;;) {
auto socket = acceptor_.accept();
std::cout << "Accepted connection from " << socket.remote_endpoint()
<< std::endl;
};
}
static Listener& GetInstance() {
static Listener s_instance{27015}; // or use weak_ptr for finite lifetime
return s_instance;
}
private:
asio::io_context ioc_; // order of declaration is order of init!
tcp::acceptor acceptor_;
Listener(uint16_t port) : acceptor_{ioc_, tcp::endpoint{tcp::v4(), port}} {}
};
int main() {
try {
Listener::GetInstance().Start();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}
Now you could hand the socket instances to a thread. I concur with the other commenters that thread-per-request is likely also an anti-pattern, and you should consider using async IO with Asio (hence the name).
Live Demo
EDIT complete and working example based on the server code from the question:
// main.cxx
#include "ServerSocket.hxx"
#include <boost/asio.hpp>
#include <iostream>
#include <string>
using boost::asio::ip::tcp;
int
main ()
{
ServerSocket *test = ServerSocket::GetInstance ();
test->StartServerSocket ();
std::cout << std::endl;
while (auto msg = test->RecieveData ())
{
std::cout << msg.value ();
}
}
// ServerSocket.hxx
#pragma once
#include <boost/asio.hpp>
#include <iostream>
#include <optional>
using boost::asio::ip::tcp;
class ServerSocket
{
public:
ServerSocket (ServerSocket &otherSingleton) = delete;
void operator= (const ServerSocket ©Singleton) = delete;
tcp::acceptor *InitAcceptor ();
tcp::socket *InitSocket ();
void StartServerSocket ();
void SendData (std::string);
std::optional<std::string> RecieveData ();
static ServerSocket *GetInstance ();
private:
static ServerSocket *instance;
tcp::acceptor *acceptor;
tcp::socket *socket;
boost::asio::io_context io_context;
ServerSocket ()
{
acceptor = InitAcceptor ();
socket = InitSocket ();
}
~ServerSocket () {
delete socket;
delete acceptor;
std::cout << "Server closed"; }
};
// ServerSocket.cxx
#include "ServerSocket.hxx"
#include <optional>
tcp::acceptor *
ServerSocket::InitAcceptor ()
{
try
{
return new tcp::acceptor (io_context, tcp::endpoint (tcp::v4 (), 27015));
}
catch (std::exception &e)
{
std::cerr << e.what () << std::endl;
}
return nullptr;
}
tcp::socket *
ServerSocket::InitSocket ()
{
try
{
return new tcp::socket (io_context);
}
catch (std::exception &e)
{
std::cerr << e.what () << std::endl;
}
return nullptr;
}
void
ServerSocket::StartServerSocket ()
{
try
{
std::cout << "Server started";
acceptor->accept (*socket);
}
catch (std::exception &e)
{
std::cerr << e.what () << std::endl;
}
}
std::optional<std::string>
ServerSocket::RecieveData ()
{
try
{
char data[5000];
for (;;)
{
boost::system::error_code error;
size_t length = socket->read_some (boost::asio::buffer (data), error);
if (error == boost::asio::error::eof) return std::nullopt; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error (error); // Some other error.
return std::string{ data, length };
}
}
catch (std::exception &e)
{
std::cerr << e.what () << std::endl;
}
return {};
}
ServerSocket *ServerSocket::instance (nullptr);
ServerSocket *
ServerSocket::GetInstance ()
{
if (instance == nullptr)
{
instance = new ServerSocket ();
}
return instance;
}
Note that there are still some problems with the server:
Error handling
More than one connection
The server does not send a message if the operation was successful
If you disconnect the client the server shuts down
We could replace some pointers with optional no need to write "new"
Just make a normal class do not write it as singleton.
If you like to test the server you can run
telnet localhost 27015
and then write some text and press enter
I'm trying to create a ServerSocket as a singleton. Here's the code:
ServerSocket.h
#pragma once
#include <asio.hpp>
#include <iostream>
#include <optional>
using asio::ip::tcp;
class ServerSocket
{
public:
ServerSocket(ServerSocket& otherSingleton) = delete;
void operator= (const ServerSocket& copySingleton) = delete;
tcp::acceptor* InitAcceptor();
tcp::socket* InitSocket();
void StartServerSocket();
void SendData(std::string);
std::optional<std::string> RecieveData();
static ServerSocket* GetInstance();
private:
static ServerSocket* instance;
tcp::acceptor* acceptor;
tcp::socket* socket;
asio::io_context io_context;
ServerSocket()
{
acceptor = InitAcceptor();
socket = InitSocket();
}
~ServerSocket() {
delete socket;
delete acceptor;
std::cout << "Server closed";
}
};
ServerSocket.cpp
#include "ServerSocket.h"
#include <optional>
tcp::acceptor* ServerSocket::InitAcceptor()
{
try
{
return new tcp::acceptor(io_context, tcp::endpoint(tcp::v4(), 27015));
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return nullptr;
}
tcp::socket* ServerSocket::InitSocket()
{
try
{
tcp::socket* deReturnat = new tcp::socket(io_context);
asio::socket_base::keep_alive option(true);
deReturnat->set_option(option);
return deReturnat;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return nullptr;
}
void ServerSocket::StartServerSocket()
{
try
{
std::cout << "Server started";
acceptor->accept(*socket);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
void ServerSocket::SendData(std::string)
{
}
std::optional<std::string> ServerSocket::RecieveData()
{
try
{
char data[5000];
asio::error_code error;
size_t length = socket->read_some(asio::buffer(data), error);
if (error == asio::error::eof) return std::nullopt; // Connection closed cleanly by peer.
else if (error)
throw asio::system_error(error); // Some other error.
return std::string{ data, length };
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return {};
}
ServerSocket* ServerSocket::instance(nullptr);
ServerSocket* ServerSocket::GetInstance()
{
if (instance == nullptr)
{
instance = new ServerSocket();
}
return instance;
}
The problem is that in InitSocket, when running a debugger, it seems like set_option doesn't work for some reason, and then an exception is thrown - and I can't understand why.
The exception that is thrown is: set_option: The file handle supplied is not valid.
How can I set the keep_alive option to true in my socket? It seems like this way doesn't work.
#Vasile, your problem, as #dewaffed told you, is that you are setting the options before the socket has been opened.
I don't know what you are trying to do but I can see that you creating a new socket, which is not open, and setting the properties, that's the problem. The correct way is:
Create the Socket
Accept the new connection, with the previous socket you've created.
Once the acceptor has ended to accept a new connection, the socket has a valid File Descriptor, which is required to set the option over the socket.
Check these links:
https://en.wikipedia.org/wiki/Berkeley_sockets
Modifying boost::asio::socket::set_option, which talks about your exception.
https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/tutorial/tutdaytime2.html
My problem is related to moving Boost.Asio sockets using std::move.
I'm working on a server in C++ with switchable implementations and libraries (Boost.Asio, POSIX sockets, Winsock sockets). To achieve this, I have HttpServer and HttpClient interfaces which are implemented by classes like PosixHttpServer, PosixHttpClient, BoostHttpServer, BoostHttpClient. After binding and listening on the server, the HttpClient would be created by HttpServer::accept(). I made sure to disable copying on both HttpServer and HttpClient. I'm not planning to use async operations right now.
// main.cpp
using HttpServerPtr = std::unique_ptr<HttpServer>;
using HttpClientPtr = std::unique_ptr<HttpClient>;
...
int main(int argc, char* argv[])
{
...
//HttpServerPtr server = std::make_unique<PosixHttpServer>();
HttpServerPtr server = std::make_unique<BoostHttpServer>();
if (!server->bind(server_port)
{
...
}
if (!server->listen())
{
...
}
std::cout << "Listening to client connections at "
<< server->getAddress()
<< " on port " << std::to_string(server_port)
<< std::endl;
while (1)
{
HttpClientPtr client = server->accept();
if (!client)
{
std::cerr << "failed to accept" << std::endl;
continue;
}
std::cout << "Got connection from "
<< client->getAddress() << std::endl;
// TODO: Write HTTP response
if (!client->write("Hello World!"))
{
std::cerr << "failed to write" << std::endl;
}
}
return EXIT_SUCCESS;
}
To decouple the client socket from the HttpServer, the client boost::asio::ip::tcp::socket would be stored as a member of HttpClient. In BoostHttpServer::accept(), I am able to pass the Boost.Asio socket to the HttpClient if I use std::shared_ptr (_io_service and _acceptor are private members of HttpServer).
// boosthttpserver.cpp
using tcp = boost::asio::ip::tcp;
bool BoostHttpServer::bind(const uint16_t port)
{
try
{
tcp::resolver resolver(_io_service);
tcp::resolver::query query(tcp::v4(), boost::asio::ip::host_name(), std::to_string(port));
tcp::resolver::iterator available_endpoint = resolver.resolve(query);
tcp::resolver::iterator end;
for (; available_endpoint != end; ++available_endpoint)
{
tcp::endpoint localhost = *available_endpoint;
_acceptor.open(localhost.protocol());
_acceptor.bind(localhost);
_address = localhost.address().to_string();
break;
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
return true;
}
bool BoostHttpServer::listen()
{
try
{
_acceptor.listen();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
return true;
}
HttpClientPtr BoostHttpServer::accept()
{
try
{
auto client_socket = std::make_shared<tcp::socket>(_io_service);
_acceptor.accept(*client_socket);
return std::make_unique<BoostHttpClient>(client_socket);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
return nullptr;
}
}
// boosthttpclient.hpp
class BoostHttpClient : public HttpClient
{
public:
BoostHttpClient(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
BoostHttpClient(const BoostHttpClient&) = delete;
BoostHttpClient& operator=(const BoostHttpClient&) = delete;
virtual bool write(const std::string& message) override;
virtual std::string getAddress() const override;
private:
boost::asio::ip::tcp::socket _socket;
const std::string _address;
};
// boosthttpclient.cpp
BoostHttpClient::BoostHttpClient(
std::shared_ptr<boost::asio::ip::tcp::socket> socket
)
: _socket(socket)
, _address(socket->remote_endpoint().address().to_string())
{
}
This is the application output when I start the server and use telnet localhost 8080 for example:
Listening to connections at 127.0.0.1 on port 8080
Got connection from 127.0.0.1
However, I want to spare the heap allocation, create client_socket on the stack and transfer the ownership of it to the BoostHttpClient with std::move.
// boosthttpserver.cpp
using tcp = boost::asio::ip::tcp;
...
HttpClientPtr BoostHttpServer::accept()
{
try
{
tcp::socket client_socket(_io_service);
_acceptor.accept(client_socket);
return std::make_unique<BoostHttpClient>(std::move(client_socket));
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
return nullptr;
}
}
// boosthttpclient.hpp
class BoostHttpClient : public HttpClient
{
public:
BoostHttpClient(boost::asio::ip::tcp::socket&& socket);
BoostHttpClient(const BoostHttpClient&) = delete;
BoostHttpClient& operator=(const BoostHttpClient&) = delete;
virtual bool write(const std::string& message) override;
virtual std::string getAddress() const override;
private:
boost::asio::ip::tcp::socket _socket;
const std::string _address;
};
// boosthttpclient.cpp
BoostHttpClient::BoostHttpClient(boost::asio::ip::tcp::socket&& socket)
: _socket(std::move(socket))
, _address(socket.remote_endpoint().address().to_string())
{
}
However, the socket is gone after the move. As if its destructor got called and got closed.
Listening to connections at 127.0.0.1 on port 8080
remote_endpoint: Bad file descriptor
failed to accept
I thought that it could be as easy as passing other non-copyable objects like std::thread or std::mutex as constructor parameters, but cleary there's something I don't understand what happens at std::move(client_socket). Why doesn't it get transferred and why does it closes itself?
I use Boost 1.64.0, gcc version 7.3.1 20180712 (Red Hat 7.3.1-6) and C++17 if that matters.
I recently met a problem with boost::asio asynchronous tasks. I want to return a pointer on an object listening to a port.
It works when I use the socket.read_some method but this method blocks my main and I want my MyClass::create method to return.
So I tried a async_read call but I saw that inside my read() method, no asynchronous tasks are launched. I tried to figure out what may cause the problem but see no solution to this issue.
Here is my code, here it's not with an async_read but with an async_wait, and the same problem appears, the timer is not launched.
Thanks for any help I might get.
The header file:
#ifndef MYCLASS_HPP
#define MYCLASS_HPP
#include <memory>
#include <boost/asio.hpp>
class MyClass
{
public:
MyClass(boost::asio::io_service& ios);
void read();
void read_handler(const boost::system::error_code& error);
static std::shared_ptr<MyClass> create(std:: string const & host, uint16_t port);
bool connect (std::string const & host, uint16_t port);
void connect_handler(const boost::system::error_code& error);
boost::asio::ip::tcp::socket m_socket;
bool m_flag;
std::vector<uint8_t> m_buffer;
};
#endif
Source file:
#include "MyClass.hpp"
#include <boost/bind.hpp>
MyClass::MyClass(boost::asio::io_service& ios)
:m_flag(false), m_socket(ios), m_buffer(20)
{
}
void MyClass::read_handler(const boost::system::error_code& er)
{
std::cout << "Timer waited 5 sec" << std::endl;
}
void MyClass::read()
{
boost::asio::deadline_timer t(m_socket.get_io_service(),boost::posix_time::seconds(5));
t.async_wait(boost::bind(&MyClass::read_handler,this,boost::asio::placeholders::error));
m_socket.get_io_service().run();//Should make the io_service wait for all asynchronous tasks to finish
std::cout << "This message should be displayed after the wait" << std::endl;
}
void MyClass::connect_handler(const boost::system::error_code& error)
{
if(!error)
{
std::cout << "Connection done" << std::endl;
m_flag = 1;
}
else
{
std::cout << "Error in connection: " << error.message() << std::endl;
}
}
//connect method
bool MyClass::connect(std::string const & host, uint16_t port)
{
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(host),port);
m_socket.async_connect(endpoint,
boost::bind(&MyClass::connect_handler, this,
boost::asio::placeholders::error));
m_socket.get_io_service().run();//Wait async_connect and connect_handler to finish
if (m_flag == 0) return false;
else return true;
}
std::shared_ptr<MyClass> MyClass::create(std:: string const & host, uint16_t port)
{
boost::asio::io_service ios;
std::shared_ptr<MyClass> ptr(new MyClass(ios));
bool bol = ptr->connect(host, port);
ptr->read();
//while(1){}
if(bol == true)
{
//connection success, reading currently listening, pointer is returned to the user
return ptr;
}
else
{
//connection failure, pointer is still returned to the user but not listening as he's not connected
return ptr;
}
}
And my main:
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/asio.hpp>
#include "MyClass.hpp"
int main()
{
try
{
std::cout << "Creation of instance" << std::endl;
std::shared_ptr <MyClass> var = MyClass::create("127.0.0.1", 8301);
std::cout << "Instance created" << std::endl;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
I figured out how to solve my problem.
I had indeed problems with io_service being destroyed after "create" method, so the pointer returned in the main was not able to continue reading.
I had to call run() at one point to launch callbacks but i couldn't do it in the main, as I wanted the main to keep doing other things.
So I created a class launching a separated thread and containing an io_service. That thread is calling run() periodically. It was then added as an attribute to MyClass.
Now I have the call to "create" returning a pointer to MyClass who doesn't stop whatever asynchronous task was launched in MyClass.
Hey guys, i'm a newbie to async-programming, this is probably a stupid question, but it indeed drove me crazy!!
Here's the code (it just modified a bit from boost.asio's sample):
server.cpp:
class tcp_server
{
public:
tcp_server(boost::asio::io_service& io_service)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), 10000)),limit(0)
{
start_accept();
}
private:
void start_accept()
{
while(1)
{
if(limit <= 10)
{
std::cout << limit << std::endl;
break;
}
}
tcp::socket* socket = new tcp::socket(acceptor_.io_service());
acceptor_.async_accept(*socket,
boost::bind(&tcp_server::handle_accept, this, boost::asio::placeholders::error));
}
void handle_accept(const boost::system::error_code& error)
{
if (!error)
{
++limit ;
start_accept();
}
}
tcp::acceptor acceptor_;
int limit;
};
int main()
{
try
{
boost::asio::io_service io_service;
tcp_server server(io_service);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
client.cpp:
int main(int argc, char* argv[])
{
int i = 0;
while(1)
{
try
{
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query("127.0.0.1", "10000");
tcp::resolver::iterator endpoint_iterator =resolver.resolve(query);
tcp::endpoint endpoint = *endpoint_iterator;
tcp::socket socket(io_service);
socket.close();
socket.connect(endpoint);
std::cout << i++ << std::endl;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
return 0;
}
I just wanna limit server to accept 10 client.
However, client cout the error information after it cout "amazing" 210 (never more or less) continuous numbers.
What happend??
I've changed server.cpp a bit. First reconfigured acceptor_ on constructor. Removed while loop, added acceptor_.close();
#include <boost/asio/io_service.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using namespace boost::asio;
using namespace boost::asio::ip;
class tcp_server
{
public:
tcp_server(boost::asio::io_service& io_service)
: acceptor_(io_service),limit(0)
{
tcp::endpoint endpoint(tcp::v4(), 10000);
acceptor_.open(endpoint.protocol());
acceptor_.bind(endpoint);
acceptor_.listen(1); //accept 1 connection at a time
start_accept();
}
private:
void start_accept()
{
tcp::socket* socket = new tcp::socket(acceptor_.io_service());
acceptor_.async_accept(*socket,
boost::bind(
&tcp_server::handle_accept,
this,
socket,
boost::asio::placeholders::error));
}
void handle_accept(tcp::socket* s, const boost::system::error_code& error)
{
if (!error)
{
++limit;
if (limit < 9)
{
start_accept();
}
else
{
acceptor_.close();
}
}
}
tcp::acceptor acceptor_;
int limit;
};
int main()
{
try
{
boost::asio::io_service io_service;
tcp_server server(io_service);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
I suppose, default acceptor can async_accept 200 connection events at a time. You open a socket and close it from the client side in an infinite loop. As a result you open and close a connection 200 times, but it is still 1 connection, 1 socket.
Capping it to 1 by calling listen(1), would force the acceptor to fire an event. You increase the count, then client closes the connection. This way you correctly count each connection event.
Last note: async io uses 1 thread to process connection events, retrieved data etc... Thus, use of mutexes are not necessary.