2015-04-07 95 views
2

我正在寫一個使用Boost Asio的死亡簡單玩具鍵值存儲,並且發生了一些非常奇怪的事情。Boost Asio不完整寫入插座

基於字符串的協議是這樣的:

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

寫入是同步的,使用

升壓:: ASIO ::寫(socket_,升壓:: ASIO ::緩衝液( resp,len));

其中socket_是一個boost :: asio :: ip :: tcp :: socket - 與asynchrounous寫故事不會改變,顯然。

的問題是,有時它不寫入套接字所有假設寫,或輸出以某種方式錯位字節...錯位列表輸出的

實施例(在本地主機上,使用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; 

回答

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; 
} 
+0

不夠公平,謝謝! - 正如你可能已經猜到的那樣,我還沒有對那些C微妙的怪癖非常熟悉:/ –

+0

我會說:永遠不要習慣C的怪癖!使用C++代替:http://paste.ubuntu.com/10759474/(另外,'enable_shared_from_this'而不是'delete this' code smell;使用Boost'array_source'來減少複製,boost'string_ref'消除它,但這是優化) – sehe

+0

現在通過編譯器:[Live Live Coliru](http://coliru.stacked-crooked.com/a/3b5a928e311f3162)。 ** CAVEAT **將'KV_IOTHREADS_NUM'保留爲1,直到您執行線程安全 – sehe