boost async_accept not working with the boost asio use_future option - sockets

I want to listen on a boost::asio::ip::tcp::socket with a timeout. For this, I am using the std::future::wait_for function. Below is my code:
std::optional<boost::asio::ip::tcp::socket> server::listen()
{
boost::asio::ip::tcp::socket sock(io_service);
std::future<void> accept_status = acceptor.async_accept(
sock, boost::asio::use_future);
if (accept_status.wait_for(std::chrono::seconds(10)) == std::future_status::timeout)
{
// I hope there's no race-condition between
// accepting a connection and calling cancel
acceptor.cancel();
std::cerr << "Timeout" << std::endl;
return {};
}
std::cerr << "Accepted a connection" << std::endl;
return {std::move(sock)};
}
This is not working though: the client is able to connect, but I still get a timeout. Which means that the future object and the asynchronous accept function aren't communicating. What am I missing?
I am using Boost version 1.65.
For Explorer_N, following is a complete program that does not work the way I expect:
#include <boost/asio.hpp>
#include <boost/asio/use_future.hpp>
#include <chrono>
#include <future>
#include <iostream>
#include <thread>
using namespace std;
void server_listen() {
boost::asio::io_service io_service;
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 31132);
boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint);
boost::asio::ip::tcp::socket socket(io_service);
std::future<void> accept_status = acceptor.async_accept(
socket, boost::asio::use_future);
while(true) {
if(accept_status.wait_for(std::chrono::seconds(10)) == std::future_status::timeout) {
acceptor.cancel();
std::cerr << "Timeout\n";
} else {
break;
}
}
// if I replace the lines starting from the async_accept call
// by just the following, everything works as expected
// acceptor.accept(socket);
std::cout << "Accepted a connection\n";
while(true) {
}
}
void client_connect() {
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::socket socket(io_service);
boost::asio::ip::tcp::endpoint endpoint(*resolver.resolve({"127.0.0.1", std::to_string(31132)}));
socket.connect(endpoint);
std::cout << "Connected to server\n";
while(true) {
}
}
int main() {
std::thread server(server_listen);
std::this_thread::sleep_for(std::chrono::seconds(2));
std::thread client(client_connect);
while(true) {
}
}
Compiled by g++ -std=c++17 <program>.cpp -lpthread -lboost_system -o <program>.
The output I get is:
Connected to server
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
...

To answer your claims:
"future object and the asynchronous accept function aren't communicating" -- not possible.
"the client is able to connect, but I still get a timeout.", -- your client connecting to a listener is one event and executing completion-handler (setting promise) is an another event.
So connection could've accepted at 9th second and callback would have scheduled to run at 11th second (for instance).
Remember, we are dealing with asynchronous ops, so making absolute prediction on future events is not something right I would say.
apart form that
// I hope there's no race-condition between
// accepting a connection and calling cancel
acceptor.cancel();
std::cerr << "Timeout" << std::endl;
return {};
acceptor.cancel(); just collect the pending waiters, and complete them with ec set to operation_aborted, if the handlers are already out to completion event queue, then cancel() is a no-op
Extending my answer based on OP's recent edit:
using namespace std;
void server_listen() {
boost::asio::io_service io_service;
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 31132);
boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint);
boost::asio::ip::tcp::socket socket(io_service);
auto work = make_work_guard(io_service);
using type= std::decay_t<decltype(work)>;
std::thread io([&](){io_service.run();});
std::future<void> accept_status = acceptor.async_accept(
socket, boost::asio::use_future);
if(accept_status.wait_for(std::chrono::seconds(10)) == std::future_status::timeout) {
acceptor.cancel();
std::cerr << "Timeout\n";
work.~type();
//break;
} else {
std::cout<<"future is ready\n";
work.~type();
// break;
}
io.join();
// if I replace the lines starting from the async_accept call
// by just the following, everything works as expected
// acceptor.accept(socket);
std::cout << "Accepted a connection\n";
}
void client_connect() {
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::socket socket(io_service);
boost::asio::ip::tcp::endpoint endpoint(*resolver.resolve({"127.0.0.1", std::to_string(31132)}));
socket.connect(endpoint);
std::cout << "Connected to server\n";
}
enter code here
int main() {
std::thread server(server_listen);
std::this_thread::sleep_for(std::chrono::seconds(2));
std::thread client(client_connect);
server.join(); client.join();
}
There are many things to take care of in your program (avoid unnecessary spin-loop, don't forgot to either join or detach the std::thread and make sure you call io_service::run when you use async* version)
Start
Connected to server
future is ready
Accepted a connection
0
Finish

Related

Boost Beast async rest client : async_resolve - resolve: Host not found (authoritative)

I have the async boost rest client code. I am able to compile and run this code using Cygwin on Windows.
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace http = boost::beast::http; // from <boost/beast/http.hpp>
void
fail(boost::system::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}
// Performs an HTTP GET and prints the response
class session : public std::enable_shared_from_this<session>
{
tcp::resolver resolver_;
tcp::socket socket_;
boost::beast::flat_buffer buffer_; // (Must persist between reads)
http::request<http::empty_body> req_;
http::response<http::string_body> res_;
public:
// Resolver and socket require an io_context
explicit
session(boost::asio::io_context& ioc)
: resolver_(ioc)
, socket_(ioc)
{
}
// Start the asynchronous operation
void
run(char const* host, char const* port, char const* target, int version)
{
// Set up an HTTP GET request message
req_.version(version);
req_.method(http::verb::get);
req_.target(target);
req_.set(http::field::host, host);
req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Look up the domain name
resolver_.async_resolve(host, port,std::bind( &session::on_resolve, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
void
on_resolve( boost::system::error_code ec, tcp::resolver::results_type results)
{
if(ec) {
return fail(ec, "resolve");
}
// Make the connection on the IP address we get from a lookup
boost::asio::async_connect(socket_,results.begin(),results.end(),std::bind(&session::on_connect,shared_from_this(), std::placeholders::_1));
}
void
on_connect(boost::system::error_code ec)
{
if(ec) {
return fail(ec, "connect");
}
// Send the HTTP request to the remote host
http::async_write(socket_, req_,std::bind(&session::on_write, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
void
on_write( boost::system::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(ec) {
return fail(ec, "write");
}
// Receive the HTTP response
http::async_read(socket_, buffer_, res_, std::bind( &session::on_read, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
void
on_read(boost::system::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(ec) {
return fail(ec, "read");
}
// Write the message to standard out
std::cout << res_ << std::endl;
// Gracefully close the socket
socket_.shutdown(tcp::socket::shutdown_both, ec);
// not_connected happens sometimes so don't bother reporting it.
if(ec && ec != boost::system::errc::not_connected) {
return fail(ec, "shutdown");
}
// If we get here then the connection is closed gracefully
}
};
int main(int argc, char** argv)
{
// Check command line arguments.
if(argc != 4 && argc != 5)
{
std::cerr <<
"Usage: http-client-async <host> <port> <target> [<HTTP version: 1.0 or 1.1(default)>]\n" <<
"Example:\n" <<
" http-client-async www.example.com 80 /\n" <<
" http-client-async www.example.com 80 / 1.0\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const target = argv[3];
int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11;
// The io_context is required for all I/O
boost::asio::io_context ioc;
// Launch the asynchronous operation
std::make_shared<session>(ioc)->run(host, port, target, version);
// Run the I/O service. The call will return when
// the get operation is complete.
ioc.run();
return EXIT_SUCCESS;
}
I have a python REST Server that runs waiting for requests from this client.
#!flask/bin/python
from flask import Flask, jsonify
app = Flask(__name__)
tasks = [
{
'id': 1,
'title': u'Buy groceries',
'description': u'Milk, Cheese, Pizza, Fruit, Tylenol',
'done': False
},
{
'id': 2,
'title': u'Learn Python',
'description': u'Need to find a good Python tutorial on the web',
'done': False
}
]
#app.route('/todo/api/v1.0/tasks', methods=['GET'])
def get_tasks():
return jsonify({'tasks': tasks})
if __name__ == '__main__':
app.run(host='0.0.0.0',debug=True)
I am able to run this server. The output is shown below.
* Serving Flask app 'RESTServer' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployme
nt.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 409-562-797
* Running on all addresses.
WARNING: This is a development server. Do not use it in a production deployme
nt.
* Running on http://192.168.1.104:5000/ (Press CTRL+C to quit)
However when I run the REST Client, as below
rest_client.exe http://192.168.1.104 5000 /todo/api/v1.0/tasks
I get the following error
resolve: Host not found (authoritative)
The program expects separate arguments. It even gives you usage instructions:
Usage: http-client-async <host> <port> <target> [<HTTP version: 1.0 or 1.1(default)>]
Example:
http-client-async www.example.com 80 /
http-client-async www.example.com 80 / 1.0
So it looks like you AT LEAST want to remove http://

How do I catch the COMException thrown by disconnected StreamSocket in C++/CX?

I'm writing an application that should retranslate some data received from Bluetooth LE device to all TCP clients connected to it. The platform is Windows 10. For now it is console application with C++/CX enable as Bluetooth LE API is only available from WinRT. The BLE part is ready but I cannot make proper TCP server using WinRT.
I'm creating TCP socket server using StreamSocketListener and store all StreamSocket objects in std::vector. In a loop iterate the vector and send the data to all connected clients. Everything is working fine at this point. But should one client disconnect and the server crashes as it tries to send a data to disconnected socket and throws COMException and I cannot catch it.
Visual Studio 2015 crash message: Exception thrown at 0x752ADAE8 in SensoCLI.exe: Microsoft C++ exception: Platform::COMException ^ at memory location 0x02EFE1C0. Call stack points to auto sent = writeTask.get();
Below is the minimal listing of my app that corresponds to the problem:
#include "pch.h"
#using <platform.winmd>
#using <Windows.winmd>
using namespace std;
using namespace Windows::Foundation;
using namespace Platform;
using namespace concurrency;
namespace WFC = Windows::Foundation::Collections;
namespace WNS = Windows::Networking::Sockets;
namespace WSS = Windows::Storage::Streams;
bool shouldStop = false;
// TCP socket server
WNS::StreamSocketListener ^tcpListener;
vector<WNS::StreamSocket ^> *tcpClients;
void OnSocketConnectionReceived(WNS::StreamSocketListener ^aListener, WNS::StreamSocketListenerConnectionReceivedEventArgs ^args);
int main(Array<String ^> ^args)
{
tcpClients = new vector<WNS::StreamSocket ^>();
tcpListener = ref new WNS::StreamSocketListener();
tcpListener->ConnectionReceived += ref new TypedEventHandler<WNS::StreamSocketListener ^, WNS::StreamSocketListenerConnectionReceivedEventArgs ^>(&OnSocketConnectionReceived);
auto listenTask = create_task(tcpListener->BindServiceNameAsync("53450"));
listenTask.wait();
while (!shouldStop)
{
if (tcpClients->size() > 0)
{
auto netDataStream = ref new WSS::InMemoryRandomAccessStream();
auto writer = ref new WSS::DataWriter(netDataStream);
auto reader = ref new WSS::DataReader(netDataStream->GetInputStreamAt(0));
String^ stringToSend("Hello");
writer->WriteUInt32(writer->MeasureString(stringToSend));
writer->WriteString(stringToSend);
auto aTask = create_task(writer->StoreAsync());
unsigned int bytesStored = aTask.get();
aTask = create_task(reader->LoadAsync(bytesStored));
aTask.wait();
auto netData = reader->ReadBuffer(bytesStored);
for (auto iter = tcpClients->begin(); iter != tcpClients->end(); ++iter)
{
create_task((*iter)->OutputStream->WriteAsync(netData)).then([](task<unsigned int> writeTask) {
try
{
// Try getting an exception.
auto sent = writeTask.get();
wcout << L"Sent: " << sent << endl;
}
catch (Exception^ exception)
{
wcout << L"Send failed with error: " << exception->Message->Data() << " " << endl;
}
});
}
Sleep(1000);
}
}
}
void OnSocketConnectionReceived(WNS::StreamSocketListener ^aListener, WNS::StreamSocketListenerConnectionReceivedEventArgs ^args)
{
auto sock = args->Socket;
tcpClients->push_back(sock);
}
How do I properly catch the exception and handle disconnected StreamSocket?

boost asio SO_REUSEPORT

I'm working on a multi-processes socket server with the boost library.
Each process run a io_service.
I want to this processes all accept on the same port.
I know SO_REUSEPORT (after linux kernel 3.9) will help.
like this python script
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s.bind(('0.0.0.0', 9091))
s.listen(1)
while True:
conn, addr = s.accept()
print "new connection"
while True:
data = conn.recv(100)
print "got data", data
if not data or data == 'exit':
break
conn.close()
But I don't know how to use this option in boost asio io_service ?
For people reading this in 2019: Asio now includes a workaround in boost/asio/detail/impl/socket_ops.ipp:
#if defined(__MACH__) && defined(__APPLE__) \
|| defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
// To implement portable behaviour for SO_REUSEADDR with UDP sockets we
// need to also set SO_REUSEPORT on BSD-based platforms.
if ((state & datagram_oriented)
&& level == SOL_SOCKET && optname == SO_REUSEADDR)
{
call_setsockopt(&msghdr::msg_namelen, s,
SOL_SOCKET, SO_REUSEPORT, optval, optlen);
}
#endif
So, socket_->set_option(udp::socket::reuse_address(true)); will set the SO_REUSEPORT option automatically if needed.
Following on from how boost/asio/socket_base.hpp defines reuse_address, I did it like this:
typedef boost::asio::detail::socket_option::boolean<SOL_SOCKET, SO_REUSEPORT> reuse_port;
socket_.set_option(reuse_port(true));
Answer by my own.
#include <iostream>
#include <string>
#include <array>
#include <boost/asio.hpp>
#include <arpa/inet.h>
using boost::asio::ip::tcp;
int main()
{
boost::asio::io_service io;
tcp::acceptor acceptor(io);
acceptor.open(tcp::v4());
int one = 1;
setsockopt(acceptor.native_handle(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &one, sizeof(one));
acceptor.bind(tcp::endpoint(tcp::v4(), 9091));
acceptor.listen();
std::cout << "start" << std::endl;
for(;;)
{
tcp::socket socket(io);
acceptor.accept(socket);
std::cout << "new connections" << std::endl;
for(;;)
{
std::array<char, 4> buf;
boost::system::error_code error;
boost::asio::read(socket, boost::asio::buffer(buf), error);
if(error)
{
std::cout << "read error: " << error << std::endl;
break;
}
std::cout << "read: " << std::string(buf.data()) << std::endl;
}
}
}
The HTTP server example shows one way: http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/example/cpp11/http/server/server.cpp
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(io_service_);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve({address, port});
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
acceptor_.listen();
IIRC there's also an acceptor constructor that takes a boolean argument to set the reuse flag.

WINSOCK error 10022 on listen when include thread

I am implementing a simple multithreaded FTP client server where I am facing a problem which is strange for me( as I am no master in C++ and threads).
The code I have written works normally until I #include <thread>.
Once I include the thread class the program fails on listen and gives a 10022 error. (I haven't done anything related to threads yet, only import).
Below is the code. The method is called from main().
#include <winsock2.h>
#include <ws2tcpip.h>
#include <process.h>
#include <winsock.h>
#include <iostream>
#include <windows.h>
#include <fstream>
#include <string>
#include <stdio.h>
#include <time.h>
#include <thread>
using namespace std;
void initializeSockets()
{
try{
logEvents("SERVER", "Initializing the server");
WSADATA wsadata;
if (WSAStartup(0x0202,&wsadata)!=0){
cout<<"Error in starting WSAStartup()\n";
logEvents("SERVER", "Error in starting WSAStartup()");
}else{
logEvents("SERVER", "WSAStartup was suuccessful");
}
gethostname(localhost,20);
cout<<"hostname: "<<localhost<< endl;
if((hp=gethostbyname(localhost)) == NULL) {
cout << "gethostbyname() cannot get local host info?"
<< WSAGetLastError() << endl;
logEvents("SERVER", "Cannot get local host info. Exiting....");
exit(1);
}
//Create the server socket
if((serverSocket = socket(AF_INET,SOCK_STREAM,0))==INVALID_SOCKET)
throw "can't initialize socket";
//Fill-in Server Port and Address info.
serverSocketAddr.sin_family = AF_INET;
serverSocketAddr.sin_port = htons(port);
serverSocketAddr.sin_addr.s_addr = htonl(INADDR_ANY);
//Bind the server port
if (bind(serverSocket,(LPSOCKADDR)&serverSocketAddr,sizeof(serverSocketAddr)) == SOCKET_ERROR)
throw "can't bind the socket";
cout << "Bind was successful" << endl;
logEvents("SERVER", "Socket bound successfully.");
if(listen(serverSocket,10) == SOCKET_ERROR)
throw "couldn't set up listen on socket";
else
cout << "Listen was successful" << endl;
logEvents("SERVER", "Socket now listening...");
//Connection request accepted.
acceptUserConnections();
}
catch(char* desc)
{
cerr<<str<<WSAGetLastError()<<endl;
logEvents("SERVER", desc);
}
logEvents("SERVER", "Closing client socket...");
closesocket(clientSocket);
logEvents("SERVER", "Closed. \n Closing server socket...");
closesocket(serverSocket);
logEvents("SERVER", "Closed. Performing cleanup...");
WSACleanup();
}
int main(void){
initializeSockets();
return 0;
}
I have read the thread Winsock Error 10022 on Listen but I don't think that this has solution to my problem.
Error 10022 is WSAEINVAL. The documentation for listen() clearly states:
WSAEINVAL
The socket has not been bound with bind.
The reason your code stops working when you add #include <thread> is because your call to bind() is being altered to no longer call WinSock's bind() function, but to instead call the STL's std::bind() function. Your using namespace std statement is masking that issue (this is one of many reasons why using namespace std is such a bad practice - teach yourself to stop using that!).
So you need to either:
get rid of using namespace std.
qualify bind() with the global namespace so it calls WinSock's function:
if (::bind(...) == SOCKET_ERROR)

Why would connect() give intermittent EINVAL on port to FreeBSD?

I have in my C++ application a failure that arose upon porting to 32 bit FreeBSD 8.1 from 32 bit Linux. I have a TCP socket connection which fails to connect. In the call to connect(), I got an error result with errno == EINVAL which the man page for connect() does not cover.
What does this error mean, which argument is invalid? The message just says: "Invalid argument".
Here are some details of the connection:
family: AF_INET
len: 16
port: 2357
addr: 10.34.49.13
It doesn't always fail though. The FreeBSD version only fails after letting the machine sit idle for several hours. But after failing once, it works reliably until you let it sit idle again for a prolonged period.
Here is some of the code:
void setSocketOptions(const int skt);
void buildAddr(sockaddr_in &addr, const std::string &ip,
const ushort port);
void deepBind(const int skt, const sockaddr_in &addr);
void
test(const std::string &localHost, const std::string &remoteHost,
const ushort localPort, const ushort remotePort,
sockaddr_in &localTCPAddr, sockaddr_in &remoteTCPAddr)
{
const int skt = socket(AF_INET, SOCK_STREAM, 0);
if (0 > skt) {
clog << "Failed to create socket: (errno " << errno
<< ") " << strerror(errno) << endl;
throw;
}
setSocketOptions(skt);
// Build the localIp address and bind it to the feedback socket. Although
// it's not traditional for a client to bind the sending socket to a the
// local address, we do it to prevent connect() from using an ephemeral port
// which (our site's firewall may block). Also build the remoteIp address.
buildAddr(localTCPAddr, localHost, localPort);
deepBind(skt, localTCPAddr);
buildAddr(remoteTCPAddr, remoteHost, remotePort);
clog << "Info: Command connect family: "
<< (remoteTCPAddr.sin_family == AF_INET ? "AF_INET" : "<unknown>")
<< " len: " << int(remoteTCPAddr.sin_len)
<< " port: " << ntohs(remoteTCPAddr.sin_port)
<< " addr: " << inet_ntoa(remoteTCPAddr.sin_addr) << endl;
if (0 > ::connect(skt, (sockaddr*)& remoteTCPAddr, sizeof(sockaddr_in)))) {
switch (errno) {
case EINVAL: {
int value = -1;
socklen_t len = sizeof(value);
getsockopt(skt, SOL_SOCKET, SO_ERROR, &value, &len);
cerr << "Error: Command connect failed on local port "
<< getLocFbPort()
<< " and remote port " << remotePort
<< " to remote host '" << remoteHost
<< "' family: "
<< (remoteTCPAddr.sin_family == AF_INET ? "AF_INET" : "<unknown>")
<< " len: " << int(remoteTCPAddr.sin_len)
<< " port: " << ntohs(remoteTCPAddr.sin_port)
<< " addr: " << inet_ntoa(remoteTCPAddr.sin_addr)
<< ": Invalid argument." << endl;
cerr << "\tgetsockopt => "
<< ((value != 0) ? strerror(value): "success") << endl;
throw;
}
default: {
cerr << "Error: Command connect failed on local port "
<< localPort << " and remote port " << remotePort
<< ": (errno " << errno << ") " << strerror(errno) << endl;
throw;
}
}
}
}
void
setSocketOptions(int skt)
{
// See page 192 of UNIX Network Programming: The Sockets Networking API
// Volume 1, Third Edition by W. Richard Stevens et. al. for info on using
// ::setsockopt().
// According to "Linux Socket Programming by Example" p. 319, we must call
// setsockopt w/ SO_REUSEADDR option BEFORE calling bind.
int so_reuseaddr = 1; // Enabled.
int reuseAddrResult
= ::setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, &so_reuseaddr,
sizeof(so_reuseaddr));
if (reuseAddrResult != 0) {
cerr << "Failed to set reuse addr on socket.";
throw;
}
// For every two hours of inactivity, a keepalive occurs.
int so_keepalive = 1; // Enabled. See page 200 for info on SO_KEEPALIVE.
int keepAliveResult =
::setsockopt(skt, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive,
sizeof(so_keepalive));
if (keepAliveResult != 0) {
cerr << "Failed to set keep alive on socket.";
throw;
}
struct linger so_linger;
so_linger.l_onoff = 1; // Turn linger option on.
so_linger.l_linger = 5; // Linger time in seconds. (See page 202)
int lingerResult
= ::setsockopt(skt, SOL_SOCKET, SO_LINGER, &so_linger,
sizeof(so_linger));
if (lingerResult != 0) {
cerr << "Failed to set linger on socket.";
throw;
}
// Disable the Nagel algorithm on the command channel. SOL_TCP is not
// defined on FreeBSD
#ifndef SOL_TCP
#define SOL_TCP (::getprotobyname("TCP")->p_proto)
#endif
unsigned int tcpNoDelay = 1;
int noDelayResult
= ::setsockopt(skt, SOL_TCP, TCP_NODELAY, &tcpNoDelay,
sizeof(tcpNoDelay));
if (noDelayResult != 0) {
cerr << "Failed to set tcp no delay on socket.";
throw;
}
}
void
buildAddr(sockaddr_in &addr, const std::string &ip, const ushort port)
{
memset(&addr, 0, sizeof(sockaddr_in)); // Clear all fields.
addr.sin_len = sizeof(sockaddr_in);
addr.sin_family = AF_INET; // Set the address family
addr.sin_port = htons(port); // Set the port.
if (0 == inet_aton(ip.c_str(), &addr.sin_addr)) {
cerr << "BuildAddr IP.";
throw;
}
};
void
deepBind(const int skt, const sockaddr_in &addr)
{
// Bind the requested port.
if (0 <= ::bind(skt, (sockaddr *)&addr, sizeof(addr))) {
return;
}
// If the port is already in use, wait up to 100 seconds.
int count = 0;
ushort port = ntohs(addr.sin_port);
while ((errno == EADDRINUSE) && (count < 10)) {
clog << "Waiting for port " << port << " to become available..."
<< endl;
::sleep(10);
++count;
if (0 <= ::bind(skt, (sockaddr*)&addr, sizeof(addr))) {
return;
}
}
cerr << "Error: failed to bind port.";
throw;
}
Here is example output when EINVAL (it doesn't always fail here, sometimes it succeeds and fails on the first packet sent over the socket getting scrambled):
Info: Command connect family: AF_INET len: 16 port: 2357 addr: 10.34.49.13
Error: Command connect failed on local port 2355 and remote port 2357 to remote host '10.34.49.13' family: AF_INET len: 16 port: 2357 addr: 10.34.49.13: Invalid argument.
getsockopt => success
I figured out what the issue was, I was first getting a ECONNREFUSED, which on Linux I can just retry the connect() after a short pause and all is well, but on FreeBSD, the following retry of connect() fails with EINVAL.
The solution is when ECONNREFUSED to back up further and instead start retrying back to beginning of test() definition above. With this change, the code now works properly.
It's interesting that the FreeBSD connect() manpage doesn't list EINVAL. A different BSD manpage states:
[EINVAL] An invalid argument was detected (e.g., address_len is
not valid for the address family, the specified
address family is invalid).
Based on the disparate documentation from the different BSD flavours floating around, I would venture that there may be undocumented return code possibilities in FreeBSD, see here for example.
My advice is to print out your address length and the sizeof and contents of your socket address structure before calling connect - this will hopefully assist you to find out what's wrong.
Beyond that, it's probably best if you show us the code you use to set up the connection. This includes the type used for the socket address (struct sockaddr, struct sockaddr_in, etc), the code which initialises it, and the actual call to connect. That'll make it a lot easier to assist.
What’s the local address? You’re silently ignoring errors from bind(2), which seems like not only bad form, but could be causing this issue to begin with!