Reputation: 1056
I have written a program for client and server. The program currently does the following:
I'm doing this asynchronously. But, the problem is they can only send/receive one message. After that, they just terminate. Below is my code:
#include <iostream>
#include <vector>
#include<boost/asio.hpp>
std::vector<char> buff(256);
void SendHandler(boost::system::error_code ex){
std::cout << " do something here" << std::endl;
}
void ReadHandler(boost::system::error_code ex){
std::cout << " print the buffer data..." << std::endl;
std::cout << buff.data() << std::endl;
}
void Server(){
boost::asio::io_service service;
using namespace boost::asio::ip;
tcp::endpoint endpoint(tcp::v4(), 4000);
tcp::acceptor acceptor(service, endpoint);
tcp::socket socket(service);
std::cout << "[Server] Waiting for connection" << std::endl;
acceptor.accept(socket);
std::cout << "[Server] Accepted a connection from client" << std::endl;
std::string msg = "Message from server";
socket.async_send(boost::asio::buffer(msg), SendHandler);
service.run();
}
void Client(){
boost::asio::io_service service;
using namespace boost::asio::ip;
tcp::endpoint endpoint(address::from_string("127.0.0.1"), 4000);
tcp::socket socket(service);
std::cout << "[Client] Connecting to server..." << std::endl;
socket.connect(endpoint);
std::cout << "[Client] Connection successful" << std::endl;
socket.async_read_some(boost::asio::buffer(buff), ReadHandler);
service.run();
}
int main(int argc, char **argv) {
if(argc == 1){
std::cout << "Please specify s for server or c for client" << std::endl;
return -1;
}
if(argv[1][0] == 's'){
Server();
}
else{
Client();
}
return 0;
}
I want to scale this program so that:
Putting the async_send()
and service.run()
in an infinite loop didn't help. It just prints the message over and over on the client side until the client terminates.
I'm fairly new to boost::asio
even to network programming
. Please tell me where and what I should modify in my code?
Upvotes: 5
Views: 21325
Reputation: 1608
An asynchronous TCP server is a part of a distributed application that satisfies the following criteria:
A typical asynchronous TCP server works according to the following algorithm:
//responsible for handling a single client by reading the request message, processing it, and then sending back the response message.
//Each instance of the Service class is intended to handle one connected client
//by reading the request message, processing it, and then sending the response message back.
class Service
{
public:
//The class's constructor accepts a shared pointer to an object representing a socket connected to a particular client as an argument
// and caches this pointer. This socket will be used later to communicate with the client application.
Service(std::shared_ptr<asio::ip::tcp::socket> sock) : m_sock(sock)
//This method starts handling the client by initiating the asynchronous reading operation
//to read the request message from the client specifying the onRequestReceived() method as a callback.
void StartHandling()
private:
void onRequestReceived(const boost::system::error_code &ec,
std::size_t bytes_transferred)
void onResponseSent(const boost::system::error_code &ec,
std::size_t bytes_transferred)
// Here we perform the cleanup.
void onFinish()
{
delete this;
}
//To keep things simple, we implement a dummy service which only emulates the execution of certain operations
//The request processing emulation consists of performing many increment operations to emulate operations
//that intensively consume CPU and then putting the thread of control to sleep for some time to emulate I/O operations
std::string ProcessRequest(asio::streambuf &request)
private:
std::shared_ptr<asio::ip::tcp::socket> m_sock;
std::string m_response;
asio::streambuf m_request;
};
//responsible for accepting the connection requests arriving from clients and instantiating the objects of the Service class,
// which will provide the service to connected clients.
class Acceptor
{
public:
//Its constructor accepts a port number on which it will listen for the incoming connection requests as its input argument.
Acceptor(asio::io_service &ios, unsigned short port_num) : m_ios(ios),
//The object of this class contains an instance of the asio::ip::tcp::acceptor class as its member named m_acceptor,
//which is constructed in the Acceptor class's constructor.
m_acceptor(m_ios,
asio::ip::tcp::endpoint(
asio::ip::address_v4::any(),
port_num)),
m_isStopped(false)
//The Start() method is intended to instruct an object of the Acceptor class to start listening and accepting incoming connection requests.
void Start()
// Stop accepting incoming connection requests.
void Stop()
private:
void InitAccept()
void onAccept(const boost::system::error_code &ec,
std::shared_ptr<asio::ip::tcp::socket> sock)
private:
asio::io_service &m_ios;
//used to asynchronously accept the incoming connection requests.
asio::ip::tcp::acceptor m_acceptor;
std::atomic<bool> m_isStopped;
};
//represents the server itself
class Server
{
public:
Server()
// Start the server.
// Accepts a protocol port number on which the server should listen for the incoming connection requests
// and the number of threads to add to the pool as input arguments and starts the server
// Nonblocking Method
void Start(unsigned short port_num,
unsigned int thread_pool_size)
// Stop the server.
// Blocks the caller thread until the server is stopped and all the threads running the event loop exit.
void Stop()
private:
asio::io_service m_ios;
std::unique_ptr<asio::io_service::work> m_work;
std::unique_ptr<Acceptor> acc;
std::vector<std::unique_ptr<std::thread>> m_thread_pool;
};
int main()
{
unsigned short port_num = 3333;
try
{
//it instantiates an object of the Server class named srv.
Server srv;
//before starting the server, the optimal size of the pool is calculated.
// The general formula often used in parallel applications to find the optimal number of threads is the number of processors the computer has multiplied by 2.
// We use the std::thread::hardware_concurrency() static method to obtain the number of processors.
unsigned int thread_pool_size =
std::thread::hardware_concurrency() * 2;
//because this method may fail to do its job returning 0,
// we fall back to default value represented by the constant DEFAULT_THREAD_POOL_SIZE, which is equal to 2 in our case.
if (thread_pool_size == 0)
thread_pool_size = DEFAULT_THREAD_POOL_SIZE;
srv.Start(port_num, thread_pool_size);
std::this_thread::sleep_for(std::chrono::seconds(60));
srv.Stop();
}
catch (system::system_error &e)
{
std::cout << "Error occured! Error code = "
<< e.code() << ". Message: "
<< e.what();
}
return 0;
}
An asynchronous TCP client application supporting the asynchronous execution of the requests and request canceling functionality:
// Function pointer type that points to the callback
// function which is called when a request is complete.
// Based on the values of the parameters passed to it, it outputs information about the finished request.
typedef void (*Callback)(unsigned int request_id, // unique identifier of the request is assigned to the request when it was initiated.
const std::string &response, // the response data
const system::error_code &ec); // error information
// data structure whose purpose is to keep the data related to a particular request while it is being executed
struct Session
{
Session(asio::io_service &ios,
const std::string &raw_ip_address,
unsigned short port_num,
const std::string &request,
unsigned int id,
Callback callback) : m_sock(ios),
m_ep(asio::ip::address::from_string(raw_ip_address),
port_num),
m_request(request),
m_id(id),
m_callback(callback),
m_was_cancelled(false) {}
asio::ip::tcp::socket m_sock; // Socket used for communication
asio::ip::tcp::endpoint m_ep; // Remote endpoint.
std::string m_request; // Request string.
// streambuf where the response will be stored.
asio::streambuf m_response_buf;
std::string m_response; // Response represented as a string.
// Contains the description of an error if one occurs during
// the request lifecycle.
system::error_code m_ec;
unsigned int m_id; // Unique ID assigned to the request.
// Pointer to the function to be called when the request
// completes.
Callback m_callback;
bool m_was_cancelled;
std::mutex m_cancel_guard;
};
// class that provides the asynchronous communication functionality.
class AsyncTCPClient : public boost::noncopyable
{
public:
AsyncTCPClient(unsigned char num_of_threads)
// initiates a request to the server
void emulateLongComputationOp(
unsigned int duration_sec, //represents the request parameter according to the application layer protocol
const std::string &raw_ip_address, //specify the server to which the request should be sent.
unsigned short port_num, //specify the server to which the request should be sent.
Callback callback, //callback function, which will be called when the request is complete.
unsigned int request_id) // unique identifier of the request
// cancels the previously initiated request designated by the request_id argument
void cancelRequest(unsigned int request_id) //accepts an identifier of the request to be canceled as an argument.
// blocks the calling thread until all the currently running requests complete and deinitializes the client.
void close()
private:
// method is called whenever the request completes with any result.
void onRequestComplete(std::shared_ptr<Session> session)
private:
asio::io_service m_ios;
std::map<int, std::shared_ptr<Session>> m_active_sessions;
std::mutex m_active_sessions_guard;
std::unique_ptr<boost::asio::io_service::work> m_work;
std::list<std::unique_ptr<std::thread>> m_threads;
};
// a function that will serve as a callback, which we'll pass to the AsyncTCPClient::emulateLongComputationOp() method
// It outputs the result of the request execution and the response message to the standard output stream if the request is completed successfully
void handler(unsigned int request_id,
const std::string &response,
const system::error_code &ec)
int main()
{
try
{
AsyncTCPClient client(4);
// Here we emulate the user's behavior.
// creates an instance of the AsyncTCPClient class and then calls its emulateLongComputationOp() method to initiate three asynchronous requests
// User initiates a request with id 1.
client.emulateLongComputationOp(10, "127.0.0.1", 3333, handler, 1);
// Decides to exit the application.
client.close();
}
catch (system::system_error &e)
{
std::cout << "Error occured! Error code = " << e.code()
<< ". Message: " << e.what();
return e.code().value();
}
return 0;
};
1. Install CMake
cd ~
wget https://github.com/Kitware/CMake/releases/download/v3.14.5/cmake-3.14.5.tar.gz
tar xf cmake-3.14.5.tar.gz
cd cmake-3.14.5
./bootstrap --parallel=10
make -j4
sudo make -j4 install
2. Install Boost
cd ~
wget https://boostorg.jfrog.io/artifactory/main/release/1.69.0/source/boost_1_69_0.tar.gz
tar xf boost_1_69_0.tar.gz
cd boost_1_69_0
./bootstrap.sh
./b2 ... cxxflags="-std=c++0x -stdlib=libc++" linkflags="-stdlib=libc++" ...
sudo ./b2 toolset=gcc -j4 install
mkdir build
cd build
cmake ..
cmake --build .
./bin/server
we can check that server is run or not
netstat -tulpn | grep LISTEN
tcp 0 0 0.0.0.0:445 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:3333 0.0.0.0:* LISTEN 6866/./bin/server <===============
tcp 0 0 0.0.0.0:139 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:5433 0.0.0.0:* LISTEN -
tcp6 0 0 :::445 :::* LISTEN -
tcp6 0 0 :::5000 :::* LISTEN -
tcp6 0 0 :::5001 :::* LISTEN -
tcp6 0 0 :::139 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 ::1:631 :::* LISTEN -
./bin/client
Request #1 has completed. Response: Response from server
You can find the project in Boost Asio C++ Network Programming Client Server
Upvotes: 7
Reputation: 1314
Start with learning some of the basics. There are excellent tutorials and examples provided by the library that will walk you through concepts and examples of both synchronous and async servers.
http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio.html
Start at the beginning. Work through the basic concepts with timers and callbacks. Continue with the tutorial into the networking. Build on the core ideas over the course of a few hours by editing the tutorial programs to try out ideas on your own.
Then work your way over to the examples section. The 'Chat' example is really close to what you are looking to build. This specific example shows how to connections open by re-issuing async_reads in the read handlers. Asynchronous asio servers will service multiple clients as long as the service threads continue to run with the assigned async_accept task.
http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/examples/cpp11_examples.html http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/example/cpp11/chat/chat_server.cpp
Upvotes: 4