2015-06-22 51 views
3

我有第三方服務器,我正在爲它編寫一個dll接口,我的客戶端使用我的dll與服務器進行通信。根據數據包序列號調度響應數據包

該協議使用長tcp連接,所有流量都來自此tcp連接。有可能被髮送/同時接收多個數據包,喜歡的send_msgheart_beat在同一時間,所以我必須用ASYNC_WRITE/async_read以防止阻塞操作。每個數據包都有其序列號。例如,我發送序列號爲123的消息,然後等待服務器響應序列號爲123的數據包。

UPDATE:無法保證服務器按順序響應數據包。如果以AB的順序發送兩個數據包,則響應順序可以是response_B,response_A。序列號是識別數據包的唯一方法。

包的樣子:

4bytes size + 2 bytes crc check + 4 bytes SEQUENCE ID + .... 

的問題是,我的客戶誰用我的DLL喜歡使用功能的阻斷方式,他們不喜歡的回調。比如,他們喜歡

bool DLL_EXPORT send_msg(...) { 
    // send msg via long_connection, the seq_id==123 
    // recv msg via long_connection, just want the packet with seq_id==123 (How?) 
    return if_msg_sent_successfully; 
} 

我使用升壓ASIO,我不知道是否有任何工具類boost的,或者設計模式適用於這種情況下,這裏是解決方案,我可以想出用:

// use a global std::map<seq_id, packet_content> 
std::map<int, std::string> map_received; 

每次接收分組時,寫入seq_idpacket_body到map_received和send_msg函數看起來像

bool DLL_EXPORT send_msg(...) { 
    // async_send msg via long_connection, the seq_id==123 
    while(not_time_out) { 
     if(map_received.find(123) != map_received.end()) { 
      // get the packet and erase the 123 pair 
     } 
     Sleep(10); // prevent cpu cost 
    } 
    return if_msg_sent_successfully; 
} 

該解決方案很難看,因此必須有更好的設計。任何想法?

回答

2

你可以使用std::promisestd::future(或助推對口如果尚未對C++ 11)。

的想法是一個std::shared_ptr<std::promise<bool>>存儲與所述當前序列ID作爲映射中的鍵時的請求被髮送。 在阻塞發送功能中,您將等待相應的std::future<bool>被設置。 收到響應數據包後,將從地圖中提取相應的std::promise<bool>,並設置該值並且發送功能處於「解除阻塞」狀態。

以下示例鬆散地基於來自Boost asio documentation的聊天客戶端示例,並且不完整(例如,連接部分丟失,標題和正文讀數未拆分等)。由於它不完整,我沒有運行時測試,但它應該說明這個想法。

#include <thread> 
#include <map> 
#include <future> 
#include <iostream> 

#include <boost/asio.hpp> 

class Message 
{ 
public: 
    enum { header_length = 10 }; 
    enum { max_body_length = 512 }; 

    Message() 
    : body_length_(0) 
    { 
    } 

    const char* data() const 
    { 
    return data_; 
    } 

    char* data() 
    { 
    return data_; 
    } 

    std::size_t length() const 
    { 
    return header_length + body_length_; 
    } 

    const char* body() const 
    { 
    return data_ + header_length; 
    } 

    char* body() 
    { 
    return data_ + header_length; 
    } 

private: 
    char data_[header_length + max_body_length]; 
    std::size_t body_length_; 
}; 


class Client 
{  
public: 
    Client(boost::asio::io_service& io_service) 
     : io_service(io_service), 
      socket(io_service), 
      current_sequence_id(0) 
    {} 

    bool blocking_send(const std::string& msg) 
    { 
     auto future = async_send(msg); 
     // blocking wait 
     return future.get(); 
    } 

    void start_reading() 
    { 
     auto handler = [this](boost::system::error_code ec, std::size_t /*length*/) 
         { 
          if(!ec) 
          { 
           // parse response ... 
           int response_id = 0; 
           auto promise = map_received[response_id]; 
           promise->set_value(true); 
           map_received.erase(response_id); 
          } 
         }; 
     boost::asio::async_read(socket, 
           boost::asio::buffer(read_msg_.data(), Message::header_length), 
           handler); 
    } 

    void connect() 
    { 
     // ... 
     start_reading(); 
    } 

private: 

    std::future<bool> async_send(const std::string& msg) 
    { 
     auto promise = std::make_shared<std::promise<bool>>(); 
     auto handler = [=](boost::system::error_code ec, std::size_t /*length*/){std::cout << ec << std::endl;}; 
     boost::asio::async_write(socket, 
           boost::asio::buffer(msg), 
           handler); 
     // store promise in map 
     map_received[current_sequence_id] = promise; 
     current_sequence_id++; 
     return promise->get_future(); 
    } 

    boost::asio::io_service& io_service; 
    boost::asio::ip::tcp::socket socket; 
    std::map<int, std::shared_ptr<std::promise<bool>>> map_received; 
    int current_sequence_id; 
    Message read_msg_; 
}; 



int main() 
{ 
    boost::asio::io_service io_service; 
    Client client(io_service); 

    std::thread t([&io_service](){ io_service.run(); }); 

    client.connect(); 
    client.blocking_send("dummy1"); 
    client.blocking_send("dummy2"); 

    return 0; 
} 
+0

這正是我所需要的。使用std :: promise作爲返回值非常聰明。這真的有很大幫助,謝謝你們。 – aj3423

+0

@ aj3423另外一個注意事項:因爲這隻適用於使用多線程的情況,所以您必須確保訪問'map_received'是序列化的(例如使用互斥鎖),因爲對'std :: map'的寫入訪問不是線程安全的。 –

+0

是的我會用多線程的互斥體,感謝提示m.s. – aj3423