c++ - Boost Asio 不完整写入套接字

标签 c++ sockets boost boost-asio

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

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

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 - 异步写入显然没有改变。

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

损坏的列表输出示例(在本地主机上,使用 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;

最佳答案

至少,由于 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++ - Boost Asio 不完整写入套接字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29486303/

相关文章:

c++ - MSVC中从_Ty到int警告的转换累积

xcode - 在 MacOS Sierra 上找不到“openssl/conf.h”文件错误

C++ 错误:[ 二进制表达式的操作数无效 ('std::map<int, std::function<void ()>, std::less<int>...]

c# - 我无法连接以测试使用 TcpListener 启动的本地服务器

python - 哪个开销更大 : Creating a new socket each time or maintaining a single socket for data transfer

c# - 通过套接字发送对象

c++ - 使用shared_ptr c++ boost tcp套接字

c++ - 在 Mac OS X 10.9 中使用 GLFW 创建 OpenGL 3.3 上下文

c++ - 使用指针运算调用结构内部的结构数组

c++ - 如何使用 MFC 增加/减少位图的对比度?