C++ ASIO implements asynchronous socket management

Boost ASIO (Asynchronous I/O) is a C++ library for asynchronous I/O operations. The framework provides a convenient way to handle network communication, multi-threaded programming, and asynchronous operations. It is especially suitable for the development of network applications. This framework can be used from basic network communication to complex asynchronous operations, such as remote control programs, high-concurrency servers, etc. The advantage of this framework is that it allows handling of multiple concurrent connections without having to create a thread to manage each connection. The most important thing is that ASIO is a cross-platform library that can run on any platform that supports C++.

In this chapter, the author will introduce how to implement a simple asynchronous network socket application through the ASIO framework. This program supports the storage of Socket sockets. By default, the socket is placed in a Map container. When needed, only Take the socket out of the container and implement communication. When the client goes offline, it will be automatically removed from the Map container. By studying the knowledge in this chapter, readers can easily build a simple cross-platform remote control function.

AsyncTcpClient asynchronous client

The following code implements a basic client with automatic heartbeat detection, which can communicate with the server through an asynchronous connection and return different data according to different commands. The code logic is relatively simple, but in order to ensure reliability and stability, actual applications need to be further optimized, handle errors and exceptions, and add more functions and security measures.

First, we encapsulate and implement the AsyncConnect class, which mainly implements two functions. The aysnc_connect() method is used to implement asynchronous connection to the server, and the port_is_open( ) method is used to verify whether the specific port of the server is open. If it is open, it means that the server is still online. If it is not open, it means that the server is offline. Here, try to wait for a while and verify again, and then call boost:: The bind() function sets a timeout waiting time through the & amp;AsyncConnect::timer_handle() function when binding the socket.

Entering the main function, first the program keeps running through the while loop, and verifies whether it is connected to the server every 5 seconds through hander.aysnc_connect(ep, 5000) The connection is successful. If connected, enter the inner loop. In the inner loop, pass hander.port_is_open("127.0.0.1", 10000, 5000) to verify whether the specific port is open. This is mainly to ensure After the server is disconnected, the client can still jump to the external loop and continue waiting for the server to come online. When the client establishes a connection with the server, it will continue to receive specific commands from the server in the inner loop to perform different operations.

#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/noncopyable.hpp>

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

// Asynchronous connection address and port
classAsyncConnect
{<!-- -->
public:
AsyncConnect(boost::asio::io_service & ios, tcp::socket & s)
:io_service_(ios), timer_(ios), socket_(s) {<!-- -->}

// Asynchronous connection
bool aysnc_connect(const tcp::endpoint &ep, int million_seconds)
{<!-- -->
bool connect_success = false;

// Asynchronous connection, the connect_handle function will be triggered when the connection is successful.
socket_.async_connect(ep, boost::bind( & amp;AsyncConnect::connect_handle, this, _1, boost::ref(connect_success)));

//Set a timer million_seconds
timer_.expires_from_now(boost::posix_time::milliseconds(million_seconds));
bool timeout = false;

// Asynchronous wait executes timer_handle if timeout occurs
timer_.async_wait(boost::bind( & amp;AsyncConnect::timer_handle, this, _1, boost::ref(timeout)));
do
{<!-- -->
// Wait for the asynchronous operation to complete
io_service_.run_one();
// Determine if the timeout has not timed out or the connection has been established, then no longer wait.
} while (!timeout & amp; & amp; !connect_success);
timer_.cancel();
return connect_success;
}

// Verify whether the server port is open
bool port_is_open(std::string address, int port, int timeout)
{<!-- -->
try
{<!-- -->
boost::asio::io_service io;
tcp::socket socket(io);
AsyncConnect hander(io, socket);
tcp::endpoint ep(boost::asio::ip::address::from_string(address), port);
if (hander.aysnc_connect(ep, timeout))
{<!-- -->
io.run();
io.reset();
return true;
}
else
{<!-- -->
return false;
}
}
catch(...)
{<!-- -->
return false;
}
}

private:
// If the connection is successful, then connect_success = true
void connect_handle(boost::system::error_code ec, bool & amp;connect_success)
{<!-- -->
if (!ec)
{<!-- -->
connect_success = true;
}
}

//Timer timeout timeout = true
void timer_handle(boost::system::error_code ec, bool & amp;timeout)
{<!-- -->
if (!ec)
{<!-- -->
socket_.close();
timeout = true;
}
}
boost::asio::io_service & amp;io_service_;
boost::asio::deadline_timer timer_;
tcp::socket &socket_;
};

int main(int argc, char * argv[])
{<!-- -->
try
{<!-- -->
boost::asio::io_service io;
tcp::socket socket(io);
AsyncConnect hander(io, socket);
boost::system::error_code error;
tcp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 10000);

// Loop to verify whether it is online
go_: while (1)
{<!-- -->
// Verify whether the connection is successful and define the timeout as 5 seconds
if (hander.aysnc_connect(ep, 5000))
{<!-- -->
io.run();
std::cout << "Connected to the server." << std::endl;

// Loop to receive commands
while (1)
{<!-- -->
// Verify whether the address port is open, wait 5 seconds by default
bool is_open = hander.port_is_open("127.0.0.1", 10000, 5000);

//Client receives data packet
boost::array<char, 4096> buffer = {<!-- --> 0 };

// If online, continue execution
if (is_open == true)
{<!-- -->
socket.read_some(boost::asio::buffer(buffer), error);

// Determine whether the received command is GetCPU
if (strncmp(buffer.data(), "GetCPU", strlen("GetCPU")) == 0)
{<!-- -->
std::cout << "Get the CPU parameters and return them to the server." << std::endl;
socket.write_some(boost::asio::buffer("CPU: 15 %"));
}

// Determine whether the received command is GetMEM
if (strncmp(buffer.data(), "GetMEM", strlen("GetMEM")) == 0)
{<!-- -->
std::cout << "Get the MEM parameters and return them to the server." << std::endl;
socket.write_some(boost::asio::buffer("MEM: 78 %"));
}

// Determine whether the received command is to terminate the program
if (strncmp(buffer.data(), "Exit", strlen("Exit")) == 0)
{<!-- -->
std::cout << "Termination client." << std::endl;
return 0;
}
}
else
{<!-- -->
//If the connection fails, jump to the waiting stage
goto go_;
}
}
}
else
{<!-- -->
std::cout << "Connection failed, reconnecting." << std::endl;
}
}
}
catch(...)
{<!-- -->
return false;
}

std::system("pause");
return 0;
}

AsyncTcpServer asynchronous server

Next we implement an asynchronous TCP server. First we need to encapsulate and implement the CAsyncTcpServer class. This class uses multi-threading to support asynchronous communication. Each client connection will create a CTcpConnection Instances of the class are used to handle specific communication operations. When the connection is established, data is transmitted, and the connection is disconnected, the server class will notify relevant operations through the event handler to support the server-side business logic. Its header file declaration is as follows;

#ifdef _MSC_VER
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#define _WIN32_WINNT 0x0601
#define _CRT_SECURE_NO_WARNINGS
#endif

#pragma once
#include <thread>
#include <array>
#include <boost\bind.hpp>
#include <boost\\
oncopyable.hpp>
#include <boost\asio.hpp>
#include <boost\asio\placeholders.hpp>

using namespace boost::asio;
using namespace boost::asio::ip;
using namespace boost::placeholders;
using namespace std;

//Each socket connection automatically corresponds to a Tcp client connection
class CTcpConnection
{<!-- -->
public:
CTcpConnection(io_service & amp; ios, int clientId) : m_socket(ios), m_clientId(clientId){<!-- -->}
~CTcpConnection(){<!-- -->}

int m_clientId;
tcp::socket m_socket;
array<BYTE, 16 * 1024> m_buffer;
};

typedef shared_ptr<CTcpConnection> TcpConnectionPtr;

class CAsyncTcpServer
{<!-- -->
public:
class IEventHandler
{<!-- -->
public:
IEventHandler(){<!-- -->}
virtual ~IEventHandler(){<!-- -->}
virtual void ClientConnected(int clientId) = 0;
virtual void ClientDisconnect(int clientId) = 0;
virtual void ReceiveData(int clientId, const BYTE* data, size_t length) = 0;
};
public:
CAsyncTcpServer(int maxClientNumber, int port);
~CAsyncTcpServer();
void AddEventHandler(IEventHandler* pHandler){<!-- --> m_EventHandlers.push_back(pHandler); }

void Send(int clientId, const BYTE* data, size_t length);
string GetRemoteAddress(int clientId);
string GetRemotePort(int clientId);

private:
void bind_hand_read(CTcpConnection* client);
void handle_accept(const boost::system::error_code & amp; error);
void handle_read(CTcpConnection* client, const boost::system::error_code & amp; error, size_t bytes_transferred);

private:
thread m_thread;
io_service m_ioservice;
io_service::work m_work;
tcp::acceptor m_acceptor;
int m_maxClientNumber;
int m_clientId;
TcpConnectionPtr m_nextClient;
map<int, TcpConnectionPtr> m_clients;
vector<IEventHandler*> m_EventHandlers;
};

Next, we will implement the functional function in the AsyncTcpServer header file. If the reader does not understand the principle of the implementation of this functional function, he or she can submit it to ChatGPT for analysis. The function will not be explained here.

// By: Zhu Yingchun (basic improved version)
#include "AsyncTcpServer.h"

//Implementation of CAsyncTcpServer
CAsyncTcpServer::CAsyncTcpServer(int maxClientNumber, int port)
: m_ioservice()
, m_work(m_ioservice)
, m_acceptor(m_ioservice)
, m_maxClientNumber(maxClientNumber)
, m_clientId(0)
{<!-- -->
m_thread = thread((size_t(io_service::*)()) & amp;io_service::run, & amp;m_ioservice);
m_nextClient = make_shared<CTcpConnection>(m_ioservice, m_clientId);
m_clientId + + ;

tcp::endpoint endpoint(tcp::v4(), port);
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(tcp::acceptor::reuse_address(true));
m_acceptor.bind(endpoint);
m_acceptor.listen();

// Asynchronously wait for client connection
m_acceptor.async_accept(m_nextClient->m_socket, boost::bind( & amp;CAsyncTcpServer::handle_accept, this, boost::asio::placeholders::error));
}

CAsyncTcpServer::~CAsyncTcpServer()
{<!-- -->
for (map<int, TcpConnectionPtr>::iterator it = m_clients.begin(); it != m_clients.end(); + + it)
{<!-- -->
it->second->m_socket.close();
}
m_ioservice.stop();
m_thread.join();
}

//Send data packets to a specific client synchronously based on the ID number
void CAsyncTcpServer::Send(int clientId, const BYTE* data, size_t length)
{<!-- -->
map<int, TcpConnectionPtr>::iterator it = m_clients.find(clientId);
if (it == m_clients.end())
{<!-- -->
return;
}
it->second->m_socket.write_some(boost::asio::buffer(data, length));
}

//Return the client IP address based on the ID number
string CAsyncTcpServer::GetRemoteAddress(int clientId)
{<!-- -->
map<int, TcpConnectionPtr>::iterator it = m_clients.find(clientId);
if (it == m_clients.end())
{<!-- -->
return "0.0.0.0";
}
std::string remote_address = it->second->m_socket.remote_endpoint().address().to_string();
return remote_address;
}

//Return the port number based on the ID number
string CAsyncTcpServer::GetRemotePort(int clientId)
{<!-- -->
map<int, TcpConnectionPtr>::iterator it = m_clients.find(clientId);
char ref[32] = {<!-- --> 0 };
if (it == m_clients.end())
{<!-- -->
return "*";
}
unsigned short remote_port = it->second->m_socket.remote_endpoint().port();
std::string str = _itoa(remote_port, ref, 10);
return str;
}

void CAsyncTcpServer::handle_accept(const boost::system::error_code & amp; error)
{<!-- -->
if (!error)
{<!-- -->
// Determine whether the number of connections has reached the maximum limit
if (m_maxClientNumber > 0 & amp; & amp; m_clients.size() >= m_maxClientNumber)
{<!-- -->
m_nextClient->m_socket.close();
}
else
{<!-- -->
//Send client connection message
for (int i = 0; i < m_EventHandlers.size(); + + i)
{<!-- -->
m_EventHandlers[i]->ClientConnected(m_nextClient->m_clientId);
}

//Set up to receive data asynchronously
bind_hand_read(m_nextClient.get());

//Put the client connection into the customer table
m_clients.insert(make_pair(m_nextClient->m_clientId, m_nextClient));

// Reset the next client connection
m_nextClient = make_shared<CTcpConnection>(m_ioservice, m_clientId);
m_clientId + + ;
}
}

// Asynchronously wait for the next client connection
m_acceptor.async_accept(m_nextClient->m_socket, boost::bind( & amp;CAsyncTcpServer::handle_accept, this, boost::asio::placeholders::error));
}

void CAsyncTcpServer::bind_hand_read(CTcpConnection* client)
{<!-- -->
client->m_socket.async_read_some(boost::asio::buffer(client->m_buffer),
boost::bind( & amp;CAsyncTcpServer::handle_read, this, client, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
return;

client->m_socket.async_receive(boost::asio::buffer(client->m_buffer),
boost::bind( & amp;CAsyncTcpServer::handle_read, this, client, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));

boost::asio::async_read(client->m_socket, boost::asio::buffer(client->m_buffer),
boost::bind( & amp;CAsyncTcpServer::handle_read, this, client, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}

void CAsyncTcpServer::handle_read(CTcpConnection* client, const boost::system::error_code & amp; error, size_t bytes_transferred)
{<!-- -->
if (!error)
{<!-- -->
//Send information about received data
for (int i = 0; i < m_EventHandlers.size(); + + i)
{<!-- -->
m_EventHandlers[i]->ReceiveData(client->m_clientId, client->m_buffer.data(), bytes_transferred);
}
bind_hand_read(client);
}
else
{<!-- -->
//Send the message that the client is offline
for (int i = 0; i < m_EventHandlers.size(); + + i)
{<!-- -->
m_EventHandlers[i]->ClientDisconnect(client->m_clientId);
}
m_clients.erase(client->m_clientId);
}
}

AsyncTcpServer class call

The server first defines the CEventHandler class and inherits from the CAsyncTcpServer::IEventHandler interface. We need to implement three methods in this class. The method ClientConnected is used It is triggered when the client connects. The method ClientDisconnect is triggered when the logged in client leaves. When the client sends data, the ReceiveData method will be triggered.

When the method ClientConnected is triggered, it automatically puts the clientId client Socket into the tcp_client_id global container and stores it, and when ClientDisconnectWhen the client exits, it directly traverses the iteration container, finds the serial number and removes it through tcp_client_id.erase;

// Triggered when client connects
virtual void ClientConnected(int clientId)
{<!-- -->
//Add the login client to the container
tcp_client_id.push_back(clientId);
}
  
// Triggered when client exits
virtual void ClientDisconnect(int clientId)
{<!-- -->
//Remove the logged out client from the container
vector<int>::iterator item = find(tcp_client_id.begin(), tcp_client_id.end(), clientId);
if (item != tcp_client_id.cend())
tcp_client_id.erase(item);
}

Once ReceiveData receives the data, it will print it directly to the screen to achieve the purpose of receiving client parameters;

//Client gets data
virtual void ReceiveData(int clientId, const BYTE* data, size_t length)
{<!-- -->
std::cout << std::endl;
PrintLine(80);
std::cout << data << std::endl;
PrintLine(80);
std::cout << "[Shell] # ";
}

Compared with receiving data, sending data is done synchronously. When we need to send data, we only need to put the data string into a BYTE* byte array and put it in When calling tcpServer.Send, pass the required parameters, socket ID, buffer Buf data, and length to send the data to the specified client;

//Send data to the specified thread synchronously
void send_message(CAsyncTcpServer & tcpServer, int clientId, std::string message, int message_size)
{<!-- -->
// Get the length
BYTE* buf = new BYTE(message_size + 1);
memset(buf, 0, message_size + 1);

for (int i = 0; i < message_size; i + + )
{<!-- -->
buf[i] = message.at(i);
}
tcpServer.Send(clientId, buf, message_size);
}

The complete code of the client is as follows. After running the client, readers can use different commands to receive parameter return values;

#include "AsyncTcpServer.h"
#include 
#include 
#include 
#include 

using namespace std;

//Storage the current client's ID number
std::vector tcp_client_id;

// Output lines of specific length
void PrintLine(int line)
{
for (int x = 0; x < line; x + + )
{
printf("-");
}
printf("\\
");
}

class CEventHandler : public CAsyncTcpServer::IEventHandler
{
public:
// Triggered when client connects
virtual void ClientConnected(int clientId)
{
//Add the login client to the container
tcp_client_id.push_back(clientId);
}

// Triggered when client exits
virtual void ClientDisconnect(int clientId)
{
//Remove the logged out client from the container
vector::iterator item = find(tcp_client_id.begin(), tcp_client_id.end(), clientId);
if (item != tcp_client_id.cend())
tcp_client_id.erase(item);
}

//Client gets data
virtual void ReceiveData(int clientId, const BYTE* data, size_t length)
{<!-- -->
std::cout << std::endl;
PrintLine(80);
std::cout << data << std::endl;
PrintLine(80);
std::cout << "[Shell] # ";
}
};

//Send data synchronously to the specified thread
void send_message(CAsyncTcpServer & tcpServer, int clientId, std::string message, int message_size)
{
// Get the length
BYTE* buf = new BYTE(message_size + 1);
memset(buf, 0, message_size + 1);

for (int i = 0; i < message_size; i + + )
{
buf[i] = message.at(i);
}
tcpServer.Send(clientId, buf, message_size);
}

int main(int argc, char* argv[])
{
CAsyncTcpServer tcpServer(10, 10000);
CEventHandler eventHandler;
tcpServer.AddEventHandler( & amp;eventHandler);
std::string command;

while (1)
{
std::cout << "[Shell] # ";
std::getline(std::cin, command);

if (command.length() == 0)
{
continue;
}
else if (command == "help")
{
printf(" _ ____ _ _ \\
");
printf("| | _ _ / ___| ___ ___| | _____| |_ \\
");
printf("| | | | | \___ \ / _ \ / __| |/ / _ \ __| \\
");
printf("| |__| |_| | ___) | (_) | (__| < __/ |_ \\
");
printf("|_____\__, | |____/ \___/ \___|_|\_\___|\__| \\
 ");
printf(" |___/ \\
\\
");
printf("Usage: LySocket \t PowerBy: LyShark.com \\
");
printf("Optional: \\
\\
");
printf("\t ShowSocket outputs all Socket containers \\
");
printf("\t GetCPU Get CPU data \\
");
printf("\t GetMemory gets memory data \\
");
printf("\t Exit exit client \\
\\
");
}
else
{
//Define word segmenter: define the dividing symbol as [comma, space]
boost::char_separator sep(", --");
typedef boost::tokenizer> CustonTokenizer;
CustonTokenizer tok(command, sep);

// Put the word segmentation results into the vector linked list
std::vector vecSegTag;
for (CustonTokenizer::iterator beg = tok.begin(); beg != tok.end(); + + beg)
{
vecSegTag.push_back(*beg);
}
// Parse [shell] # ShowSocket
if (vecSegTag.size() == 1 & amp; & vecSegTag[0] == "ShowSocket")
{
PrintLine(80);
printf("Customer ID \t Customer IP address \t Customer port \\
");
PrintLine(80);
for (int x = 0; x < tcp_client_id.size(); x + + )
{
std::cout << tcp_client_id[x] << " \t "
<< tcpServer.GetRemoteAddress(tcp_client_id[x]) << " \t "
<< tcpServer.GetRemotePort(tcp_client_id[x]) << std::endl;
}
PrintLine(80);
}

// Parse [shell] # GetCPU --id 100
if (vecSegTag.size() == 3 & amp; & vecSegTag[0] == "GetCPU")
{
char *id = (char *)vecSegTag[2].c_str();
send_message(tcpServer, atoi(id), "GetCPU", strlen("GetCPU"));
}

// Parse [shell] # GetMemory --id 100
if (vecSegTag.size() == 3 & amp; & vecSegTag[0] == "GetMemory")
{
char* id = (char*)vecSegTag[2].c_str();
send_message(tcpServer, atoi(id), "GetMEM", strlen("GetMEM"));
}

// Parse [shell] # Exit --id 100
if (vecSegTag.size() == 3 & amp; & vecSegTag[0] == "Exit")
{
char* id = (char*)vecSegTag[2].c_str();
send_message(tcpServer, atoi(id), "Exit", strlen("Exit"));
}
}
}
return 0;
}

Case demonstration

First run the server program, and then run multiple clients to achieve automatic online;

When the user needs to communicate, he only needs to specify the id serial number to the specified Socket number;

Author of this article: Wang Rui
Link to this article: https://www.lyshark.com/post/d0805aed.html
Copyright Statement: Unless otherwise stated, all articles on this blog are licensed under the BY-NC-SA license. Please indicate the source!