Boost asio异步操作坏文件描述符

Posted

技术标签:

【中文标题】Boost asio异步操作坏文件描述符【英文标题】:Boost asio async operation bad file descriptor 【发布时间】:2016-05-04 22:57:29 【问题描述】:

我正在为 IRC 机器人使用 boost asio,而我的一个异步操作导致文件描述符错误。我试图将套接字放在 shared_ptr 中,但仍然出现“错误文件描述符”错误。我不知道它有什么问题。

这是文件,我省略了cpp文件中的一些功能。但是我想阅读完整的文件,在我的 Github 上是 here。

错误发生在_read函数中。

谢谢!

irc.hpp

#ifndef H_IRC
#define H_IRC

#include <vector>
#include <boost/asio.hpp>
#include <boost/tokenizer.hpp>
#include <boost/shared_ptr.hpp>

class Irc 

    public:
        Irc(const std::string &server, const std::string &port, const std::function<void()> onConnect);

        void connect();
        void close();

        void user(const std::string &username);
        void user(const std::string &username, const std::string &hostname, const std::string &server, const std::string &realname);
        void nick(std::string &nickname);
        void join(const std::string &chan);
        void part(const std::string &chan);
        void privmsg(const std::string &to, const std::string &msg);
        void command(const std::string &cmd, const std::string &msg);
        void command(const std::string &cmd, const std::string &to, const std::string &msg);

        void run();

    private:
        void _read(const boost::system::error_code &error);
        void _send(std::string &message);
        void _readHandler(const boost::tokenizer<boost::char_separator<char> > &tokenizer);
        void _connectHandler(const boost::system::error_code &error);

        void _pong(const std::string &ping);

        std::string _server;
        std::string _port;
        std::string _chan;
        std::vector<std::function<void (const boost::tokenizer<boost::char_separator<char> >&)>> _readHandlers;
        std::function<void()> _onConnect;
        boost::asio::streambuf _buffer;
        boost::asio::io_service _ios;
        boost::shared_ptr<boost::asio::ip::tcp::socket> _socket;
;

#endif

irc.cpp

#include "irc.hpp"
#include <iostream>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>

Irc::Irc(const std::string &server, const std::string &port, const std::function<void()> onConnect)
    : _server(server), _port(port), _onConnect(onConnect), 
      _socket(boost::make_shared<boost::asio::ip::tcp::socket>(boost::ref(_ios)))

    // Ping back handler
    _readHandlers.push_back([this](const boost::tokenizer<boost::char_separator<char> > &tokenizer) 
        std::vector<std::string> tokens(begin(tokenizer), end(tokenizer)); 

        if(tokens[0].compare("PING") == 0)
            _pong(tokens[1]);   
    );



void Irc::connect()

    boost::asio::ip::tcp::resolver resolver(_ios);
    boost::asio::ip::tcp::resolver::query query(_server, _port);
    boost::asio::ip::tcp::resolver::iterator it = resolver.resolve(query);
    boost::asio::ip::tcp::resolver::iterator end;
    boost::system::error_code error = boost::asio::error::host_not_found;

    while(it != end)
    
        if(!error)
            break;

        std::cout << "Connecting to " << _server << " " << _port << std::endl;

        boost::asio::async_connect(*_socket, it,
            boost::bind(&Irc::_connectHandler, this, error)
        );

        it++;

        if(error)
            std::cout << "Error : " << error.message() << std::endl;

    

    if(error)
        std::cout << "Error connectinf to " << _server << " " << error.message() << std::endl;
    else
        std::cout << "Connection success" << std::endl;



void Irc::close()

    _socket->close();
    _ios.stop();


void Irc::run()

    boost::asio::async_read_until(*_socket, _buffer, "\r\n",
        boost::bind(&Irc::_read, this,
            boost::asio::placeholders::error
        )
    );

    _ios.run();


/*
 * Private
 */

void Irc::_read(const boost::system::error_code &error)

    if(error)
    
        std::cerr << "Error in read : " << error.message() << std::endl;
    
    else
    
        std::string data(buffers_begin(_buffer.data()), buffers_begin(_buffer.data()) + _buffer.size());
        std::cout << data << std::endl;     

        boost::char_separator<char> sep("!@:; ");
        boost::tokenizer<boost::char_separator<char> > tokenizer(data, sep);

        _readHandler(tokenizer);
        boost::asio::async_read_until(*_socket, _buffer, "\r\n",
            boost::bind(&Irc::_read, this,
                boost::asio::placeholders::error
            )
        );

    


inline void Irc::_send(std::string &message)

    boost::asio::write(*_socket, boost::asio::buffer(message + "\r\n"));


void Irc::_readHandler(const boost::tokenizer<boost::char_separator<char> > &tokenizer)

    for(auto it : _readHandlers)
        it(tokenizer);


void Irc::_connectHandler(const boost::system::error_code &error)

    if(!error)
    
        _onConnect();
    

【问题讨论】:

看东西livecoding.tv/sehe @sehe : 很好,我会把它放在我的书签里,不幸的是我的笔记本电脑上没有 Flashplayer。 应该已经支持 html5 (lemme check)。 roadmap.livecoding.tv 表示 2016 年第 4 周 i.imgur.com/xpk55U1.png 可能是我没有打开的选项? 【参考方案1】: connect 永远不会被调用。

这会导致“错误的文件句柄”错误

补充说明

突然,_send 使用 同步 asio::write。为什么? 也应该在那里添加错误处理(捕获或传递error_code&amp; 参数)。

只有一个套接字永远不会被重新初始化或分配。将其嵌入到共享指针中不会改变任何东西¹。

然而这很奇怪:

    std::cout << "Connecting to " << _server << " " << _port << std::endl;

    boost::asio::async_connect(*_socket, it,
        boost::bind(&Irc::_connectHandler, this, error)
    );

这可能会同时在同一个套接字上执行许多异步连接操作。这是一场数据竞赛,因此 Undefined Behaviour,请参阅:documentation。

因此您需要修复它以使用多个套接字或顺序。 原来,这很简单:你已经在使用async_connect的免费功能版本了:

http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/async_connect/overload1.html

此函数尝试将套接字连接到一系列端点中的一个。 它通过重复调用套接字的async_connect 成员函数来实现这一点,对序列中的每个端点调用一次,直到成功建立连接。

所以解决方法是只调用一次。

您的绑定不使用占位符,而是使用硬编码错误!

boost::system::error_code error = boost::asio::error::host_not_found;
boost::asio::async_connect(_socket, it, boost::bind(&Irc::_connectHandler, this, error));

需要更像

boost::asio::async_connect(_socket, it, boost::bind(&Irc::_connectHandler, this, boost::asio::placeholders::error()));

处理传入流量后,需要消耗缓冲区内容,否则会无限重复:

_readHandler(tokenizer);

_buffer.consume(_buffer.size());

拉取请求

https://github.com/Bl4ckb0ne/irc-boost/pull/1

补充:

869c225 Use shared_ptr
9042c6d Add function level trace
50dee1b Revert shared_ptr and rename _onConnect(ed)
20475b9 Fixing the async_connect debacle
c6d8a2e Fixed channel handling and consistency join/part
6fd9242 Initiate `connect()` instead of read from `run()`
06a6c06 Do **not** assume contiguous buffer storage (UB)
090fe8c Consume handled input
68e5e8a Comment

以上都变了,我已经成功连接到一个IRC频道并接收消息了。


¹(尤其是除非您确保某些东西挂在 shared_ptr 的实例上)

【讨论】:

最后,我在 PR 中的审查结果:github.com/Bl4ckb0ne/irc-boost/pull/1 - 您可以在此处查看我对这些更改进行编码的录制直播:livecoding.tv/video/reviewing-a-toy-irc-bot-in-boost-asio 嗨,我只是想知道如何运行程序。我可以通过从 github 下载源代码来构建程序,正如@sehe 提到的那样。我需要知道步骤... . 创建的可执行文件运行良好...

以上是关于Boost asio异步操作坏文件描述符的主要内容,如果未能解决你的问题,请参考以下文章

异步读取 inotify 描述符失败

提升 asio 绑定:错误的文件描述符

boost::asio 扩展 TCP 套接字

boost.asio 和文件 i/o 有啥关系?

错误系统:9:错误的文件描述符(BOOST::FileSystem)

使用 Boost Asio 在 TCP 套接字上执行异步写入操作