【问题标题】:Boost Asio incomplete write to socketBoost Asio 对套接字的写入不完整
【发布时间】:2015-06-11 17:42:33
【问题描述】:

我正在使用 Boost Asio 编写一个非常简单的玩具键值存储,并且发生了一些非常奇怪的事情。

基于字符串的协议是这样的:

S <key> <value> // to set a key
G <key>         // to get the key value
L               // to list all key-value pairs

写入是同步的,使用

boost::asio::write(socket_,boost::asio::buffer(resp, len));

其中 socket_ 是 boost::asio::ip::tcp::socket - 显然,异步写入的故事不会改变。

问题在于有时它不会将它应该写入的所有字节都写入套接字,或者输出以某种方式被破坏......

重整列表输出示例(在 localhost 上,使用 nc、echo 和 hexdump):

> echo S a 12 | nc localhost 5000       
A
> echo S b 23 | nc localhost 5000
A
> echo L | nc localhost 5000 | hexdump -C
00000000  61 3a 20 31 32 3b 20 62  60 00 00 00 00 00        |a: 12; b`.....|
0000000e
> echo L | nc localhost 5000 | hexdump -C
00000000  61 3a 20 31 32 3b 20 62  3a 20 32 33 3b 20        |a: 12; b: 23; |
0000000e

我正在使用 Ubuntu 14.10 存储库中的 Boost 1.55。 遵循服务于客户端的函数的代码。

提前感谢您的任何提示!

void ClientSession::handle_read(const boost::system::error_code& error, size_t bytes_transferred) {
if (!error) {

    std::string cmd(data_, bytes_transferred);
    cmd = trim_str(cmd);

    const char* resp = NULL;
    int len = 0;

    switch(cmd.at(0)) {
    case SET: {
        std::size_t k_pos = cmd.find(" ") + 1;
        std::size_t v_pos = cmd.find(" ", k_pos+1) + 1;

        std::string key = trim_str(cmd.substr(k_pos, v_pos-3));
        std::string value = trim_str(cmd.substr(v_pos, cmd.length()-1));
        cout << "SET key " << key << ", value " << value << "*" <<endl;

        kvs->db[key] = std::atoi(value.c_str());

        resp = "A";
        len = 1;
        break;
    }
    case GET: {
        std::size_t k_pos = cmd.find(" ") + 1;
        std::string key = trim_str(cmd.substr(k_pos, cmd.length()));
        cout << "GET key " << key << "*" << endl;

        int value = kvs->db[key];
        char str[5];
        sprintf(str, "%d", value);
        resp = (const char*) str;
        len = strlen(resp);
        break;
    }
    case LIST: {
        ostringstream os;
        for (std::map<string, int>::iterator iter = kvs->db.begin();
              iter != kvs->db.end(); ++iter )
              os << iter->first << ": " << iter->second << "; ";
        cout << "list: " << os.str().c_str() << endl;

        resp = os.str().c_str();
        len = strlen(resp);
        break;
    }
    case DEL: {
        std::size_t k_pos = cmd.find(" ") + 1;
        std::string key = trim_str(cmd.substr(k_pos, cmd.length()));

        kvs->db.erase(key);
        resp = "A";
        len = 1;
        break;
    }
    default: {
        resp = "NACK.";
        len = 5;
    }
    }

    cout << "resp: " << resp << "*" << endl;
    cout << "len: " << len << "*" << endl;
    std::size_t written = boost::asio::write(socket_,
            boost::asio::buffer(resp, len));
    cout << "written: " << written << endl;

    boost::system::error_code ignored_ec;
    socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
    socket_.close();

} else
    delete this;

【问题讨论】:

    标签: c++ sockets boost boost-asio


    【解决方案1】:

    至少由于case GET 中的resp 指针悬空而导致您的行为未定义:

    {
        // ...
        char str[5];
        resp = (const char*) str; // WHOOOOOOOOOOOPS
        len = strlen(resp);
    }
    

    case LIST下的一模一样:

    {
        std::ostringstream os;
        // ....
        resp = os.str().c_str(); // WHOOOOOOOOOOOPS
    }
    

    当你有 Undefined Behaviour 时,所有关于程序的推理都不再有用。

    修复这些问题(可能还有更多我没有发现的问题),然后重新测试。在 valgrind 下运行。使用静态分析工具。

    更新:修复单线程版本:https://gist.github.com/sehe/69379e17350fb718892f#comment-1428235

    测试运行输出:

    $ for a in S{a..d}\ $RANDOM Gnonexisting L; do echo "$a -> $(netcat 127.0.0.1 5000 <<< "$a")"; done | nl
         1  Sa 15936 -> A
         2  Sb 3671 -> A
         3  Sc 10550 -> A
         4  Sd 7741 -> A
         5  Gnonexisting -> 0
         6  L -> 1: 1; 2: 2; a: 15936; asdasd: 0; b: 3671; c: 10550; d: 7741; nonexisting: 0; 
    

    handle_read 的代码如下:

    void ClientSession::handle_read(const boost::system::error_code& error, size_t bytes_transferred) {
        if (!error) {
            std::istringstream request(std::string(data_, bytes_transferred));
            boost::asio::streambuf resp;
            std::ostream os(&resp);
    
            char cmd_char = 0;
            std::string key;
            int value;
            if (request >> cmd_char) switch(cmd_char) {
                case SET:                          
                    if (request >> key >> value)
                        kvs->db[key] = value;
    
                    os << "A";
                    break;
                case GET:
                    if (request >> key)
                        os << kvs->db[key];
                    break;
    
                case LIST:
                    for (auto const& e : kvs->db)
                        os << e.first << ": " << e.second << "; ";
                    break;
    
                case DEL: 
                    if (request >> key)
                        kvs->db.erase(key);
    
                    os << "A";
                    break;
                default: 
                    os << "NACK.";
            }
    
            cout << "resp: " << &resp << "*" << endl;
            cout << "len: " << resp.size() << "*" << endl;
            std::size_t written = boost::asio::write(socket_, resp);
            cout << "written: " << written << endl;
    
            boost::system::error_code ignored_ec;
            socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
            socket_.close();
        } else
            delete this;
    }
    

    【讨论】:

    • 很公平,谢谢! - 正如您可能已经猜到的那样,我(还)不太熟悉所有这些 C 微妙的怪癖:/
    • 我想说:永远不要习惯 C 的怪癖!使用 C++ 代替:paste.ubuntu.com/10759474(同样,enable_shared_from_this 代替 delete this 代码异味;使用 Boost array_source 减少复制,使用 boost string_ref 消除复制,但这是优化)
    • 现在通过编译器:Live On ColiruCAVEATKV_IOTHREADS_NUM 保留为 1,直到实现线程安全
    猜你喜欢
    • 1970-01-01
    • 2012-07-19
    • 1970-01-01
    • 2011-10-15
    • 2011-12-05
    • 2015-02-22
    • 2015-06-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多