Im trying to make a TCP/IP client using boost::asio in C++. I have created function that creates a socket to connect to the server
void TCP_IP_Communication::Create_Socket()
{
...
_socket = new tcp::socket(_io); //create socket
_io.run();
boost::system::error_code error= boost::asio::error::host_not_found;
try
{
while (error && endpoint_iterator != end) //if error go to next endpoint
{
_socket->close();
_socket->connect(*endpoint_iterator++, error);
}
//if error throw error
if(error)
throw boost::system::system_error(error);
//else the router is connected
boost::asio::read_until(*_socket,buf,'\n');
}}
Then I use another function/thread to send a command and receive response.
try
{
if(p=='i')
_socket->send(boost::asio::buffer(" sspi l1\n\n")); //sending signal presence for input command
else
_socket->send(boost::asio::buffer(" sspo l1\n\n")); //sending signal presence for output command
for(; ;) //loop reading all values from router
{
//wait for reply??
boost::asio::read_until(*_socket,buf,'\n');
std::istream is(&buf);
std::getline(is,this->data);
std::cout<<std::endl<<this->data;
The problem is, each time I connect to the server it gives a response
? "login"
But I need to suppress it when I send a command. Actually it shouldn't show up as I'm not connecting to the server each time I send a command,but it does. What did I do wrong here? I just cant figure it out.
The main function is like this:
int main()
{
TCP_IP_Connection router;
router.Create_Socket();
boost::thread router_thread1,router_thread2;
router_thread1=boost::thread(&TCP_IP_Connection::get_status,&router,'i');
router_thread1.join();
std::string reply="\nend of main()";
std::cout<<reply;
return 0;
}
Related
I've written a simple code sample that writes some data to the socket towards a simple TCP echo server. The data is written successfully to the socket (writtenBytes > 0), but the server doesn't respond that it has received the data.
The application is run in a Docker devcontainer, and from the development container, I'm communicating with the tcp-server-echo container on the same network.
io_service ioservice;
tcp::socket tcp_socket{ioservice};
void TestTcpConnection() {
boost::asio::ip::tcp::resolver nameResolver{ioservice};
boost::asio::ip::tcp::resolver::query query{"tcp-server-echo", "9000"};
boost::system::error_code ec{};
auto iterator = nameResolver.resolve(query, ec);
if (ec == 0) {
boost::asio::ip::tcp::resolver::iterator end{};
boost::asio::ip::tcp::endpoint endpoint = *iterator;
tcp_socket.connect(endpoint, ec);
if (ec == 0) {
std::string str{"Hello world test"};
while (tcp_socket.is_open()) {
auto writtenBytes =
boost::asio::write(tcp_socket, boost::asio::buffer(str));
if (writtenBytes > 0) {
// this line is executed successfully every time.
// writtenBytes == 13, which equals to str.length()
std::cout << "Bytes written successfully!\n";
}
using namespace std::chrono_literals;
std::this_thread::sleep_for(2000ms);
}
}
}
In this case writtenBytes > 0 is a sign of a successful write to the socket.
The echo server is based on istio/tcp-echo-server:1.2 image. I can ping it from my devcontainer by name or IP address with no issues. Also, when I write a similar code sample but using async functions (async_resolve, async_connect, except for the write operation, which is not async), and a separate thread to run ioservice, the server does see my data and responds appropriately.
Why the server doesn't see my data in case of no-async writes? Thanks in advance.
It turned out the issue was with the Docker container that received the message. The image istio/tcp-echo-server:1.2 doesn't write to logs unless you send the data with \n in the end.
I'm using secure websocket boost::beast implementation and I'd like to be able to receive new message while current one is being handled. So I've chosen to try it using co-routines (method with yield)
this is my websocket object :
using Websocket = boost::beast::websocket::stream<
boost::beast::ssl_stream<boost::beast::tcp_stream>>;
std::optional<Websocket> ws_;
And this is how I call my websocket listener code
ws_.reset();
boost::asio::spawn(ioc_,
[=](const boost::asio::yield_context &yield) {
this->startAndServeWs(yield);
});
}
And this is how my websocket handler method works : notice this it's divided into 2 parts.
First, the initialization part.
Second, the websocket mainloop in which it ready to read new message.
So my Question is whether this code below is suitable to fetch new messages from server while handling the current message (in either sendPostFetchItems or sendPostDownloadNewVersion which can take a while since they trigger http post request and wait for server response). And if it doesn't, so can I assume that the new message will be queued, waiting for the next iteration when current message handle will be completed ?
My second question is about the catch statement, and if this is the proper way to retry the connection
void Comm::startAndServeWs(const boost::asio::yield_context &yield) {
try {
// webSocet init according to ssl context and io_context.
ws_.emplace(ioc_, ctx_);
boost::beast::get_lowest_layer(ws_.value())
.expires_after(std::chrono::seconds(30));
boost::asio::ip::tcp::resolver resolver(io_context_);
auto const results =
resolver.async_resolve(ip_.value(), port_.value(), yield);
auto ep = boost::beast::get_lowest_layer(ws_.value())
.async_connect(results, yield);
// Set SNI Hostname (many hosts need this to handshake successfully)
if (!SSL_set_tlsext_host_name(ws_.value().next_layer().native_handle(),
ip_.value().c_str())) {
throw("Failed to set SNI Hostname");
}
// Update the host_ string. This will provide the value of the
// Host HTTP header during the WebSocket handshake.
// Se e https://tools.ietf.org/html/rfc7230#section-5.4
auto address = ip_.value() + std::to_string(ep.port());
// Perform the SSL handshake
ws_.value().next_layer().handshake(boost::asio::ssl::stream_base::client);
// Turn off the timeout on the tcp_stream, because
// the websocket stream has its own timeout system.
boost::beast::get_lowest_layer(ws_.value()).expires_never();
// Set suggested timeout settings for the websocket
ws_.value().set_option(
boost::beast::websocket::stream_base::timeout::suggested(
boost::beast::role_type::client));
// Set a decorator to change the User-Agent of the handshake
ws_.value().set_option(boost::beast::websocket::stream_base::decorator(
[](boost::beast::websocket::request_type &req) {
req.set(boost::beast::http::field::user_agent, kWebsocketIdentifier);
}));
Log("trying to establish websocket in address {}", address);
ws_.value().async_handshake(address, "/ws", yield);
//////////////////// here's the websocket main loop.
for (;;) {
boost::beast::flat_buffer buffer;
// Read a message into our buffer
---> ws_.value().async_read(buffer, yield);
Log("websocket response buffer = {}",boost::beast::make_printable(buffer.data()));
try {
nlohmann::json response = nlohmann::json::parse(s);
if (response["command"] == "fetchItems") {
sendPostFetchItems();
} else if (response["command"] == "getLogs") {
sendPostDownloadNewVersion();
}...
}
} catch (std::exception &e) {
Log("websocket reconnect failed. reason = {}", e.what());
ws_.reset();
timer_websocket_.expires_at(timer_websocket_.expiry() +
boost::asio::chrono::seconds(10));
timer_websocket_.async_wait(
[this]([[maybe_unused]] const boost::system::error_code &error) {
boost::asio::spawn(io_context_,
[this](const boost::asio::yield_context &yield) {
this->startAndServeWs(yield);
});
});
}
}
So, let's say I have a client, and to respond to server messages, the client must have a function that listens for them, like in my code:
int Client::loop(void *data)
{
Client *instance = (Client*)data;
for (;;)
{
boost::array<unsigned char, PACKET_LENGTH> buf;
boost::system::error_code error;
// Read any incoming package
size_t len = instance->socket.read_some(boost::asio::buffer(buf), error);
if (error == boost::asio::error::eof)
{
// Connection closed, return
return 0;
}
DataHeader header = static_cast<DataHeader>(buf[0]);
switch (header) // Let's see which type of packet the server is sending.
{
case GTREG: // Server is sending a GTREG response.
instance->getRegionResponse(buf);
break;
case PLOBJ: // Server is sending a PLOBJ response.
instance->placeObjResponse(buf);
break;
case MOVPL: // WIP.
break;
case SYOBJ: // Server is sending an object that other player placed.
instance->syncObj(buf);
break;
default:
break;
}
}
}
This function is made a thread by SDL, so that the main process of my program can do work without having to listen to the server. Now, at some point in time, I'll want to close the program, and to do so, I have to disconnect the listening socket.
This "closing function" is called by the main process of my program, so it somehow needs to tell the client thread to shutdown before closing.
Now, how do I do that? I've tried a function like this:
void Client::disconnect()
{
boost::system::error_code error;
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
socket.close();
}
However, using it crashes the application with an error.
Is there something I'm missing? Thanks in advance for any help!
You should shutdown the socket, then wait for the thread to terminate before closing the socket.
I have use boost::asio, there are 8 threads
boost::asio::io_service ios;
boost::asio::ip::tcp::acceptor(ios);
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port);
acceptor.open(endpoint.protocol());
acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor.listen();
LocalTcpServer::getInstance()->initialize(ios, acceptor, pool);
boost::thread_group th_group;
for(i=0; i< 8; i++)
th_group.add_thread(new boost::thread(boost::bind(&boost::asio::io_service::run, &ios)));
th_group.join_all();
session::start()
{
socket.async_read_some(boost::asio::buffer(buffer), m_strand.wrap(boost::bind(&session::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)))
}
session::handleread(boost::system::error_code &e, size_t byteTrans)
{
if(e || byteTrans == 0 )
{
socket.shutdown(...)
//socketRelease close the socket and delete this
timeInfo->timer->async_wait(boost::bind(socketRelease(), ...);
}
else
{
//deal with data whit pool;
}
socket.async_read_some(.....);
}
LocaltcpServer::initialize(ios, acceptor, pool){
//init, pool is inherit from threadpool, used in handle read to deal with receive data
...;
startaccept();
}
LocalTcpServer::Accept()
{
session* pSession = new session(acceptor->get_io_service, pool);
acceptor.async_accept(session->socket, boost::bind(handle_accept, this, pSession, boost::asio::placeholder::error))
}
LocalTcpServer::handle_accept(boost::system::error_code& e; ... );
{
if(e)
{
//when app run sometime(serveral hours or days, e has always error 22, means invalid argument )
LOG_ERROR << e.message() << e.value();
delete newSession;
accept();
}
else
{
session.start();
accept();
}
}
the app is work fine at first, but some times later, may serveral hours, 1 or two days later , the error comes , hander_accpte always get an err, invalid argument. so , there is no new connect,
the socket connect is almost 10000, and file open limit is 65535,
and I have use netstat to check that the socket is closed normally, there is no socket whitout closed
I wonder why the err occured, and how can I fixed it,
or if my code has some errors?
I wish that I describe the question clear. thanks.
If the listening socket has failed as well, one of the main suspect is dhcp. The interface's ip address may have changed.
In this case, all open sockets bound to that interface become invalid and must be closed, that includes the listening socket, listening must then be restarted with a new socket.
I am converting an app which had a very simple heartbeat / status monitoring connection between two services. As that now needs to be made to run on linux in addition to windows, I thought I'd use boost (v1.51, and I cannot upgrade - linux compilers are too old and windows compiler is visual studio 2005) to accomplish the task of making it platform agnostic (considering, I really would prefer not to either have two code files, one for each OS, or a littering of #defines throughout the code, when boost offers the possibility of being pleasant to read (6mos after I've checked in and forgotten this code!)
My problem now, is the connection is timing out. Actually, it's not really working at all.
First time through, the 'status' message is sent, it's received by the server end which sends back an appropriate response. Server end then goes back to waiting on the socket for another message. Client end (this code), sends the 'status' message again... but this time, the server never receives it and the read_some() call blocks until the socket times out. I find it really strange that
The server end has not changed. The only thing that's changed, is my having altered the client code from basic winsock2 sockets, to this code. Previously, it connected and just looped through send / recv calls until the program was aborted or the 'lockdown' message was received.
Why would subsequent calls (to send) silently fail to send anything on the socket and, what do I need to adjust in order to restore the simple send / recv flow?
#include <boost/signals2/signal.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
using namespace std;
boost::system::error_code ServiceMonitorThread::ConnectToPeer(
tcp::socket &socket,
tcp::resolver::iterator endpoint_iterator)
{
boost::system::error_code error;
int tries = 0;
for (; tries < maxTriesBeforeAbort; tries++)
{
boost::asio::connect(socket, endpoint_iterator, error);
if (!error)
{
break;
}
else if (error != make_error_code(boost::system::errc::success))
{
// Error connecting to service... may not be running?
cerr << error.message() << endl;
boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
}
}
if (tries == maxTriesBeforeAbort)
{
error = make_error_code(boost::system::errc::host_unreachable);
}
return error;
}
// Main thread-loop routine.
void ServiceMonitorThread::run()
{
boost::system::error_code error;
tcp::resolver resolver(io_service);
tcp::resolver::query query(hostnameOrAddress, to_string(port));
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::socket socket(io_service);
error = ConnectToPeer(socket, endpoint_iterator);
if (error && error == boost::system::errc::host_unreachable)
{
TerminateProgram();
}
boost::asio::streambuf command;
std::ostream command_stream(&command);
command_stream << "status\n";
boost::array<char, 10> response;
int retry = 0;
while (retry < maxTriesBeforeAbort)
{
// A 1s request interval is more than sufficient for status checking.
boost::this_thread::sleep_for(boost::chrono::seconds(1));
// Send the command to the network monitor server service.
boost::asio::write(socket, command, error);
if (error)
{
// Error sending to socket
cerr << error.message() << endl;
retry++;
continue;
}
// Clear the response buffer, then read the network monitor status.
response.assign(0);
/* size_t bytes_read = */ socket.read_some(boost::asio::buffer(response), error);
if (error)
{
if (error == make_error_code(boost::asio::error::eof))
{
// Connection was dropped, re-connect to the service.
error = ConnectToPeer(socket, endpoint_iterator);
if (error && error == make_error_code(boost::system::errc::host_unreachable))
{
TerminateProgram();
}
continue;
}
else
{
cerr << error.message() << endl;
retry++;
continue;
}
}
// Examine the response message.
if (strncmp(response.data(), "normal", 6) != 0)
{
retry++;
// If we received the lockdown response, then terminate.
if (strncmp(response.data(), "lockdown", 8) == 0)
{
break;
}
// Not an expected response, potential error, retry to see if it was merely an aberration.
continue;
}
// If we arrived here, the exchange was successful; reset the retry count.
if (retry > 0)
{
retry = 0;
}
}
// If retry count was incremented, then we have likely encountered an issue; shut things down.
if (retry != 0)
{
TerminateProgram();
}
}
When a streambuf is provided directly to an I/O operation as the buffer, then the I/O operation will manage the input sequence appropriately by either commiting read data or consuming written data. Hence, in the following code, command is empty after the first iteration:
boost::asio::streambuf command;
std::ostream command_stream(&command);
command_stream << "status\n";
// `command`'s input sequence contains "status\n".
while (retry < maxTriesBeforeAbort)
{
...
// write all of `command`'s input sequence to the socket.
boost::asio::write(socket, command, error);
// `command.size()` is 0, as the write operation will consume the data.
// Subsequent write operations with `command` will be no-ops.
...
}
One solution would be to use std::string as the buffer:
std::string command("status\n");
while (retry < maxTriesBeforeAbort)
{
...
boost::asio::write(socket, boost::asio::buffer(command), error);
...
}
For more details on streambuf usage, consider reading this answer.