Sorry to resurrect this thread, but Elysia requested a Boost.Asio example, and I think that it's related enough to this thread to continue with here. If a moderator disagrees please feel free to split it.
client.cpp:
Code:
#include <iostream>
#include <sstream>
#include <string>
#include "boost/asio/io_service.hpp"
#include "boost/asio/read.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "boost/shared_ptr.hpp"
#define PORT 4423 // a macro so you can change this at compile time
using namespace boost::asio::ip;
class ClientSocket {
private:
// pointer to io_service object, which should be shared between sockets
boost::shared_ptr<boost::asio::io_service> io_service;
boost::shared_ptr<tcp::socket> socket;
public:
ClientSocket(boost::shared_ptr<boost::asio::io_service> io_service)
: io_service(io_service) {}
/** Connects this client socket to a server socket at the given
@a host and @a port.
@throws boost::system::system_error
*/
void open(const std::string &host, unsigned short port);
bool read(std::string &data, int size);
};
void ClientSocket::open(const std::string &host, unsigned short port) {
// convert port number to string (I believe named ports work too, e.g. "ssh")
std::ostringstream portStream;
portStream << port;
tcp::resolver resolver(*io_service);
tcp::resolver::query query(host, portStream.str());
tcp::resolver::iterator it = resolver.resolve(query); // throws boost::system::system_error
tcp::resolver::iterator end;
socket = boost::shared_ptr<tcp::socket>(new tcp::socket(*io_service));
// Loop through all the possible ways of resolving the given host and port,
// trying each one until one address can be successfully connected to.
boost::system::error_code error = boost::asio::error::host_not_found;
while(error && it != end) {
if(socket->is_open()) socket->close();
socket->connect(*it++, error); // sets error instead of throwing
}
// Most boost::asio functions have two variants, one which throws an
// exception upon error, and one that sets a boost::system::error_code
// parameter.
//
// Here we've used an error code because we might have multiple endpoints
// to try connecting to. But by this point if there is still an error we
// could not connect, so mimic the boost::asio behaviour by throwing an
// exception.
if(error) {
throw boost::system::system_error(error);
}
}
bool ClientSocket::read(std::string &data, int size) {
std::vector<char> buffer(size);
boost::system::error_code error;
boost::asio::read(*socket,
boost::asio::buffer(buffer),
boost::asio::transfer_all(), error);
if(!error) {
// There's no easy way to read into an std::string directly, but it's
// possible to read into an std::vector and then use the fact that
// vectors are guaranteed to be contiguous to construct a string from
// the underlying raw string. It's also possible to use a boost::array
// instead.
data = std::string(&buffer[0], buffer.size());
return true;
}
return false;
}
int main() {
try {
// One io_service should be used to control all sockets. This allows
// e.g. async events for multiple sockets to be handled in one place,
// which can be more efficient if boost is using an operating system
// mechanism like select() under the hood.
boost::shared_ptr<boost::asio::io_service> io_service(
new boost::asio::io_service());
ClientSocket socket(io_service);
socket.open("localhost", PORT); // throws on error
std::string data;
if(socket.read(data, 10)) {
std::cout << "client: Read 10 bytes: <" << data << ">\n";
}
else {
std::cout << "client: Error reading data\n";
}
}
catch(std::exception &e) {
std::cerr << "client: Exception: " << e.what() << std::endl;
}
return 0;
}
server.cpp:
Code:
#include <iostream>
#include <string>
#include "boost/asio/io_service.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "boost/asio/write.hpp"
#define PORT 4423 // a macro so you can change this at compile time
using namespace boost::asio::ip;
class ServerSocket {
private:
// pointer to io_service object, which should be shared between sockets
// (newly accepted connections use this io_service)
boost::shared_ptr<boost::asio::io_service> io_service;
boost::shared_ptr<tcp::acceptor> acceptor;
public:
ServerSocket(boost::shared_ptr<boost::asio::io_service> io_service)
: io_service(io_service) {}
/** Begin listening on the given port.
*/
void listen(unsigned short port);
/** Accept a single connection on the server socket.
Must be called after listen().
*/
void accept();
};
void ServerSocket::listen(unsigned short port) {
acceptor = boost::shared_ptr<tcp::acceptor>(
new tcp::acceptor(
*io_service,
tcp::endpoint(tcp::v4(), port)));
}
void ServerSocket::accept() {
tcp::socket socket(*io_service);
// This will block until a client connection is detected, at which point
// the socket variable will be initialized and ready to use at this end.
acceptor->accept(socket);
std::string message = "Greetings."; // 10 characters
boost::asio::write(socket, boost::asio::buffer(message));
std::cout << "server: Sent message \"" << message << "\"\n";
// automatically close the socket when the variable goes out of scope
// (use a boost::shared_ptr to prevent this from happening)
}
int main() {
try {
boost::shared_ptr<boost::asio::io_service> io_service(
new boost::asio::io_service());
ServerSocket socket(io_service);
socket.listen(PORT); // throws on error
std::cout << "server: Listening on port " << PORT << " ...\n";
// This function blocks until a connection is received on the port
// we're listening on. If this was a real server we could do this in
// a loop, but here we just want to deal with one client.
socket.accept();
}
catch(std::exception &e) {
std::cerr << "server: Exception: " << e.what() << std::endl;
}
return 0;
}
Compile with
Code:
$ g++ client.cpp -o client -lboost_system
$ g++ server.cpp -o server -lboost_system
Boost.Asio is for the most part (maybe entirely?) a headers-only library, so there's nothing to link with for it. I used some code from Boost.System in the examples; actually maybe only the client requires the library to be linked in, I didn't try the server without it.
This dependency is not required: you can call almost any Boost.Asio function with a variant that throws exceptions, or a variant that takes a boost::system::error_code. If you want to avoid Boost.System dependencies I believe you can use only the exception variants.
Make sure port 4423 is open in your firewall, launch the server in one terminal and launch the client in another. The server will send a 10-byte message to the client and both programs will exit.
This is just a quick example to get you started. You'll soon run into problems if you try to build a larger program: for example, what if you want to read from multiple sockets at once? One way to do it is to call the .available() function to see if enough data is ready, and so not use blocking function calls. Unfortunately you can't use this method to detect if a socket has been disconnected (I did some research into this at one point and I believe it's an operating system limitation).
At some point you'll start using the asynchronous boost asio functions, for these reasons or others. The idea is simple: there are versions of functions called e.g. async_accept() to replace accept(). You pass in a function (which can be a method, if you use boost::bind) to be called when the operation completes. Your code goes on its merry way and whenever you call the run() method of the associated io_service, any waiting events will have their handlers called.
You can get away with using just synchronous I/O for a first example however, and I recommend it or else you may find Asio too confusing. I made the networking for a fairly large multiplayer game in this way without too much difficulty, although I had to write some code to e.g. detect disconnects with ping packets. In more recent projects I've found asynchronous I/O to be more useful, but it took quite some time to be understandable.
Good luck in your Asio adventures.