I'm trying to write a very simple client/server app with boost::socket. I need a server to run and a single client to connect, send data, disconnect and possibly reconnect later and repeat.
The code reduced to the minimum is here:
Server app:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
class TheServer
{
public:
TheServer(int port) : m_port(port)
{
m_pIOService = new boost::asio::io_service;
m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));
listenForNewConnection();
}
~TheServer()
{
m_bContinueReading = false;
m_pIOService->stop();
m_pThread->join();
delete m_pThread;
delete m_pSocket;
delete m_pAcceptor;
delete m_pIOService;
}
void listenForNewConnection()
{
if (m_pSocket)
delete m_pSocket;
if (m_pAcceptor)
delete m_pAcceptor;
// start new acceptor operation
m_pSocket = new tcp::socket(*m_pIOService);
m_pAcceptor = new tcp::acceptor(*m_pIOService, tcp::endpoint(tcp::v4(), m_port));
std::cout << "Starting async_accept" << std::endl;
m_pAcceptor->async_accept(*m_pSocket,
boost::bind<void>(&TheServer::readSession, this, boost::asio::placeholders::error));
}
void readSession(boost::system::error_code error)
{
if (!error)
{
std::cout << "Connection established" << std::endl;
while ( m_bContinueReading )
{
static unsigned char buffer[1000];
boost::system::error_code error;
size_t length = m_pSocket->read_some(boost::asio::buffer(&buffer, 1000), error);
if (!error && length != 0)
{
std::cout << "Received " << buffer << std::endl;
}
else
{
std::cout << "Received error, connection likely closed by peer" << std::endl;
break;
}
}
std::cout << "Connection closed" << std::endl;
listenForNewConnection();
}
else
{
std::cout << "Connection error" << std::endl;
}
std::cout << "Ending readSession" << std::endl;
}
void run()
{
while (m_bContinueReading)
m_pIOService->run_one();
std::cout << "Exiting run thread" << std::endl;
}
bool m_bContinueReading = true;
boost::asio::io_service* m_pIOService = NULL;
tcp::socket* m_pSocket = NULL;
tcp::acceptor* m_pAcceptor = NULL;
boost::thread* m_pThread = NULL;
int m_port;
};
int main(int argc, char* argv[])
{
TheServer* server = new TheServer(1900);
std::cout << "Press Enter to quit" << std::endl;
std::string sGot;
getline(std::cin, sGot);
delete server;
return 0;
}
Client app:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
int main(int argc, char* argv[])
{
std::cout << std::endl;
std::cout << "Starting client" << std::endl;
using boost::asio::ip::tcp;
boost::asio::io_service* m_pIOService = NULL;
tcp::socket* m_pSocket = NULL;
try
{
m_pIOService = new boost::asio::io_service;
std::stringstream sPort;
sPort << 1900;
tcp::resolver resolver(*m_pIOService);
tcp::resolver::query query(tcp::v4(), "localhost", sPort.str());
tcp::resolver::iterator iterator = resolver.resolve(query);
m_pSocket = new tcp::socket(*m_pIOService);
m_pSocket->connect(*iterator);
std::cout << "Client conected" << std::endl;
std::string hello = "Hello World";
boost::asio::write( *m_pSocket, boost::asio::buffer(hello.data(), hello.size()) );
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
hello += "(2)";
boost::asio::write(*m_pSocket, boost::asio::buffer(hello.data(), hello.size()));
}
catch (std::exception& e)
{
delete m_pSocket;
m_pSocket = NULL;
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
Note that I use non-blocking async_accept to be able to cleanly stop the server when Enter is pressed.
Under Windows, it works perfectly fine, I run the server, it outputs:
Starting async_accept
Press Enter to quit
For each client app run, it outpts:
Starting client
Client conected
and server app outputs:
Connection established
Received Hello World
Received Hello World(2)
Received error, connection likely closed by peer
Connection closed
Starting async_accept
Ending readSession
Then when I press Enter in server app console, it outputs Exiting run thread and cleanly stops.
Now, when I compile this same code under Linux, the client outputs the same as under Windows, but nothing happens on the server side...
Any idea what's wrong?
There are many questionable elements.
There is a classical data race on m_bContinueReading. You write from another thread, but the other thread may never see the change because of the data race.
The second race condition is likely your problem:
m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));
listenForNewConnection();
Here the run thread may complete before you ever post the first work. You can use a work-guard to prevent this. In your specific code you would already fix it by reordering the lines:
listenForNewConnection();
m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));
I would not do this, because I would not have those statements in my constructor body. See below for the work guard solution
There is a lot of raw pointer handling and new/delete going on, which merely invites errors.
You use the buffer assuming that it is NUL-terminated. This is especially unwarranted because you use read_some which will read partial messages as they arrive on the wire.
You use a static buffer while the code may have different instances of the class. This is very false optimization. Instead, prevent all the allocations! Combining with the previous item:
char buffer[1000];
while (m_bContinueReading) {
size_t length = m_Socket.read_some(asio::buffer(&buffer, 1000), ec);
std::cout << "Received " << length << " (" << quoted(std::string(buffer, length)) << "), "
<< ec.message() << std::endl;
if (ec.failed())
break;
}
You start a new acceptor always, where there is no need: a single acceptor can accept as many connections as you wish. In fact, the method shown runs into the problems
that lingering connections can prevent the new acceptor from binding to the same port. You could also alleviate that with
m_Acceptor.set_option(tcp::acceptor::reuse_address(true));
the destroyed acceptor may have backlogged connections, which are discarded
Typically you want to support concurrent connection, so you can split of a "readSession" and immediately accept the next connection. Now, strangely your code seems to expect clients to be connected until the server is prompted to shutdown (from the console) but after that you somehow start listening to new connections (even though you know the service will be stopping, and m_bContinueReading will remain false).
In the grand scheme of things, you don't want to destroy the acceptor unless something invalidated it. In practice this is rare (e.g. on Linux the acceptor will happily survive disabling/re-enabling the network adaptor).
you have spurious explicit template arguments (bind<void>). This is an anti-pattern and may lead to subtle problems
similar with the buffer (just say asio::buffer(buffer) and no longer have correctness concerns. In fact, don't use C-style arrays:
std::array<char, 1000> buffer;
size_t n = m_Socket.read_some(asio::buffer(buffer), ec);
std::cout << "Received " << n << " " << quoted(std::string(buffer.data(), n))
<< " (" << ec.message() << ")" << std::endl;
Instead of running a manual run_one() loop (where you forget to handle exceptions), consider "just" letting the service run(). Then you can .cancel() the acceptor to let the service run out of work.
In fact, this subtlety isn't required in your code, since your code already forces "ungraceful" shutdown anyways:
m_IOService.stop(); // nuclear option
m_Thread.join();
More gentle would be e.g.
m_Acceptor.cancel();
m_Socket.cancel();
m_Thread.join();
In which case you can respond to the completion error_code == error::operation_aborted to stop the session/accept loop.
Technically, you may be able to do away with the boolean flag altogether.
I keep it because it allows us to handle multiple session-per-thread in
"fire-and-forget" manner.
In the client you have many of the same problems, and also a gotcha where
you only look at the first resolver result (assuming there was one),
ignoring the rest. You can use asio::connect instead of
m_Socket.connect to try all resolved entries
Addressing the majority of these issues, simplifying the code:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/optional.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using namespace std::chrono_literals;
using boost::system::error_code;
class TheServer {
public:
TheServer(int port) : m_port(port) {
m_Acceptor.set_option(tcp::acceptor::reuse_address(true));
do_accept();
}
~TheServer() {
m_shutdownRequested = true;
m_Work.reset(); // release the work-guard
m_Acceptor.cancel();
m_Thread.join();
}
private:
void do_accept() {
std::cout << "Starting async_accept" << std::endl;
m_Acceptor.async_accept( //
m_Socket, boost::bind(&TheServer::on_accept, this, asio::placeholders::error));
}
void on_accept(error_code ec) {
if (!ec) {
std::cout << "Connection established " << m_Socket.remote_endpoint() << std::endl;
// leave session running in the background:
std::thread(&TheServer::read_session_thread, this, std::move(m_Socket)).detach();
do_accept(); // and immediately accept new connection(s)
} else {
std::cout << "Connection error (" << ec.message() << ")" << std::endl;
std::cout << "Ending readSession" << std::endl;
}
}
void read_session_thread(tcp::socket sock) {
std::array<char, 1000> buffer;
for (error_code ec;;) {
size_t n = sock.read_some(asio::buffer(buffer), ec);
std::cout << "Received " << n << " " << quoted(std::string(buffer.data(), n)) << " ("
<< ec.message() << ")" << std::endl;
if (ec.failed() || m_shutdownRequested)
break;
}
std::cout << "Connection closed" << std::endl;
}
void thread_func() {
// http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.effect_of_exceptions_thrown_from_handlers
for (;;) {
try {
m_IOService.run();
break; // exited normally
} catch (std::exception const& e) {
std::cerr << "[eventloop] error: " << e.what();
} catch (...) {
std::cerr << "[eventloop] unexpected error";
}
}
std::cout << "Exiting service thread" << std::endl;
}
std::atomic_bool m_shutdownRequested{false};
uint16_t m_port;
asio::io_service m_IOService;
boost::optional<asio::io_service::work> m_Work{m_IOService};
tcp::socket m_Socket{m_IOService};
tcp::acceptor m_Acceptor{m_IOService, tcp::endpoint{tcp::v4(), m_port}};
std::thread m_Thread{boost::bind(&TheServer::thread_func, this)};
};
constexpr uint16_t s_port = 1900;
void run_server() {
TheServer server(s_port);
std::cout << "Press Enter to quit" << std::endl;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
void run_client() {
std::cout << std::endl;
std::cout << "Starting client" << std::endl;
using asio::ip::tcp;
try {
asio::io_service m_IOService;
tcp::resolver resolver(m_IOService);
auto iterator = resolver.resolve("localhost", std::to_string(s_port));
tcp::socket m_Socket(m_IOService);
connect(m_Socket, iterator);
std::cout << "Client connected" << std::endl;
std::string hello = "Hello World";
write(m_Socket, asio::buffer(hello));
std::this_thread::sleep_for(100ms);
hello += "(2)";
write(m_Socket, asio::buffer(hello));
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
}
int main(int argc, char**) {
if (argc>1)
run_server();
else
run_client();
}
I am coding simple networking system based on sfml-network liblary and tcp sockets.
When i compile and run my program i am getting that output on screen(screenshot).
It looks like selector.wait() (Server.cpp:20) is not waiting for any packet and selector.isReady(TCPSOCKET) (Server.cpp:43) is not checking correctly if client is sending package to server.
Code:
main.cpp
#include <iostream>
#include "Server.h"
int main()
{
int mode = 0;
std::cin >> mode;
if(mode == 0)
{
Server server(55200);
}else if(mode == 1){
sf::TcpSocket socket;
if (socket.connect("localhost", 55200) != sf::Socket::Done)
{
std::cout << "Error1\n";
}
//Sleep(2000);
sf::Packet packet;
packet << 11;
if (socket.send(packet) != sf::Socket::Done)
{
std::cout << "Error2\n";
}
}
std::cin.get();
std::cin.get();
}
Server.h
#pragma once
#include <SFML/Network.hpp>
#include <vector>
class Server
{
private:
sf::TcpListener listener;
std::vector<sf::TcpSocket*> clients;
sf::SocketSelector selector;
unsigned short port;
public:
Server(unsigned short port);
~Server();
};
Server.cpp
#include "Server.h"
#include <iostream>
#include <SFML/Network.hpp>
#include <vector>
Server::Server(unsigned short pport)
{
port = pport;
std::cout << "Starting Server....\n";
if (listener.listen(port) != sf::Socket::Done)
{
std::cout << "Failed while starting listening on port: " << port << std::endl;
return;
}
selector.add(listener);
while(true)
{
if (selector.wait())
{
//new connection
if (selector.isReady(listener))
{
std::cout << "New connection!!\n";
sf::TcpSocket* tmp = new sf::TcpSocket; // !!!!!!!!!!!
if (listener.accept(*tmp) != sf::Socket::Done)
{
std::cout << "Error while accepting new connection\n";
delete tmp;
}
else {
selector.add(*tmp);
clients.push_back(tmp);
}
}
else {
for (int i = 0; i < clients.size(); i++)
{
//new incoming packet
if(selector.isReady(*(clients[i])))
{
sf::Packet pakiet;
if (clients[i]->receive(pakiet) != sf::Socket::Done)
{
std::cout << "Error while receiving packet\n";
}
else {
int x;
pakiet >> x;
std::cout << "Recived new data!!!: " << x << std::endl;
}
}
}
}
}
}
}
Server::~Server()
{
for (int i = 0; i < clients.size();i++)
{
delete clients[i];
}
}
You created a client which connected to the server, sent one package and at the end of the scope was destroyed. Socket at the client side doesn't exist and the connection between client-server is closed. So, how do you want to get the information about closed connection at server side ?
Function isReady returns true, then you call receive for this socket and as output you get one of Status codes: Done, NotReady, Disconnected, Error. You need to check if status is Disconnected, if so, the client socket should be removed from selector.
if(selector.isReady(*(clients[i])))
{
sf::Packet pakiet;
if ( clients[i]->receive(pakiet) == sf::Socket::Done)
{
// process data
}
else if ( clients[i]->receive(pakiet) == sf::Socket::Disconnected ) {
// delete socket, remove from selector
}
else {
//
}
}
i have a client program that connects to a server via a TCP socket, below:
int main ( )
{
std::cout << "HunterChat client starting up" << std::endl;
std::string cmd;
std::string reply;
bool cont = true;
ClientSocket client_socket ( "localhost", PORT );
try {
while(cont) {
try {
std::cout << ">> ";
// std::getline(std::cin,cmd);
gets(cmd);
if(cmd.compare("logout") == 0) {
cont = false;
break;
}
client_socket << cmd;
client_socket >> reply;
std::cout << reply << std::endl;
}
catch ( SocketException& e) {
std::cout << "Exception was caught:" << e.description() << "\n";
}
}
}
catch ( SocketException& e ) {
std::cout << "Exception was caught:" << e.description() << "\n";
}
return 0;
}
ClientSocket is a custom class that lets me set up and use the TCP connection; the stream operator is overloaded with, the following code:
int status = ::send ( m_sock, s.c_str(), s.size(), MSG_NOSIGNAL );
if ( status == -1 )
{
return false;
}
else
{
return true;
}
The TCP connection itself is working fine, so I won't clutter the post up with more of it. The problem is that one of the available commands involves sending input to a client instance while said client is still waiting for cin input. This means that the server messages only get read and written when I type something into cin. I'm trying to avoid using multithreading, so is there any way to allow cin to be interrupted without it?
Well, you could use a loop and the function kbhit() to check for user input if you really want to. However, threading seems to me such a better solution.
#include <conio.h>
#include <iostream>
using namespace std;
int main()
{
while(1)
{
if(kbhit())
{
char x = getch();
// ...
}
// check messages asynchronously here
}
}
My goal is to write a little program in c++ with boost asio that notifies me whenever an ICMP packet is received on any network interface.
The program doesn't print any errors and there are no exceptions.
But it also doesn't receive any ICMP packets sent by any program but one (the ping example of boost asio, which can be found here).
What's even stranger is the fact that the ICMP echo request packet from the boost example (after adjusting the payload and the ICMP identifier accordingly) and the default windows ICMP echo request (when using "ping" in the windows commmand line) look almost exactly the same in wireshark. The only difference beeing the identification field in the IPv4 header and thus also the ICMP checksum.
The same behaviour can be observed no matter where the echo request comes from, be it a virtual machine or another real computer in the network.
I can ping all those machines without any issues.
Disabling the windows firewall yields the same result.
OS: Windows 10 64 bit Enterprise N (10.0.10586 Build 10586)
Boost version: 1.62.0.
IDE: Microsoft Visual Studio Community 15.
Here is what I came up with:
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/icmp.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/placeholders.hpp>
#include <boost/system/error_code.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <iostream>
class ICMPReceiver {
public:
ICMPReceiver( boost::asio::io_service & IOS ):
m_sock( IOS ),
m_localEP( boost::asio::ip::icmp::v4(), 0 )
{
}
bool open() {
boost::system::error_code ec;
if ( !m_sock.is_open() ) {
m_sock.open( boost::asio::ip::icmp::v4(), ec );
if ( ec ) {
std::cerr << "Error in socket.open():\n" << ec.message() << '\n';
return false;
}
}
return true;
}
bool bind() {
boost::system::error_code ec;
m_sock.bind( m_localEP, ec );
if ( ec ) {
std::cerr << "Error in socket.bind():\n" << ec.message() << '\n';
return false;
}
}
bool startReceiving() {
try {
m_sock.async_receive_from( boost::asio::buffer( m_receiveBuffer ),
m_remoteEP,
boost::bind( &ICMPReceiver::receiveHandle,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred ) );
} catch ( std::exception const & e ) {
std::cerr << "Exception in socket.async_receive_from():\n" << e.what() << '\n';
return false;
}
return true;
}
private:
void receiveHandle( boost::system::error_code const & ec, size_t bytes ) {
if ( ec ) {
if ( ec != boost::asio::error::operation_aborted ) {
std::cerr << "Error in receiveHandle():\n" << "Code: " << ec << ": " << ec.message() << '\n';
return;
} else {
std::cerr << "operation aborted\n";
return;
}
} else {
std::cout << "ICMP packet received\n";
}
startReceiving();
}
boost::asio::ip::icmp::socket m_sock;
boost::asio::ip::icmp::endpoint m_localEP;
boost::asio::ip::icmp::endpoint m_remoteEP;
boost::array< char, 2048 > m_receiveBuffer;
};
int main() {
try {
boost::asio::io_service IOS;
ICMPReceiver receiver( IOS );
receiver.open();
receiver.bind();
receiver.startReceiving();
IOS.run();
return 0;
} catch ( std::exception const & e ) {
std::cerr << "Unhandled exception: " << e.what() << '\n';
return 1;
}
}
I'm using what looks to be a real nice API for streaming sockets found here:
http://www.pcs.cnu.edu/~dgame/sockets/socketsC++/sockets.html.
I'm having trouble accessing the IP of the connected user because its a private member of a class "Socket" that is used within another class "ServerSocket". My program looks exactly like the demo only it it forks processes.
// libraries
#include <signal.h>
#include <string>
#include <iostream>
// headers
#include "serversocket.hpp"
#include "socketexception.hpp"
#include "config.hpp"
using namespace std;
void sessionHandler( ServerSocket );
int main ( int argc, char** argv )
{
configClass config; // this object handles command line args
config.init( argc, argv ); // initialize config with args
pid_t childpid; // this will hold the child pid
signal(SIGCHLD, SIG_IGN); // this prevents zombie processes on *nix
try
{
ServerSocket server ( config.port ); // create the socket
cout << "server alive" << "\n";
cout << "listening on port: " << config.port << "\n";
while ( true )
{
ServerSocket new_client; // create socket stream
server.accept ( new_client ); // accept a connection to the server
switch ( childpid = fork() ) // fork the child process
{
case -1://error
cerr << "error spawning child" << "\n";
break;
case 0://in the child
sessionHandler( new_client ); // handle the new client
exit(0); // session ended normally
break;
default://in the server
cout << "child process spawned: " << childpid << "\n";
break;
}
}
}
catch ( SocketException& e ) // catch problem creating server socket
{
cerr << "error: " << e.description() << "\n";
}
return 0;
}
// function declarations
void sessionHandler( ServerSocket client )
{
try
{
while ( true )
{
string data;
client >> data;
client << data;
}
}
catch ( SocketException& e )
{
cerr << "error: " << e.description() << "\n";
}
}
So my question is, can I not access the IP of the client currently connected to the socket? If it has to be modified for that functionality, what would the cleanest way to do it be?
Thanks for suggestions
I was able to add these 2 functions that allowed me to get the IP only from the scope of main like this:
server.get_ip( new_client );
but what I'd really like is to get it like this new_client.ip();
here's my 2 functions, maybe you can help me further:
std::string Socket::get_ip( Socket& new_socket )
{
char cstr[INET_ADDRSTRLEN];
std::string str;
inet_ntop(AF_INET, &(m_addr.sin_addr), cstr, INET_ADDRSTRLEN);
str = cstr;
return str;
}
std::string ServerSocket::get_ip( ServerSocket& sock )
{
return Socket::get_ip( sock );
}
The Socket class you are using has a private data member:
sockaddr_in m_addr;
This contains the info of the client connected to the socket. You can get the human-readable address with:
char str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(m_addr.sin_addr), str, INET_ADDRSTRLEN);
As for the changes you need to make, either make m_addr public (not recommended) or add a member function that can return a string based on the above code sample.