2015-12-10 109 views
0

我真的很樂意爲我的C++應用程序學習boost :: asio。 所以我從一個「簡單」的客戶端應用程序開始,它在我的筆記本電腦上編譯並運行。這個想法非常簡單。客戶端啓動與服務器的連接並每2秒詢問一次答案。我有可能觸發服務器的狀態(讓我們說一個警報信號)。作爲一種反應,當客戶端嘗試連接到服務器時,後者會通知它我必須準備好接收一些數據(具有一些雙精度和整數值的列表)。客戶端詢問列表並且服務器通過連接發送想要的列表。boost :: asio客戶端連接停止接收數據

問題:該列表未完全傳輸。列表中的一大塊將被髮送並被正確接收,但是我不再收到任何數據。

因爲我分裂我的申請類別,這裏的代碼:

client.h

#include <iostream> 
#include <string> 
#include <sstream> 
#include <fstream> 
#include <ostream> 
#include <chrono> 

#include <boost/asio.hpp> 
#include <boost/bind.hpp> 
#include <boost/array.hpp> 
#include <boost/regex.hpp> 
#include <boost/lexical_cast.hpp> 
#include <boost/asio/io_service.hpp> 
#include <boost/asio/write.hpp> 
#include <boost/asio/buffer.hpp> 
#include <boost/asio/ip/tcp.hpp> 

#ifndef __CLIENT_CLASS__ 
#define __CLIENT_CLASS__ 

//#define DEBUG 

class Client 
{ 
    boost::asio::io_service io_service_; 

    boost::asio::ip::tcp::resolver resolver_{ io_service_ }; 
    boost::asio::ip::tcp::socket socket_{ io_service_ }; 

    boost::asio::streambuf request_; 
    boost::asio::streambuf response_; 

    const std::string host_server_; 
    const std::string port_; 
    const uint uiId_;    
    const long double dLon_;    
    const long double dLat_; 
    const double dHeight_;  

    const std::string useragent_ = "HTMLGET 1.0"; 

    enum State { STANDBY = 0x01, 
       ALARM = 0x02, 
       WPL_REC = 0x04 }; 

    State state_ = STANDBY; 

    std::chrono::milliseconds server_response_delay_ = std::chrono::milliseconds(2000); // SERVER_RESPONSE_DELAY 
    uint uiError_ = 0; 

    bool bAlarmFlag_; 
    unsigned int uiRequestCounter_; 

    std::chrono::high_resolution_clock::time_point begin_time_; 

    public: 
     explicit Client(const std::string &address, const std::string &port, uint id, 
         long double start_longitude, long double start_latitude, 
         double start_height); 
     virtual ~Client(); 

     void run(void); 

     std::chrono::milliseconds getServerResponseDelay(void) const; 
     void setServerResponseDelay(std::chrono::milliseconds milliseconds); 


    private: 
     void buildQuery(void); 
     void handleResolve(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator it); 
     void handleConnect(const boost::system::error_code &ec); 
     void handleWriteRequest(const boost::system::error_code &ec); 
     void handleReadStatusLine(const boost::system::error_code &ec); 
     void handleMessage(const boost::system::error_code &error_msg); 
     void readContent(const boost::system::error_code &error_msg); 
     void build_wpl_query(void); 
     std::string build_ok_answer(void); 
     std::string build_alarm_answer(void); 
     std::string build_wpl_answer(void); 
}; 

#endif 

client.cpp。我在有問題的地方寫了一行評論。

#include "client.h" 

Client::Client(const std::string &address, const std::string &port, uint id, 
         long double start_longitude, long double start_latitude, 
         double start_height) : host_server_(address), port_(port), 
         uiId_(id), dLon_(start_longitude), dLat_(start_latitude), dHeight_(start_height) 
{ 

    std::cout << "Constructor of the class \"Client\" called" << std::endl; 

    bAlarmFlag_  = false; 
    uiRequestCounter_ = 1; 

    begin_time_ = std::chrono::high_resolution_clock::now(); 

} 

Client::~Client() 
{ 
    std::cout << "Destructor of the class \"Client\" called" << std::endl; 
} 

void Client::run(void) 
{ 
    if(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - begin_time_) > server_response_delay_) 
    { 
     begin_time_ = std::chrono::high_resolution_clock::now(); 

     boost::asio::ip::tcp::resolver::query local_query(host_server_, port_); 

     resolver_.async_resolve(local_query, boost::bind(&Client::handleResolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator)); 

     size_t service_status = io_service_.run(); 

     std::cout << "IO Service status: " << service_status << std::endl << std::endl; 

     io_service_.reset(); 
    } 
} 

void Client::handleResolve(const boost::system::error_code &error_msg, boost::asio::ip::tcp::resolver::iterator it) 
{ 
    if(!error_msg) { 
     boost::asio::async_connect(socket_, it, boost::bind(&Client::handleConnect, this, boost::asio::placeholders::error)); 

    } else { 
     std::cout << "Handle Resolv Error: " << error_msg.message() << std::endl; 
    } 
} 

void Client::handleConnect(const boost::system::error_code &error_msg) 
{ 
    if(!error_msg) { 

     std::cout << "-------------------------------------------------------------------------------"; 
     std::cout << std::endl << "Request Nr: " << (int)uiRequestCounter_ << std::endl; 
     std::cout << "Socket created" << std::endl; 

     if(!bAlarmFlag_) 
      buildQuery(); 
     if(bAlarmFlag_) { 
      build_wpl_query(); 
      bAlarmFlag_ = false; 
     } 

     boost::asio::async_write(socket_, request_, boost::bind(&Client::handleWriteRequest, this, boost::asio::placeholders::error)); 

     ++uiRequestCounter_; 

    } else { 
     std::cout << "Handle Connect Error: " << error_msg.message() << std::endl; 
    } 
} 

void Client::handleWriteRequest(const boost::system::error_code &error_msg) 
{ 
    std::cout << "Waiting answer from Server..." << std::endl; 

    if(!error_msg) { 

     boost::asio::async_read_until(socket_, response_, "\r\n", boost::bind(&Client::handleReadStatusLine, this, boost::asio::placeholders::error));  
    } else { 
     std::cout << "Handle Write Request Error: " << error_msg.message() << std::endl; 
    } 
} 

void Client::handleReadStatusLine(const boost::system::error_code &error_msg) 
{ 
    std::string header_string; 
    unsigned int status_code; 
    std::string server_status; 

    if(!error_msg) { 
     std::istream response_stream(&response_); 
     response_stream >> header_string; 
     response_stream >> status_code; 
     response_stream >> server_status; 


     std::cout << std::endl << header_string << " " << status_code << " " << server_status << std::endl; 

     if(header_string.substr(0, 8) != "HTTP/1.1") { 

      std::cout << "Not a valid message from Server" << std::endl; 
      return; 
     } 

     if(status_code != 200) { 
      std::cout << "Code 200 not available from Server" << std::endl; 
      return; 
     } 

     if(server_status != "OK") { 

      std::cout << "Not a OK message from Server" << std::endl; 
      return; 
     } 

     if((header_string.substr(0, 8) == "HTTP/1.1") && (status_code == 200) && (server_status == "OK")) { 

      std::cout << "Server OK, start retrieving data" << std::endl; 

      boost::asio::async_read_until(socket_, response_, "\r\n\r\n", boost::bind(&Client::handleMessage, this, boost::asio::placeholders::error)); 

     } else { 

      std::cout << "Not a valid packet from Server" << std::endl; 
     } 


    } else { 

     std::cout << "Handle Read Status Line Error: " << error_msg.message() << std::endl; 
    } 
} 

void Client::handleMessage(const boost::system::error_code &error_msg) 
{ 
    if(!error_msg) { 

     std::istream response_stream(&response_); 
     std::string header_string; 

     while(std::getline(response_stream, header_string) && header_string != "\r\n") { 

      // http://stackoverflow.com/a/30083146 
/*   if(static_cast<int>(header_string.find("Content-Length:")) != -1) { 

       std::string car = boost::regex_replace(header_string, boost::regex("[^0-9]*([0-9]+).*"), std::string("\\1")); 

       std::cout << "Content-Length: " << std::stoi(car) << std::endl; 

      }*/ 

      if(header_string.find(build_ok_answer()) != std::string::npos) { 

       bAlarmFlag_ = false; 
       std::cout << "I received a \033[1;32mALIVE-Signal\033[0m from the Server." << std::endl; 

      } else if(header_string.find(build_alarm_answer()) != std::string::npos) { 
       bAlarmFlag_ = true; 
       std::cout << "I received a \033[1;31mALARM-Signal\033[0m from Server!" << std::endl; 

      } else if(header_string.find(build_wpl_answer()) != std::string::npos) { 

       std::cout << std::endl << "List \033[1;34mData\033[0m received." << std::endl; 

/* HERE I READ A BLOCK OF THE LIST BUT NOT THE WHOLE LIST!!!! */ 
std::cout << &response_; 

      } 

     } 

     boost::asio::async_read(socket_, response_, boost::asio::transfer_at_least(1), 
           boost::bind(&Client::readContent, this, boost::asio::placeholders::error)); 

    } else { 

     std::cout << "Handle Message Error: " << error_msg.message() << std::endl; 
    } 
} 

void Client::readContent(const boost::system::error_code &error_msg) 
{ 
    if(!error_msg) { 

     /* Read until the EOF */ 
     boost::asio::async_read(socket_, response_, boost::asio::transfer_at_least(1), 
           boost::bind(&Client::readContent, this, boost::asio::placeholders::error)); 

    } else if (error_msg != boost::asio::error::eof) { 
     std::cout << "Read Content Error: " << error_msg.message() << std::endl; 
    } 
} 

void Client::buildQuery(void) 
{ 
    std::ostream ssRequest(&request_); 

    ssRequest << "GET /api/rest/v1/register?"; 
    ssRequest << "id=" << uiId_ << '&' << "delay=" << (static_cast<int>(server_response_delay_.count())) << '&' << "lon=" << dLon_ << '&'; 
    ssRequest << "lat=" << dLat_ << '&' << "height=" << dHeight_ << '&' << "state=" << state_ << '&' << "error=" << uiError_; 
    ssRequest << " HTTP/1.0\r\nHost: " << host_server_ << "\r\nUser-Agent: " << useragent_ << "\r\n\r\n"; 

} 

std::string Client::build_ok_answer(void) 
{ 
    std::ostringstream ssAnswerOk; 
    ssAnswerOk << "{\"Text\":\"OK\",\"Id\":" << uiId_ << "}"; 
    return ssAnswerOk.str(); 
} 

std::string Client::build_alarm_answer(void) 
{ 
    std::ostringstream ssAnswerAlarm; 
    ssAnswerAlarm << "{\"Text\":\"ALARM\",\"Id\":" << uiId_ << "}"; 
    return ssAnswerAlarm.str(); 
} 

std::string Client::build_wpl_answer(void) 
{ 
    std::ostringstream ssAnswerWpl; 
    ssAnswerWpl << "QGC WPL 120"; 
    return ssAnswerWpl.str(); 
} 

std::chrono::milliseconds Client::getServerResponseDelay(void) const 
{ 
    return server_response_delay_; 
} 

void Client::build_wpl_query(void) 
{ 
    std::ostream ssRequestWpl(&request_); 
    ssRequestWpl << "GET /File/data" << uiId_ << ".txt"; 
    ssRequestWpl << " HTTP/1.0\r\nHost: " << host_server_ << "\r\nUser-Agent: " << useragent_ << "\r\n\r\n"; 
} 

現在...我的靈感來自boost website上的例子。如果我啓動程序,它會開始每2秒鐘詢問一次服務器是否正常。 我收到了正確答案:

Constructor of the class "Client" called 

----------------------------------- 
Request Nr: 1 Socket created Waiting answer from Server... 

HTTP/1.1 200 OK 
Server OK, start retrieving data 
I received a ALIVE-Signal from the Server. 
IO Service status: 6 

------------------------------------ 
Request Nr: 2 Socket created Waiting answer from Server... 

HTTP/1.1 200 OK 
Server OK, start retrieving data 
I received a ALIVE-Signal from the Server. 
IO Service status: 6 

------------------------------------ 
Request Nr: 3 Socket created Waiting answer from Server... 

HTTP/1.1 200 OK 
Server OK, start retrieving data 
I received a ALIVE-Signal from the Server. 
IO Service status: 6 

------------------------------------ 

但據我開始要求我收到下面的列表:

Request Nr: 6 
Socket created 
Waiting answer from Server... 

HTTP/1.1 200 OK 
Server OK, start retrieving data 

List Data received. 
0 1 3 22 1 1000 1 3 51.9896769729347099 8.62269043922424316 35 1 0 
1 0 3 16 1 1000 3 1 51.9908133374338917 8.6260378360748291 0 1 0 
2 0 3 16 1 5000 3 1 51.9903706872495235 8.62733602523803711 -30 1 1 
3 0 3 16 1 1000 3 1 51.9906 
IO Service status: 7 

這是不是真的整個列表。 列表有幾個多行:

0 1 3 22 1 1000 1 3 231.9896769729347099 83.62269043922424316 35 1 0 
1 0 3 16 1 1000 3 1 221.9908133374338917 82.6260378360748291 0 1 0 
2 0 3 16 1 5000 3 1 121.9903706872495235 84.62733602523803711 -30 1 1 
3 0 3 16 1 1000 3 1 45.99066872495235564 

82.62733602523803711 -30 1 1 2 0 3 16 1 5000 3 1 124.9903706872495235 24.62733602324442711 -30 1 1 3 0 3 16 1 1000 3 1 22.99066872495235564 22.62235353533535351 - 30 1 1

在我看來,連接(也許我錯了這個)突然中斷。然後它從頭開始再次接收剩餘的線。 但我需要一次收到整個列表。 有什麼不對?


編輯:非常感謝rhashimoto。我幫助我瞭解了正在發生的事情以及可以改進的方面。 他的anwser沒有解決這個問題。因爲,如果我這樣做:

void Client::run(void) 
{ 
    if(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - begin_time_) > server_response_delay_) 
    { 
     begin_time_ = std::chrono::high_resolution_clock::now(); 

     boost::asio::ip::tcp::resolver::query local_query(host_server_, port_); 

     resolver_.async_resolve(local_query, boost::bind(&Client::handleResolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator)); 

     size_t service_status = io_service_.run(); 

     if(flag_) { 

      list << &response_; 

      std::cout << list.str(); 
     } 

     std::cout << "IO Service status: " << service_status << std::endl << std::endl; 

     io_service_.reset(); 

    } 
} 

(其中當我請求列表標誌只是一次設置),然後我收到列表的第二部分,但不是第一個。

有什麼想法?

如果我在沒有標誌變量的情況下放置該行,那麼連接就會在這一點上發生變化,並且不會顯示任何東西。

+0

您是否根據rhashimoto的建議移動了'ostream'插入內容,或者您​​現在是否調用了兩次? 'ostream'的插入操作將消耗streambuf的輸入序列([demo](http://coliru.stacked-crooked.com/a/6cdf00a82eee3d13))。此外,使用['transfer_all'](http://www.boost.org/doc/libs/1_59_0/doc/html/booster_asio/reference/transfer_all.html)完成條件可能更加可選,表達意圖簡潔與'transfer_at_least(1)'鏈相比。 –

+0

感謝您的評論。我嘗試了'transfer_all()'方法,並且工作得非常好。我所做的是以下內容:我完全改寫並重寫了我的代碼。我在上面發佈的代碼中發現了更多的錯誤和誤解。最後,我明白了什麼rhashimoto說:我的代碼讀取郵件的標題+一些數據。然後它再次運行並獲取剩餘的數據。由於它現在分成兩個流,我無法一次獲得所有信息。我沒有做他所說的話,但我更瞭解它的工作原理 – Dave

回答

2

您的async_read*處理程序缺少bytes_transferred參數。他們應該有signature

void handler(
    const boost::system::error_code& error, // Result of operation. 
    std::size_t bytes_transferred   // Number of bytes copied into the 
              // buffers. If an error occurred, 
              // this will be the number of 
              // bytes successfully transferred 
              // prior to the error. 
); 

請注意,您還需要添加的佔位符的說法,無論你將它們綁定。

雖然這不是你的問題。問題在於你在之前打印出響應主體。需要注意的是這行:

/* HERE I READ A BLOCK OF THE LIST BUT NOT THE WHOLE LIST!!!! */ 
std::cout << &response_; 

在此行之前執行:

boost::asio::async_read(socket_, response_, boost::asio::transfer_at_least(1), 
         boost::bind(&Client::readContent, this, boost::asio::placeholders::error)); 

好了,現在你可能想知道如何任何身體可以得到打印出來,你讀它。答案是您使用async_read_until來讀取HTTP狀態行和所有標題。文檔註釋:

成功async_read_until操作後,流緩衝可以 含有超出分隔符附加數據。應用程序 通常將該數據保留在streambuf中以供隨後的async_read_until操作檢查。

所以你打印出你沒有用std::istream消耗的額外數據。這恰好是您列表的第一部分。然後你繼續閱讀其餘的內容,但你不打印出來,所以它只是在streambuf(你會看到下一個請求)。

試一下std::cout線移動到Client::run()後讀操作完成:

void Client::run(void) 
{ 
    if(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - begin_time_) > server_response_delay_) 
    { 
     begin_time_ = std::chrono::high_resolution_clock::now(); 

     boost::asio::ip::tcp::resolver::query local_query(host_server_, port_); 

     resolver_.async_resolve(local_query, boost::bind(&Client::handleResolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator)); 

     size_t service_status = io_service_.run(); 

     // ****************************** 
     // ***** MOVE PRINT TO HERE ***** 
     // ****************************** 
     std::cout << &response_; 

     std::cout << "IO Service status: " << service_status << std::endl << std::endl; 

     io_service_.reset(); 
    } 
} 

或者你可以在Client::readContent()上面放(如果可以將其打印出來的塊)。