I am writing my own Aerospike client in C ++ and I have a problem: although my request seems to reach the server (if you send nonsense, the connection will be dropped), the server does not return any response.
Here is my code:
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <iostream>
void read_message(boost::asio::ip::tcp::socket& socket)
{
for (;;)
{
boost::array<char, 1> buf;
boost::system::error_code error;
size_t len = socket.read_some(boost::asio::buffer(buf), error);
if (error == boost::asio::error::eof)
break;
else if (error)
throw boost::system::system_error(error);
std::cout.write(buf.data(), len);
std::cout << std::endl;
}
}
void send_message(boost::asio::ip::tcp::socket& socket, std::string message)
{
boost::array<char, 1024> buf;
std::copy(message.begin(), message.end(), buf.begin());
boost::system::error_code error;
socket.write_some(boost::asio::buffer(buf, message.size()), error);
}
int main()
{
std::cout << "Connecting to socket.." << std::endl;
boost::asio::io_service ios;
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 3000);
boost::asio::ip::tcp::socket socket(ios);
socket.connect(endpoint);
std::cout << "Connected to socket. Writing message." << std::endl;
send_message(socket, "\x02\x01\x00\x00\x00\x00\x006build\nedition\nnode\nservice\nservices\nstatistics\nversion");
std::cout << "Writed message. Reading response." << std::endl;
read_message(socket);
std::cout << "Read response. Exiting prigram." << std::endl;
socket.close();
return 0;
}
This code works correctly with, for example, 1.1.1.1:80 - HTML is returned with the words "Bad request".
You are calling socket.write_some() only once in your send_message() function. You are basically assuming that all the bytes will be sent in one call. There is no such guarantee. When I tried your code, it sent only 2 bytes in my run. Unless all bytes reach the server, it won't respond (obviously).
Related
I am trying to connect to a secure websocket using asio.
This example will work for an ip address:
#include <iostream>
#include <asio.hpp>
int main() {
asio::error_code ec;
asio::io_context context;
asio::io_context::work idleWork(context);
asio::ip::tcp::endpoint endpoint(asio::ip::make_address("51.38.81.49", ec), 80);
asio::ip::tcp::socket socket(context);
socket.connect(endpoint, ec);
if (!ec) {
std::cout << "Connected!" << std::endl;
} else {
std::cout << "Failed to connect to address: \n" << ec.message() << std::endl;
}
return 0;
}
but how would I change it so I connect to "wss://api2.example.com"?
EDIT:
Thanks for your answer karastojko - it seems to get me some of the way. I would though like to know if I am actually connected to the server, so I have updated my example with your input, added a working WSS which I know will answer and created read and write.
#include <asio.hpp>
#include <asio/ts/buffer.hpp>
std::vector<char> vBuffer(1 * 1024);
// This should output the received data
void GrabSomeData(asio::ip::tcp::socket& socket) {
socket.async_read_some(asio::buffer(vBuffer.data(), vBuffer.size()),
[&](std::error_code ec, std::size_t lenght) {
if (!ec) {
std::cout << "\n\nRead " << lenght << " bytes\n\n";
for (int i = 0; i < lenght; i++)
std::cout << vBuffer[i];
GrabSomeData(socket);
}
}
);
}
int main() {
asio::error_code ec;
asio::io_context context;
asio::io_context::work idleWork(context);
std::thread thrContext = std::thread([&]() { context.run(); });
// I hope this is what you meant
asio::ip::tcp::resolver res(context);
asio::ip::tcp::socket socket(context);
asio::connect(socket, res.resolve("echo.websocket.org", std::to_string(443)));
// Check the socket is open
if (socket.is_open()) {
// Start to output incoming data
GrabSomeData(socket);
// Send data to the websocket, which should be sent back
std::string sRequest = "Echo";
socket.write_some(asio::buffer(sRequest.data(), sRequest.size()), ec);
// Wait some time, so the data is received
using namespace std::chrono_literals;
std::this_thread::sleep_for(20000ms);
context.stop();
if (thrContext.joinable()) thrContext.join();
}
return 0;
}
For that purpose use the resolver class:
tcp::resolver res(context);
asio::ip::tcp::socket socket(context);
boost::asio::connect(socket, res.resolve("api2.example.com", 80));
I'm trying to learn how Boost.asio works. I've written a basic server and client example as such:
server.cpp
#include <iostream>
#include <string>
#include <boost/asio.hpp>
#define PORT 27015
using namespace boost::asio;
using ip::tcp;
std::string read(tcp::socket& socket) {
boost::system::error_code error;
boost::asio::streambuf buffer;
boost::asio::read(socket, buffer, boost::asio::transfer_all(), error);
if (error) {
std::cerr << "read error: " << error.message() << "\n";
return "ERROR";
}
else {
std::string data = boost::asio::buffer_cast<const char*>(buffer.data());
return data;
}
}
void send(tcp::socket& socket, const std::string& message) {
boost::system::error_code error;
boost::asio::write(socket, boost::asio::buffer(message), error);
if (error)
std::cerr << "send error: " << error.message() << "\n";
else
std::cout << "sent \"" << message << "\" to the client" << "\n";
}
int main() {
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), PORT)); // create listener for new connection(s)
tcp::socket socket(io_service); // socket creation
std::cout << "awaiting connection..." << "\n";
acceptor.accept(socket); // direct connection(s) to the socket we created
std::cout << "accepted connection!" << "\n";
std::string received = read(socket); // receive data
std::cout << "received message: " << received << "\n";
send(socket, "hello from server!"); // send data
}
client.cpp
#include <iostream>
#include <string>
#include <boost/asio.hpp>
#define PORT 27015
using namespace boost::asio;
using ip::tcp;
int main(int argc, char *argv[])
{
boost::asio::io_service io_service;
tcp::socket socket(io_service); // socket creation
std::string server_ipv4_address = "192.168.1.2";
std::cout << "connecting to server at " << server_ipv4_address << "\n";
try {
socket.connect(tcp::endpoint(boost::asio::ip::address::from_string(server_ipv4_address), PORT)); // connection
std::cout << "connected!" << "\n";
}
catch (const boost::system::system_error& e) {
std::cerr << "error while connecting: " << e.what() << "\n";
return -1;
}
boost::system::error_code error; // error holder
std::string message = "hello from client!!\n";
boost::asio::write(socket, boost::asio::buffer(message), error); // send message to server
if (error)
std::cerr << "send failed: " << error.message() << "\n";
else
std::cout << "sent \"" << message << "\" to the server" << "\n";
boost::asio::streambuf receive_buffer;
boost::asio::read(socket, receive_buffer, boost::asio::transfer_all(), error); // receive from server
if (error && error != boost::asio::error::eof)
std::cerr << "receive failed: " << error.message() << "\n";
else {
std::string data = boost::asio::buffer_cast<const char*>(receive_buffer.data());
std::cout << "received data: " << data << "\n";
}
}
The connection gets established properly, but the read() function from the server blocks the program as either it does not receive data from the client, or there is a problem with the way I'm calling it. What seems to be the issue with the boost::asio::read() here?
Everything works properly if I swap boost::asio::read with boost::asio::read_until as shown below. Why does the function work properly in the client but not in the server?
std::string read(tcp::socket& socket) {
boost::system::error_code error;
boost::asio::streambuf buffer;
boost::asio::read_until(socket, buffer, "\n");
std::string data = boost::asio::buffer_cast<const char*>(buffer.data());
return data;
}
Read with the completion condition transfer_all means it will just keep reading until the buffer is full or the connection becomes invalid.
The buffer will "never" be full (since it's a DynamicBuffer).
So that leaves the cause that the client never hangs up.
Everything works properly if I swap boost::asio::read with boost::asio::read_until as shown below.
Exactly. Because then you have another reason to stop reading. Mind you, it could still block forever (when a '\n' never arrives).
Why does the function work properly in the client but not in the server?
It doesn't. It appears to because the server, apparently, does shutdown the connection (signalling EOF). [You would notice this because a subsequent read would return error_code boost::asio::error::eof.]
I have the following simple coroutine-based server:
class Server
{
private:
boost::asio::io_service Service;
boost::asio::ip::tcp::acceptor Acceptor;
boost::asio::ip::tcp::socket Socket;
private:
void Accept(boost::asio::yield_context Yield);
void Write(boost::asio::yield_context Yield);
public:
Server(): Acceptor(Service), Socket(Service) {}
void Open(unsigned short PortNum);
void Run();
void Stop();
};
void Server::Accept(boost::asio::yield_context Yield)
{
boost::system::error_code ec;
for (;;)
{
Socket.close();
Acceptor.async_accept(Socket,Yield[ec]);
spawn(Yield,std::bind(&Server::Write,this,Yield[ec]));
}
}
void Server::Write(boost::asio::yield_context Yield)
{
char InBuffer[1024]= {};
std::size_t Size;
boost::system::error_code ec;
double Data= 6.66;
for (;;)
{
boost::asio::streambuf OutBuffer;
std::ostream os(&OutBuffer);
Size= Socket.async_read_some(boost::asio::buffer(InBuffer),Yield[ec]);
if (ec)
break;
os.write(reinterpret_cast<const char *>(&Data),sizeof(double));
Socket.async_write_some(OutBuffer.data(),Yield[ec]);
if (ec)
break;
}
}
void Server::Open(unsigned short PortNum)
{
Acceptor.open(boost::asio::ip::tcp::v4());
Acceptor.bind({{},PortNum});
Acceptor.listen();
}
void Server::Run()
{
spawn(Service,std::bind(&Server::Accept,this,std::placeholders::_1));
Service.run();
}
void Server::Stop()
{
Service.stop();
}
I want to run this server on a thread and stop it cleanly when the main program is about to finish:
int main()
{
Server s;
s.Open(1024);
std::thread Thread(&Server::Run,&s);
Sleep(10'000);
s.Stop();
Thread.join();
}
Unfortunately, if there is a connected socket, when I call Stop an exception boost::coroutines::detail::forced_unwind is thrown.
I have also tried creating an explicit strand and dispatching a Socket.close() before stopping with the same result.
Is there something wrong with this approach?
I’m having trouble trying to stop gracefully a similar server ( stackoverflow.com/questions/50833730/…). – metalfox 4 hours ago
Here's a minimal change that shows how to handle
an Exit command that closes a session
a Shutdown command that closes the server (so it stops accepting connections and terminates after the last session exits)
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
using boost::asio::ip::tcp;
using boost::system::error_code;
using boost::asio::streambuf;
int main() {
boost::asio::io_service svc;
tcp::acceptor a(svc);
a.open(tcp::v4());
a.set_option(tcp::acceptor::reuse_address(true));
a.bind({{}, 6767}); // bind to port 6767 on localhost
a.listen(5);
using session = std::shared_ptr<tcp::socket>;
std::function<void()> do_accept;
std::function<void(session)> do_session;
do_session = [&](session s) {
// do a read
auto buf = std::make_shared<boost::asio::streambuf>();
async_read_until(*s, *buf, "\n", [&,s,buf](error_code ec, size_t /*bytes*/) {
if (ec)
std::cerr << "read failed: " << ec.message() << "\n";
else {
std::istream is(buf.get());
std::string line;
while (getline(is, line)) // FIXME being sloppy with partially read lines
{
async_write(*s, boost::asio::buffer("Ack\n", 4), [&,s,buf](error_code ec, size_t) {
if (ec) std::cerr << "write failed: " << ec.message() << "\n";
});
if (line == "Exit") {
std::cout << "Exit received\n";
return;
}
if (line == "Shutdown") {
std::cout << "Server shutdown requested\n";
a.close();
return;
}
}
do_session(s); // full duplex, can read while writing, using a second buffer
}
});
};
do_accept = [&] {
auto s = std::make_shared<session::element_type>(svc);
a.async_accept(*s, [&,s](error_code ec) {
if (ec)
std::cerr << "accept failed: " << ec.message() << "\n";
else {
do_session(s);
do_accept(); // accept the next
}
});
};
do_accept(); // kick-off
svc.run(); // wait for shutdown (Ctrl-C or failure)
}
Note the sample sessions
echo -en "hello world\nExit\n" | netcat 127.0.0.1 6767
echo -en "hello world\nShutdown\n" | netcat 127.0.0.1 6767
Printing
Ack
Ack
Ack
Ack
Exit received
Server shutdown requested
accept failed: Operation canceled
A Terminate Command
If you want a "Terminate" command that actively closes all open sessions and shuts down the server, you'll have to
keep a list of sessions
or use signal
You can see code for both approaches here: Boost ASIO: Send message to all connected clients
The simplest way to integrate with the current sample:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <list>
using boost::asio::ip::tcp;
using boost::system::error_code;
using boost::asio::streambuf;
int main() {
boost::asio::io_service svc;
tcp::acceptor a(svc);
a.open(tcp::v4());
a.set_option(tcp::acceptor::reuse_address(true));
a.bind({{}, 6767}); // bind to port 6767 on localhost
a.listen(5);
using session = std::shared_ptr<tcp::socket>;
using sessref = std::weak_ptr<tcp::socket>;
std::function<void()> do_accept;
std::function<void(session)> do_session;
std::list<sessref> session_list;
auto garbage_collect_sessions = [&session_list] {
session_list.remove_if(std::mem_fn(&sessref::expired));
};
do_session = [&](session s) {
// do a read
auto buf = std::make_shared<boost::asio::streambuf>();
async_read_until(*s, *buf, "\n", [&,s,buf](error_code ec, size_t /*bytes*/) {
if (ec)
std::cerr << "read failed: " << ec.message() << "\n";
else {
std::istream is(buf.get());
std::string line;
while (getline(is, line)) // FIXME being sloppy with partially read lines
{
async_write(*s, boost::asio::buffer("Ack\n", 4), [&,s,buf](error_code ec, size_t) {
if (ec) std::cerr << "write failed: " << ec.message() << "\n";
});
if (line == "Exit") {
std::cout << "Exit received\n";
return;
}
if (line == "Shutdown") {
std::cout << "Server shutdown requested\n";
a.close();
return;
}
if (line == "Terminate") {
std::cout << "Server termination requested\n";
a.close();
for (auto wp : session_list) {
if (auto session = wp.lock())
session->close();
}
return;
}
}
do_session(s); // full duplex, can read while writing, using a second buffer
}
});
};
do_accept = [&] {
auto s = std::make_shared<session::element_type>(svc);
a.async_accept(*s, [&,s](error_code ec) {
if (ec)
std::cerr << "accept failed: " << ec.message() << "\n";
else {
garbage_collect_sessions();
session_list.push_back(s);
do_session(s);
do_accept(); // accept the next
}
});
};
do_accept(); // kick-off
svc.run(); // wait for shutdown (Ctrl-C or failure)
}
Which obvioiusly uses a session_list to implement the "Terminate" command:
if (line == "Terminate") {
std::cout << "Server termination requested\n";
a.close();
for (auto wp : session_list) {
if (auto session = wp.lock())
session->close();
}
return;
}
This is a extension to my previous question here with a "minimum viable example" of the code.
I have developed a UDPClient & UDPServer app as below. These apps are very much similar to the boost official udp client & boost official udp server:
UDPClient.cpp
#include <iostream>
#include <algorithm>
#include <boost/asio.hpp>
using boost::asio::ip::udp;
struct Person {
char name[1024];
int age;
};
int main(int argc, char* argv[]) {
// Turn this variable TRUE on to send structs of "Person"
bool send_structs = false;
try {
if (argc != 2) {
std::cerr << "Usage: client <host>" << std::endl;
return 1;
}
boost::asio::io_service io_service;
udp::resolver resolver(io_service);
udp::resolver::query query(udp::v4(), argv[1], "7123");
udp::endpoint receiver_endpoint = *resolver.resolve(query);
udp::socket socket(io_service);
socket.open(udp::v4());
for (;;) {
if (send_structs == true) {
// Send structs
Person send_data = { "something_from_client", 123 };
boost::system::error_code ignored_error;
socket.send_to(boost::asio::buffer(&send_data, sizeof(send_data)), receiver_endpoint, 0, ignored_error);
udp::endpoint sender_endpoint;
Person receive_data;
std::size_t len = socket.receive_from(boost::asio::buffer(&receive_data, sizeof(receive_data)), sender_endpoint);
std::cout << "After receiving at client header is " << receive_data.name << std::endl;
std::cout << "After receiving at client version is " << receive_data.age << std::endl;
}
else {
// Send & receive char vectors
std::vector<unsigned char> send_data(1024);
std::string str = "Hello from Client";
std::copy (str.begin(),str.begin(), send_data.begin());
boost::system::error_code ignored_error;
std::cout << "Before sending vector length is " << send_data.size() << std::endl;
socket.send_to(boost::asio::buffer(send_data.data(), send_data.size()), receiver_endpoint, 0, ignored_error);
udp::endpoint sender_endpoint;
std::vector<unsigned char> receive_data;
receive_data.resize(1024);
std::size_t bytes_rec = socket.receive_from(boost::asio::buffer(receive_data), sender_endpoint);
std::cout << "After receiving at client vector size is " << receive_data.size() << std::endl;
std::cout << "Received bytes at client are " << bytes_rec << std::endl;
}
}
}
catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
UDPServer.cpp
#include <iostream>
#include <algorithm>
#include <boost/asio.hpp>
using boost::asio::ip::udp;
struct Person {
char name[1024];
int age;
};
int main() {
// Turn this variable TRUE on to send structs of "Person"
bool send_structs = false;
try {
boost::asio::io_service io_service;
udp::socket socket(io_service, udp::endpoint(udp::v4(), 7123));
for (;;) {
udp::endpoint remote_endpoint;
if (send_structs == true) {
// Send structs
boost::system::error_code error;
Person receive_data;
size_t len = socket.receive_from(boost::asio::buffer(&receive_data, sizeof(receive_data)), remote_endpoint, 0, error);
std::cout << "After receiving at server header is " << receive_data.name << std::endl;
std::cout << "After receiving at server version is " << receive_data.age << std::endl;
if (error && error != boost::asio::error::message_size)
throw boost::system::system_error(error);
boost::system::error_code ignored_error;
Person send_data = { "something_from_server", 456 };
socket.send_to(boost::asio::buffer(&send_data, sizeof(send_data)), remote_endpoint, 0, ignored_error);
}
else {
// Send & receive char vectors
boost::system::error_code error;
std::vector<unsigned char> receive_data;
receive_data.resize(1024);
std::size_t bytes_rec = socket.receive_from(boost::asio::buffer(receive_data), remote_endpoint, 0, error);
std::cout << "Bytes received at server are " << bytes_rec << std::endl;
std::cout << "After receiving at server vector length is " << receive_data.size() << std::endl;
if (error && error != boost::asio::error::message_size)
throw boost::system::system_error(error);
boost::system::error_code ignored_error;
std::vector<unsigned char> send_data(1024);
std::string str = "Hello from Server";
std::copy (str.begin(),str.begin(), send_data.begin());
std::cout << "Before sending vector length is " << send_data.size() << std::endl;
std::size_t bytes_sent = socket.send_to(boost::asio::buffer(send_data.data(), send_data.size()), remote_endpoint, 0, ignored_error);
std::cout << "Bytes sent from server are " << bytes_sent << std::endl;
}
}
}
catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
I am building the apps on clang OSX with following compile command:
clang++ -std=c++14 -lboost_system UDPClient.cpp -o UDPClient
clang++ -std=c++14 -lboost_system UDPServer.cpp -o UDPServer
Run the apps on console like so. Run the server firstly before client has started:
./UDPServer
./UDPClient <host_ip>
Here is the problem:
When I send structs by setting boolean flag send_structs as true on both sides then it works all fine sending the structs nicely. But when I set send_structs as false on both sides & try the same technique to send an std::vector<unsigned char>, it strangely doesn't work.
So, How can I send an std::vector of unsigned char over an UDP socket using boost asio?
Snapshot of server app log:
After receiving at server vector length is 0
Before sending vector length is 1024
Bytes sent from server are 1024
Bytes received at server are 0
After receiving at server vector length is 0
Before sending vector length is 1024
Bytes sent from server are 1024
Bytes received at server are 0
After receiving at server vector length is 0
Snapshot of client app log:
Before sending vector length is 1024
After receiving at client vector size is 0
Received bytes at client are 0
Before sending vector length is 1024
After receiving at client vector size is 0
As you can see, after receiving the vector length is zero !!
PS: I also noticed that while sending the structs, when I start the server firstly, it waits until the client has opened the socket. But, when sending vectors, server just blindly keeps looping on the send. Is that a mistake in my code or expected behaviour? (secondary question)
If you want to receive into a vector, you will have to size the buffer to indicate how much you want to receive. This is exactly what happened in this answer: boost asio write/read vector
Right now, this:
std::vector<unsigned char> receive_data;
std::size_t bytes_rec = socket.receive_from(boost::asio::buffer(receive_data), sender_endpoint);
Asks to receive 0 bytes (because the size of the receiving buffer is 0).
You might know the expected datagram size. Otherwise, you can simply over-dimension the buffer, because the received bytes_rec will tell you how many elements have been received.
I wonder how to implement a synchronous socket accept with boost which can be terminated.
To demonstrate my problem I slightly modified the synchonous tcp echo example.
Note: the provided code seems to be working on Windows platforms but i'm having problems on a Linux machine.
Let's say the server receives a quit message and now wants to terminate an endless loop which accepts new connections.
Most tutorials etc. recommend you to run acceptor->close() in this case. But as
this post states, the results might be undefined if close() is called from another thread.
If you do so, accept() won't terminate this time but when another client tries to connect it returnes an error (on Linux!)
So my question again: how do I correctly terminate a server which is based on boost::asio which continuously synchronously accepts connections?
Here the code:
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/smart_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
void session(boost::shared_ptr<tcp::socket> sock, tcp::acceptor *acceptor )
{
try {
for (;;) {
char data[ 1024 ];
boost::system::error_code error;
size_t length = sock->read_some(boost::asio::buffer(data), error);
if (error == boost::asio::error::eof) { break; }
else if (error) { throw boost::system::system_error(error); }
if( std::string("quit") == data ) { // TRY TO CANCEL THE ACCEPT
acceptor->close();
break;
}
boost::asio::write(*sock, boost::asio::buffer(data, length));
}
}
catch (std::exception& e) { std::cerr << "exception in thread: " << e.what() << "\n"; }
}
void server(boost::asio::io_service& io_service, short port)
{
tcp::acceptor a( io_service, tcp::endpoint(tcp::v4(), port) );
for (;;) {
boost::shared_ptr<tcp::socket> sock(new tcp::socket(io_service));
boost::system::error_code error;
a.accept( *sock, error );
if( !error ) {
boost::thread t( boost::bind( session, sock, &a ) );
}
else {
std::cout << "acceptor canceled "<< error << std::endl;
break;
}
}
}
int main(int argc, char* argv[])
{
try{
// ..check args here...
boost::asio::io_service io_service;
server(io_service, std::atoi(argv[1]));
}
catch (std::exception& e) {std::cerr << "Exception: " << e.what() << "\n";}
return 0;
}