Reputation: 113
I am using boost asio for a client server application and ran into this problem, with this not so informative error message(at least to me ;)), I am sending structs as messages to and fro, the sending works perfectly well from the client side, but almost similar attempt from the server's side causes this problem(rather error) : Send failed: Bad file descriptor
here's a snippet of the sending part ( please ask for any other details required in the comments):
void read_from_ts(const char* buf, int len) { // this is the read callback function
if (len <= 0) {
std::cerr << "Error: Connection closed by peer. " << __FILE__ << ":" << __LINE__ << std::endl;
tcp_client_.close(tcp_connection_);
tcp_connection_ = nullptr;
ios_.stop(); // exit
return;
}
const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);
int tempId = obj->MessageHeaderIn.TemplateID;
Responses r;
switch(tempId)
{
case 10018: //login
const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);
//std::cout<<"Login request received"<<"\n";
boost::asio::ip::tcp::socket sock_(ios_);
r.login_ack(sock_);
/*will add more*/
}
std::cout << "RX: " << len << " bytes\n";
}
class Responses
{
public:
int login_ack(boost::asio::ip::tcp::socket& socket)
{
//std::cout<<"here"<<"\n";
UserLoginResponse info;
MessageHeaderOutComp mh;
ResponseHeaderComp rh;
rh.MsgSeqNum = 0; //no use for now
rh.RequestTime = 0; //not used at all
mh.BodyLen = 53; //no use
mh.TemplateID = 10019; // IMP
info.MessageHeaderOut = mh;
info.LastLoginTime = 0;
info.DaysLeftForPasswdExpiry = 10; //not used
info.GraceLoginsLeft = 10; //not used
rh.SendingTime = 0;
info.ResponseHeader = rh;
//Pad6 not used
async_write(socket, boost::asio::buffer(&info, sizeof(info)), on_send_completed);
}
static void on_send_completed(boost::system::error_code ec, size_t bytes_transferred) {
if (ec)
std::cout << "Send failed: " << ec.message() << "\n"; //**error shows up here**
else
std::cout << "Send succesful (" << bytes_transferred << " bytes)\n";
}
};
};
Upvotes: 2
Views: 1744
Reputation: 392979
UPDATE Just noticed a third, trivial explanation when reading your code, see added bullet
Typically when the file-descriptor is closed elsewhere.
If you're using Asio, this typically means
the socket
¹ object was destructed. This can be a beginner error when the code doesn't extend the lifetime of objects for the duration of asynchronous operations
the file-descriptor was passed to other code that closed it (e.g. using native_handle(https://www.boost.org/doc/libs/1_73_0/doc/html/boost_asio/reference/basic_stream_socket/native_handle.html)
and the other code closed it (e.g. because it assumes ownership and did error-handling).
UPDATE Or, it can mean your socket was never initialized to begin. In your code I read:
//std::cout<<"Login request received"<<"\n";
boost::asio::ip::tcp::socket sock_(ios_);
r.login_ack(sock_);
However, that just constructs a new socket, never connects or binds it and tries to do login_ack
on it. That won't work because login_ack
doesn't bind nor connect the socket and invokes async_write
on it.
Did you mean to use
tcp_connection_.sock_
or similar?
In general closing file-descriptors in third-party code is an error in multi-threaded code because it invites race conditions which will lead to arbitrary stream corruption (see e.g. How do you gracefully select() on sockets that could be closed on another thread?)
You can use
shutdown
instead in the majority of cases
Also, note that
info
doesn't have sufficient lifetime (it goes out of scope before the async_write
would be completedlogin_ack
never returns a valueThis is what I imagine surrounding code to look like, when removing the above problems.
In fact it could be significantly simpler due to the static nature of the response, but I didn't want to assume all responses would be that simple, so I went with shared-pointer lifetime:
#include <boost/asio.hpp>
#include <boost/core/ignore_unused.hpp>
#include <iostream>
using boost::asio::ip::tcp;
struct MyProgram {
boost::asio::io_context ios_;
struct UserLoginRequest {
struct MessageHeaderInComp {
int TemplateID = 10018;
} MessageHeaderIn;
};
struct Connection {
tcp::socket sock_;
template <typename Executor>
Connection(Executor ex) : sock_{ex} {}
};
std::unique_ptr<Connection> tcp_connection_ = std::make_unique<Connection>(ios_.get_executor());
struct {
void close(std::unique_ptr<Connection> const&);
} tcp_client_;
struct Responses {
static auto login_ack() {
struct UserLoginResponse {
struct MessageHeaderOutComp {
int BodyLen = 53; // no use
int TemplateID = 10019; // IMP
} MessageHeaderOut;
int LastLoginTime = 0;
int DaysLeftForPasswdExpiry = 10; // not used
int GraceLoginsLeft = 10; // not used
struct ResponseHeaderComp {
int MsgSeqNum = 0; // no use for now
int RequestTime = 0; // not used at all
int SendingTime = 0;
} ResponseHeader;
};
return std::make_shared<UserLoginRequest>();
}
};
void read_from_ts(const char* buf, int len) { // this is the read callback function
if (len <= 0) {
std::cerr << "Error: Connection closed by peer. " << __FILE__ << ":" << __LINE__ << std::endl;
tcp_client_.close(tcp_connection_);
tcp_connection_ = nullptr;
ios_.stop(); // exit
return;
}
const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);
int tempId = obj->MessageHeaderIn.TemplateID;
switch(tempId) {
case 10018: //login
const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);
//std::cout<<"Login request received"<<"\n";
boost::asio::ip::tcp::socket sock_(ios_);
auto response = Responses::login_ack();
async_write(tcp_connection_->sock_, boost::asio::buffer(response.get(), sizeof(*response)),
[response](boost::system::error_code ec, size_t bytes_transferred) {
if (ec)
std::cout << "Send failed: " << ec.message() << "\n"; //**error shows up here**
else
std::cout << "Send succesful (" << bytes_transferred << " bytes)\n";
});
/*will add more*/
boost::ignore_unused(obj);
}
std::cout << "RX: " << len << " bytes\n";
}
};
int main() {
MyProgram p;
}
¹ (or acceptor
/posix::strean_descriptor
)
Upvotes: 3