Accepting multiple connections on a boost asio tcp socket - c++

I want to accept twice on one socket
That is, I listen on a port (an unconnected socket) and want to get two connected sockets in the end.
Said differently, if I accept twice on the same tcp socket, I'm having trouble grokking how to distinguish between the two connected sockets in asio. This is on linux.
I have a relatively simple tcp server class. It assumes that all clients that might connect to it are homogeneous: if a message is waiting to go to a client and none is connected, it can be sent to the next client that does connect. This works great with one connected socket, but now I need to listen on more than one socket (that is, two clients will connect). The homogeneity assumption is still almost true, but now I have the additional constraint that if a message is in response to someone, it should go to that someone. (Replies are often acknowledgements.)
I start by listening and accepting:
short port = kSomethingKnown;
boost::asio::io_service& io_service_;
boost::asio::ip::tcp::socket socket_(io_service_);
boost::asio::ip::tcp::acceptor acceptor_(
io_service_,
boost::asio::ip::tcp::endpoint(tcp::v4(), port));
acceptor_.async_accept(socket_, [this](boost::system::error_code ec) {
if (ec) {
// Failed to accept. Schedule to try again (not shown).
return;
}
// Accepted.
SendMessage(); // Flush any old messages, appropriate with a single client.
ReceiveHeader();
});
Comparison to the BSD socket interface
I'll explain the rest below, but this illustrates the main point. ACCEPT(2) looks like this:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
The return value, if non-zero, is the file descriptor of the connected socket. That is, if I'm on host H and listening on port P, then sockfd represents an unconnected socket,
(H, P, tcp, 0, 0)
and the file descriptor returned represents a connected socket,
(H, P, tcp, H1, P1)
where H1 is the client host and P1 is the (probably ephemeral) port on the client that is the other side of this socket. If I successfully accept a second time, I'll get another connected socket,
(H, P, tcp, H2, P2)
where at least one of H2 and P2 is different than H1 and P1. I don't see in asio how to refer to these two connected sockets. I've been reading source code, which is teaching me a great deal about how asio works but not how async_accept works.
Ancillary details
Fwiw, here are the details on the send and receive calls, but I think the above is what I really need. Once I understand that, then I use those connected sockets instead of socket_.
SendMessage() exists in a form that takes a message (just pushes it on a deque) and the form above that processes the queue. That second form looks like this:
void SendMessage() {
if (WeAreDead()) {
// This checked that the connection seems valid,
// we aren't being asked to shut down, etc.
return;
}
if (send_queue_.empty()) {
// Nothing to send.
return;
}
boost::asio::async_write(
socket_, boost::asio::buffer(send_queue_.front()),
[this](boost::system::error_code ec, size_t length) {
if (ec) {
// Failed, schedule another attempt, not shown here.
return;
}
send_queue_.pop_front();
if (!send_queue_.empty()) {
SendMessage();
}
});
}
The ReceiveHeader() (and a similar ReceiveBody()) look similar, with the key bit being a call that looks like this:
boost::asio::async_read(
socket_, boost::asio::buffer(receive_buffer_, kTcpHeaderSize),
boost::asio::transfer_exactly(kTcpHeaderSize),
[this](boost::system::error_code ec, std::size_t received_length) {
Again, the part that I'm finding confusing is related to async_accept().

You can do it like this:
acceptor_.async_accept(
[this] (std::error_code ec, tcp::socket&& new_socket) {
In this case you get a new_socket object, which represents the accepted connection. I have taken it from this example.
I hope I understood your question correctly.

Related

How to recover from network interruption using boost::asio

I am writing a server that accepts data from a device and processes it. Everything works fine unless there is an interruption in the network (i.e., if I unplug the Ethernet cable, then reconnect it). I'm using read_until() because the protocol that the device uses terminates the packet with a specific sequence of bytes. When the data stream is interrupted, read_until() blocks, as expected. However when the stream starts up again, it remains blocked. If I look at the data stream with Wireshark, the device continues transmitting and each packet is being ACK'ed by the network stack. But if I look at bytes_readable it is always 0. How can I detect the interruption and how to re-establish a connection to the data stream? Below is a code snippet and thanks in advance for any help you can offer. [Go easy on me, this is my first Stack Overflow question....and yes I did try to search for an answer.]
using boost::asio::ip::tcp;
boost::asio::io_service IOservice;
tcp::acceptor acceptor(IOservice, tcp::endpoint(tcp::v4(), listenPort));
tcp::socket socket(IOservice);
acceptor.accept(socket);
for (;;)
{
len = boost::asio::read_until(socket, sbuf, end);
// Process sbuf
// etc.
}
Remember, the client initiates a connection, so the only thing you need to achieve is to re-create the socket and start accepting again. I will keep the format of your snippet but I hope your real code is properly encapsulated.
using SocketType = boost::asio::ip::tcp::socket;
std::unique_ptr<SocketType> CreateSocketAndAccept(
boost::asio::io_service& io_service,
boost::asio::ip::tcp::acceptor& acceptor) {
auto socket = std::make_unique<boost::asio::ip::tcp::socket>(io_service);
boost::system::error_code ec;
acceptor.accept(*socket.get(), ec);
if (ec) {
//TODO: Add handler.
}
return socket;
}
...
auto socket = CreateSocketAndAccept(IOservice, acceptor);
for (;;) {
boost::system::error_code ec;
auto len = boost::asio::read_until(*socket.get(), sbuf, end, ec);
if (ec) // you could be more picky here of course,
// e.g. check against connection_reset, connection_aborted
socket = CreateSocketAndAccept(IOservice, acceptor);
...
}
Footnote: Should go without saying, socket needs to stay in scope.
Edit: Based on the comments bellow.
The listening socket itself does not know whether a client is silent or whether it got cut off. All operations, especially synchronous, should impose a time limit on completion. Consider setting SO_RCVTIMEO or SO_KEEPALIVE (per socket, or system wide, for more info How to use SO_KEEPALIVE option properly to detect that the client at the other end is down?).
Another option is to go async and implement a full fledged "shared" socket server (BOOST example page is a great start).
Either way, you might run into data consistency issues and be forced to deal with it, e.g. when the client detects an interrupted connection, it would resend the data. (or something more complex using higher level protocols)
If you want to stay synchronous, the way I've seen things handled is to destroy the socket when you detect an interruption. The blocking call should throw an exception that you can catch and then start accepting connections again.
for (;;)
{
try {
len = boost::asio::read_until(socket, sbuf, end);
// Process sbuf
// etc.
}
catch (const boost::system::system_error& e) {
// clean up. Start accepting new connections.
}
}
As Tom mentions in his answer, there is no difference between inactivity and ungraceful disconnection so you need an external mechanism to detect this.
If you're expecting continuous data transfer, maybe a timeout per connection on the server side is enough. A simple ping could also work. After accepting a connection, ping your client every X seconds and declare the connection dead if he doesn't answer.

Using boost::asio for simple udp communication

This is a simple problem, but I can't seem to figure out what I am doing wrong. I am attempting to read data sent to a port on a client using Boost and I have the following code which sets up 1) the UDP client, 2) a buffer for reading to and 3) an attempt to read from the socket:
// Set up the socket to read UDP packets on port 10114
boost::asio::io_service io_service;
udp::endpoint endpoint_(udp::v4(), 10114);
udp::socket socket(io_service, endpoint_);
// Data coming across will be 8 bytes per packet
boost::array<char, 8> recv_buf;
// Read data available from port
size_t len = socket.receive_from(
boost::asio::buffer(recv_buf,8), endpoint_);
cout.write(recv_buf.data(), len);
The problem is that the recieve_from function never returns. The server is running on another computer and generating data continuously. I can see traffic on this port on the local computer using Wireshark. So, what am I doing wrong here?
So, it turns out that I need to listen on that port for connections coming from anywhere. As such, the endpoint needs to be setup as
boost::asio::ip::udp::endpoint endpoint_(boost::asio::ip::address::from_string("0.0.0.0"), 10114);
Using this setup, I get the data back that I expect. And fyi, 0.0.0.0 is the same as INADDR_ANY.

boost asio server-client. Connect between two computers in local area network

I wrote a program to synchronize files between two computers in the same local area network, just like the DropBox. It works perfectly to synchronize files from one folder to another folder in the same computer, but when I want to test the program between two computers, it fails to connect to another computer.
my router's IP is 192.168.1.1, one host's IP is 192.168.1.101(host A), another's is 192.168.1.107(host B), they are all in the same local area network. host A runs the client program, host B runs the server program. when host A tries to connect to host B, it fails and displays the message as below:
A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond
I just don't know what's the problem in connecting to another computer in my local area network, and I display the connection code.
client:
bool Send::CheckConnect(boost::asio::ip::tcp::socket& socket)
{
boost::asio::io_service io_connect;
boost::asio::ip::tcp::resolver resolver(io_connect);
boost::asio::ip::tcp::resolver::query query("192.168.1.107", "6873");
boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query), end;
boost::system::error_code error_connect;
unsigned short count = 0;
while( count < 3 )
{
++count;
if( boost::asio::connect(socket, endpoint_iterator, error_connect) != end )
return true;
}
std::cout<<boost::system::system_error(error_connect).what()<<std::endl;
return false;
}
server:
boost::asio::io_service io_sev;
boost::asio::ip::tcp::acceptor accept_server( io_sev, boost::asio::ip::tcp::endpoint
(boost::asio::ip::tcp::v4(), 6873) );
unsigned count = 0;
boost::asio::ip::tcp::socket socket_server(io_sev);
On Server side, I dont see any accept or async_accept !
you need something like:
void ConnectionServer::creatSocketAndAccept()
{
//Accept the next connection.
acceptor.async_accept(socket,
boost::bind(&ConnectionServer::handle_accept, this, boost::asio::placeholders::error)
);
}
in case of accept (since you used connect not async_connect) you dont need to provide the boost::bind for accept handler.
hope that helps

how to unit test a simple tcp client/server on one local machine/host?

I am currently wrapping BSD sockets for the first time and also unit test my results along the way. Anyways I came across a problem while writing a simple test to test my Acceptor and TcpSocket class which is related to reusing the local host address, i.e.
Pseudocode:
//server thread
{
//binds, listens and accepts on port 50716 on localhost
TcpAcceptor acceptor(Resolver::fromService("50716"));
//i get a ECONNREFUSED error inside the accept function when trying to create newSock
TcpSocket newSock = acceptor.accept();
}
//connect in the main thread
TcpSocket connectionSocket(Resolver::resolve(Resolver::Query("localhost", "50716")));
Is it even possible to listen and connect on the same host/port? Is there any way to run a simple client/server test on the same machine/host?
Thanks!
EDIT:
Cool, things work now! Just for reference, I also noticed in the process that you don't even need to use a thread, even if you use blocking sockets to perform a simple test, if you decouple listen from accept like this:
//server socket
TcpAcceptor acceptor;
acceptor.bind(Resolver::fromService("0"));
acceptor.listen();
//client socket, blocks until connection is established
TcpSocket clientSock(SocketAddress("127.0.0.1", acceptor.address().port()));
//accept the connection, blocks until one accept is done
TcpSocket connectionSock = acceptor.accept();
//send a test message to the client
size_t numBytesSent = connectionSock.send(ByteArray("Hello World!"));
//read the message on the client socket
ByteArray msg(12);
size_t bytesReceived = clientSock.receive(msg);
std::cout<<"Num Bytes received: "<<bytesReceived<<std::endl;
std::cout<<"Message: "<<msg<<std::endl;
building the tests like this allows for nice and simple test cases even for the blocking functions.
Yes, it's possible. There is no such restriction that a server and a client must be different processes. One thread can open/listen a socket and other thread can connect to it.

boost::asio::ip::tcp::socket is connected?

I want to verify the connection status before performing read/write operations.
Is there a way to make an isConnect() method?
I saw this, but it seems "ugly".
I have tested is_open() function as well, but it doesn't have the expected behavior.
TCP is meant to be robust in the face of a harsh network; even though TCP provides what looks like a persistent end-to-end connection, it's all just a lie, each packet is really just a unique, unreliable datagram.
The connections are really just virtual conduits created with a little state tracked at each end of the connection (Source and destination ports and addresses, and local socket). The network stack uses this state to know which process to give each incoming packet to and what state to put in the header of each outgoing packet.
Because of the underlying — inherently connectionless and unreliable — nature of the network, the stack will only report a severed connection when the remote end sends a FIN packet to close the connection, or if it doesn't receive an ACK response to a sent packet (after a timeout and a couple retries).
Because of the asynchronous nature of asio, the easiest way to be notified of a graceful disconnection is to have an outstanding async_read which will return error::eof immediately when the connection is closed. But this alone still leaves the possibility of other issues like half-open connections and network issues going undetected.
The most effectively way to work around unexpected connection interruption is to use some sort of keep-alive or ping. This occasional attempt to transfer data over the connection will allow expedient detection of an unintentionally severed connection.
The TCP protocol actually has a built-in keep-alive mechanism which can be configured in asio using asio::tcp::socket::keep_alive. The nice thing about TCP keep-alive is that it's transparent to the user-mode application, and only the peers interested in keep-alive need configure it. The downside is that you need OS level access/knowledge to configure the timeout parameters, they're unfortunately not exposed via a simple socket option and usually have default timeout values that are quite large (7200 seconds on Linux).
Probably the most common method of keep-alive is to implement it at the application layer, where the application has a special noop or ping message and does nothing but respond when tickled. This method gives you the most flexibility in implementing a keep-alive strategy.
TCP promises to watch for dropped packets -- retrying as appropriate -- to give you a reliable connection, for some definition of reliable. Of course TCP can't handle cases where the server crashes, or your Ethernet cable falls out or something similar occurs. Additionally, knowing that your TCP connection is up doesn't necessarily mean that a protocol that will go over the TCP connection is ready (eg., your HTTP webserver or your FTP server may be in some broken state).
If you know the protocol being sent over TCP then there is probably a way in that protocol to tell you if things are in good shape (for HTTP it would be a HEAD request)
If you are sure that the remote socket has not sent anything (e.g. because you haven't sent a request to it yet), then you can set your local socket to a non blocking mode and try to read one or more bytes from it.
Given that the server hasn't sent anything, you'll either get a asio::error::would_block or some other error. If former, your local socket has not yet detected a disconnection. If latter, your socket has been closed.
Here is an example code:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
using namespace std;
using namespace boost;
using tcp = asio::ip::tcp;
template<class Duration>
void async_sleep(asio::io_service& ios, Duration d, asio::yield_context yield)
{
auto timer = asio::steady_timer(ios);
timer.expires_from_now(d);
timer.async_wait(yield);
}
int main()
{
asio::io_service ios;
tcp::acceptor acceptor(ios, tcp::endpoint(tcp::v4(), 0));
boost::asio::spawn(ios, [&](boost::asio::yield_context yield) {
tcp::socket s(ios);
acceptor.async_accept(s, yield);
// Keep the socket from going out of scope for 5 seconds.
async_sleep(ios, chrono::seconds(5), yield);
});
boost::asio::spawn(ios, [&](boost::asio::yield_context yield) {
tcp::socket s(ios);
s.async_connect(acceptor.local_endpoint(), yield);
// This is essential to make the `read_some` function not block.
s.non_blocking(true);
while (true) {
system::error_code ec;
char c;
// Unfortunately, this only works when the buffer has non
// zero size (tested on Ubuntu 16.04).
s.read_some(asio::mutable_buffer(&c, 1), ec);
if (ec && ec != asio::error::would_block) break;
cerr << "Socket is still connected" << endl;
async_sleep(ios, chrono::seconds(1), yield);
}
cerr << "Socket is closed" << endl;
});
ios.run();
}
And the output:
Socket is still connected
Socket is still connected
Socket is still connected
Socket is still connected
Socket is still connected
Socket is closed
Tested on:
Ubuntu: 16.04
Kernel: 4.15.0-36-generic
Boost: 1.67
Though, I don't know whether or not this behavior depends on any of those versions.
you can send a dummy byte on a socket and see if it will return an error.